The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit 5eea80c71925Commit detailsView latest

tests/api.test.ts 133 lines · 4.4 KB · typescript
1
import { describe, expect, it } from 'vitest';
2
import { buildApi } from '../apps/api/src/app.js';
3
4
describe('HTTP API', () => {
5
  it('serves health, readiness, and OpenAPI', async () => {
6
    const app = buildApi();
7
    await app.ready();
8
    expect((await app.inject({ method: 'GET', url: '/health' })).statusCode).toBe(200);
9
    expect((await app.inject({ method: 'GET', url: '/ready' })).json()).toEqual({
10
      status: 'ready',
11
    });
12
    const specification = (await app.inject({ method: 'GET', url: '/openapi.json' })).json();
13
    expect(specification.info.title).toBe('OpenClasp API');
14
    expect(specification.paths).toHaveProperty('/v0.1/risk/assess');
15
    await app.close();
16
  });
17
18
  it('isolates hosted dashboard and settings by authenticated operator', async () => {
19
    const calls: string[] = [];
20
    const repository = {
21
      dashboard: async (operatorId: string) => {
22
        calls.push(`dashboard:${operatorId}`);
23
        return {
24
          agents: [],
25
          projects: [],
26
          installations: [],
27
          setupRequests: [],
28
          publications: [],
29
          interactions: [],
30
          events: [],
31
          conflicts: [],
32
          receipts: [],
33
          profiles: [],
34
        };
35
      },
36
      getSettings: async (operatorId: string) => {
37
        calls.push(`settings:${operatorId}`);
38
        return {
39
          displayName: '',
40
          contributionEnabled: false,
41
          retentionDays: 30,
42
          evidenceSharing: 'ask' as const,
43
          rawConversationsStored: false as const,
44
        };
45
      },
46
      saveSettings: async (operatorId: string, value: any) => {
47
        calls.push(`save:${operatorId}`);
48
        return { ...value, rawConversationsStored: false as const };
49
      },
50
      upsert: async () => undefined,
51
      list: async () => [],
52
      publishAgent: async (_operatorId: string, card: any) => card,
53
      unpublishAgent: async () => true,
54
      getPublishedAgent: async () => undefined,
55
      searchPublishedAgents: async () => [],
56
    };
57
    const app = buildApi(undefined, undefined, repository);
58
    await app.ready();
59
    expect((await app.inject({ method: 'GET', url: '/v0.1/dashboard' })).statusCode).toBe(401);
60
    expect(
61
      (
62
        await app.inject({
63
          method: 'GET',
64
          url: '/v0.1/dashboard',
65
          headers: { 'x-openclasp-operator': 'user-a' },
66
        })
67
      ).statusCode,
68
    ).toBe(200);
69
    expect(
70
      (
71
        await app.inject({
72
          method: 'GET',
73
          url: '/v0.1/settings',
74
          headers: { 'x-openclasp-operator': 'user-b' },
75
        })
76
      ).statusCode,
77
    ).toBe(200);
78
    expect(calls).toEqual(['dashboard:user-a', 'settings:user-b']);
79
    await app.close();
80
  });
81
82
  it('publishes only an owned agent public card', async () => {
83
    const published: any[] = [];
84
    const repository = {
85
      dashboard: async () => ({}) as any,
86
      getSettings: async () => ({}) as any,
87
      saveSettings: async () => ({}) as any,
88
      upsert: async () => undefined,
89
      list: async () => [
90
        {
91
          kind: 'agent_profile' as const,
92
          recordId: 'agent-one',
93
          payload: {
94
            agentId: 'agent-one',
95
            projectId: 'secret-project',
96
            name: 'Research agent',
97
            framework: 'Codex',
98
            capabilities: ['research'],
99
            limitations: ['no purchases'],
100
            identityMode: 'oauth_installation' as const,
101
            status: 'active' as const,
102
            createdAt: '2026-01-01T00:00:00.000Z',
103
            updatedAt: '2026-01-01T00:00:00.000Z',
104
          },
105
        },
106
      ],
107
      publishAgent: async (_operatorId: string, card: any) => {
108
        published.push(card);
109
        return card;
110
      },
111
      unpublishAgent: async () => true,
112
      getPublishedAgent: async () => undefined,
113
      searchPublishedAgents: async () => published,
114
    };
115
    const app = buildApi(undefined, undefined, repository);
116
    await app.ready();
117
    const response = await app.inject({
118
      method: 'POST',
119
      url: '/v0.1/agents/agent-one/publication',
120
      headers: { 'x-openclasp-operator': 'owner-one' },
121
      payload: { published: true },
122
    });
123
    expect(response.statusCode).toBe(200);
124
    expect(published[0]).toMatchObject({
125
      agentId: 'agent-one',
126
      capabilities: ['research'],
127
      assurance: 'oauth_authenticated',
128
    });
129
    expect(published[0]).not.toHaveProperty('projectId');
130
    expect(published[0]).not.toHaveProperty('operatorId');
131
    await app.close();
132
  });
133
});