File size: 3,550 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

import { afterAll, beforeAll, describe, expect, it } from 'vitest';

import { type RunningServer, startServer } from '../src/start';
import { TEST_HOST_IDENTITY } from './helpers/hostIdentity';

let home: string;
let server: RunningServer | undefined;

beforeAll(async () => {
  home = mkdtempSync(join(tmpdir(), 'kimi-server-v2-file-history-'));
  server = await startServer({
    hostIdentity: TEST_HOST_IDENTITY,
    host: '127.0.0.1',
    port: 0,
    homeDir: home,
    logLevel: 'silent',
  });
});

afterAll(async () => {
  try {
    await server?.close();
  } catch {
  }
  server = undefined;
  rmSync(home, { recursive: true, force: true });
});

async function boot(): Promise<RunningServer> {
  return server as RunningServer;
}

interface InjectResponse {
  statusCode: number;
  body: string;
  json: () => unknown;
}

interface AppLike {
  inject: (req: unknown) => Promise<InjectResponse>;
}

function appOf(r: RunningServer): AppLike {
  const app = r.app as unknown as AppLike;
  return {
    inject(req: unknown): Promise<InjectResponse> {
      const request = req as { headers?: Record<string, string> };
      return app.inject({
        ...request,
        headers: {
          ...request.headers,
          authorization: `Bearer ${r.authTokenService.getToken()}`,
        },
      });
    },
  };
}

interface Envelope<T = unknown> {
  code: number;
  msg: string;
  data: T | null;
}

async function createSession(r: RunningServer): Promise<string> {
  const res = await appOf(r).inject({
    method: 'POST',
    url: '/api/v1/sessions',
    payload: { metadata: { cwd: home } },
    headers: { 'content-type': 'application/json' },
  });
  const envelope = res.json() as Envelope<{ id: string }>;
  if (envelope.code !== 0 || envelope.data === null) {
    throw new Error(`failed to create session: ${res.body}`);
  }
  return envelope.data.id;
}

describe('file history routes', () => {
  it('serves empty changes and null content for a live session without history', async () => {
    const r = await boot();
    const sessionId = await createSession(r);

    const changes = await appOf(r).inject({
      method: 'GET',
      url: `/api/v1/sessions/${sessionId}/file-history/changes?turn_id=1`,
    });
    expect(changes.statusCode).toBe(200);
    expect((changes.json() as Envelope<{ changes: unknown[] }>).data).toEqual({
      changes: [],
      recorded: false,
    });

    const content = await appOf(r).inject({
      method: 'GET',
      url: `/api/v1/sessions/${sessionId}/file-history/content?turn_id=1&path=a.txt`,
    });
    expect(content.statusCode).toBe(200);
    expect((content.json() as Envelope<{ content: unknown }>).data).toEqual({ content: null });
  });

  it('rejects a session that is not live', async () => {
    const r = await boot();
    const res = await appOf(r).inject({
      method: 'GET',
      url: '/api/v1/sessions/does-not-exist/file-history/changes?turn_id=1',
    });
    const envelope = res.json() as Envelope;
    expect(envelope.code).not.toBe(0);
    expect(envelope.data).toBeNull();
  });

  it('rejects a malformed turn_id', async () => {
    const r = await boot();
    const sessionId = await createSession(r);
    const res = await appOf(r).inject({
      method: 'GET',
      url: `/api/v1/sessions/${sessionId}/file-history/changes?turn_id=abc`,
    });
    const envelope = res.json() as Envelope;
    expect(envelope.code).not.toBe(0);
  });
});