File size: 3,498 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
import { describe, expect, it } from 'vitest';

import {
  capabilityStatusSchema,
  listCapabilitiesResponseSchema,
} from '../src/protocol/rest-capability';
import { sharedAuthHeaders, sharedServer } from './helpers/sharedServer';

interface Envelope<T> {
  code: number;
  msg: string;
  data: T;
  request_id: string;
}

describe('server-v2 /api/v1 capabilities', () => {
  async function getJson<T>(path: string): Promise<{ status: number; body: Envelope<T> }> {
    const res = await fetch(`${sharedServer().base}${path}`, {
      headers: sharedAuthHeaders(),
    } as never);
    return { status: res.status, body: (await res.json()) as Envelope<T> };
  }

  async function postJson<T>(path: string): Promise<{ status: number; body: Envelope<T> }> {
    const res = await fetch(`${sharedServer().base}${path}`, {
      method: 'POST',
      headers: sharedAuthHeaders({ 'content-type': 'application/json' }),
      body: '{}',
    } as never);
    return { status: res.status, body: (await res.json()) as Envelope<T> };
  }

  it('lists both built-in capabilities with the documented shape', async () => {
    const { body } = await getJson<unknown>('/api/v1/capabilities');
    expect(body.code).toBe(0);
    const parsed = listCapabilitiesResponseSchema.parse(body.data);
    const ids = parsed.capabilities.map((c) => c.id).toSorted();
    expect(ids).toEqual(['kimi-cu', 'kimi-webbridge']);
    for (const capability of parsed.capabilities) {
      expect(capabilityStatusSchema.parse(capability)).toBeTruthy();
      expect(capability.install.running).toBe(false);
    }
    const kimiCu = parsed.capabilities.find((c) => c.id === 'kimi-cu');
    if (process.platform === 'darwin' || (process.platform === 'win32' && process.arch === 'x64')) {
      expect(kimiCu?.supported).toBe(true);
    } else {
      expect(kimiCu?.supported).toBe(false);
      expect(kimiCu?.state).toBe('unsupported');
    }
    const webbridge = parsed.capabilities.find((c) => c.id === 'kimi-webbridge');
    expect(webbridge?.supported).toBe(true);
    expect(webbridge?.steps.find((s) => s.id === 'skill')?.state).toBe('missing');
    expect(webbridge?.steps.find((s) => s.id === 'extension')?.optional).toBe(true);
  });

  it('gets a single capability and 40418s on an unknown id', async () => {
    const { body } = await getJson<unknown>('/api/v1/capabilities/kimi-webbridge');
    expect(body.code).toBe(0);
    expect(capabilityStatusSchema.parse(body.data).id).toBe('kimi-webbridge');

    const missing = await getJson<unknown>('/api/v1/capabilities/nope');
    expect(missing.body.code).toBe(40418);
    expect(missing.body.data).toBeNull();
  });

  it('installs 40418 on an unknown id without side effects', async () => {
    const { body } = await postJson<unknown>('/api/v1/capabilities/nope:install');
    expect(body.code).toBe(40418);
  });

  it('rejects bare ids and unknown actions with 40001', async () => {
    const bare = await postJson<unknown>('/api/v1/capabilities/kimi-cu');
    expect(bare.body.code).toBe(40001);
    const bogus = await postJson<unknown>('/api/v1/capabilities/kimi-cu:uninstall');
    expect(bogus.body.code).toBe(40001);
  });

  it.skipIf(process.platform === 'darwin' || (process.platform === 'win32' && process.arch === 'x64'))(
    'rejects kimi-cu install on unsupported platforms with 40925',
    async () => {
      const { body } = await postJson<unknown>('/api/v1/capabilities/kimi-cu:install');
      expect(body.code).toBe(40925);
    },
  );
});