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

Viewing files at commit 9a8fdf2387ffCommit detailsView latest

tests/api.test.ts 171 lines · 5.8 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
    expect(
13
      (await app.inject({ method: 'GET', url: '/extensions/trust/v0.1' })).json(),
14
    ).toMatchObject({ version: '0.1', transportsMessages: false });
15
    const specification = (await app.inject({ method: 'GET', url: '/openapi.json' })).json();
16
    expect(specification.info.title).toBe('OpenClasp API');
17
    expect(specification.paths).toHaveProperty('/v0.1/risk/assess');
18
    await app.close();
19
  });
20
21
  it('isolates hosted dashboard and settings by authenticated operator', async () => {
22
    const calls: string[] = [];
23
    const repository = {
24
      dashboard: async (operatorId: string) => {
25
        calls.push(`dashboard:${operatorId}`);
26
        return {
27
          agents: [],
28
          projects: [],
29
          installations: [],
30
          setupRequests: [],
31
          publications: [],
32
          interactions: [],
33
          federatedInteractions: [],
34
          events: [],
35
          conflicts: [],
36
          receipts: [],
37
          profiles: [],
38
        };
39
      },
40
      getSettings: async (operatorId: string) => {
41
        calls.push(`settings:${operatorId}`);
42
        return {
43
          displayName: '',
44
          contributionEnabled: false,
45
          retentionDays: 30,
46
          evidenceSharing: 'ask' as const,
47
          rawConversationsStored: false as const,
48
        };
49
      },
50
      saveSettings: async (operatorId: string, value: any) => {
51
        calls.push(`save:${operatorId}`);
52
        return { ...value, rawConversationsStored: false as const };
53
      },
54
      upsert: async () => undefined,
55
      list: async () => [],
56
      publishAgent: async (_operatorId: string, card: any) => card,
57
      unpublishAgent: async () => true,
58
      getPublishedAgent: async () => undefined,
59
      searchPublishedAgents: async () => [],
60
    };
61
    const app = buildApi(undefined, undefined, repository);
62
    await app.ready();
63
    expect((await app.inject({ method: 'GET', url: '/v0.1/dashboard' })).statusCode).toBe(401);
64
    expect(
65
      (
66
        await app.inject({
67
          method: 'GET',
68
          url: '/v0.1/dashboard',
69
          headers: { 'x-openclasp-operator': 'user-a' },
70
        })
71
      ).statusCode,
72
    ).toBe(200);
73
    expect(
74
      (
75
        await app.inject({
76
          method: 'GET',
77
          url: '/v0.1/settings',
78
          headers: { 'x-openclasp-operator': 'user-b' },
79
        })
80
      ).statusCode,
81
    ).toBe(200);
82
    expect(calls).toEqual(['dashboard:user-a', 'settings:user-b']);
83
    await app.close();
84
  });
85
86
  it('publishes only an owned agent public card', async () => {
87
    const published: any[] = [];
88
    const repository = {
89
      dashboard: async () => ({}) as any,
90
      getSettings: async () => ({}) as any,
91
      saveSettings: async () => ({}) as any,
92
      upsert: async () => undefined,
93
      list: async () => [
94
        {
95
          kind: 'agent_profile' as const,
96
          recordId: 'agent-one',
97
          payload: {
98
            agentId: 'agent-one',
99
            projectId: 'secret-project',
100
            name: 'Research agent',
101
            description: 'Finds primary sources',
102
            framework: 'Codex',
103
            agentVersion: '1.0.0',
104
            a2aEndpoint: 'https://agent.example/a2a',
105
            capabilities: ['research'],
106
            limitations: ['no purchases'],
107
            identityMode: 'oauth_installation' as const,
108
            status: 'active' as const,
109
            createdAt: '2026-01-01T00:00:00.000Z',
110
            updatedAt: '2026-01-01T00:00:00.000Z',
111
          },
112
        },
113
      ],
114
      publishAgent: async (_operatorId: string, card: any) => {
115
        published.push(card);
116
        return card;
117
      },
118
      unpublishAgent: async () => true,
119
      getPublishedAgent: async () => published[0],
120
      searchPublishedAgents: async () => published,
121
    };
122
    const app = buildApi(undefined, undefined, repository);
123
    await app.ready();
124
    const response = await app.inject({
125
      method: 'POST',
126
      url: '/v0.1/agents/agent-one/publication',
127
      headers: { 'x-openclasp-operator': 'owner-one' },
128
      payload: { published: true },
129
    });
130
    expect(response.statusCode).toBe(200);
131
    expect(published[0]).toMatchObject({
132
      agentId: 'agent-one',
133
      capabilities: ['research'],
134
      assurance: 'oauth_authenticated',
135
    });
136
    expect(published[0]).not.toHaveProperty('projectId');
137
    expect(published[0]).not.toHaveProperty('operatorId');
138
    const publicCard = await app.inject({
139
      method: 'GET',
140
      url: '/agents/agent-one/card.json',
141
    });
142
    expect(publicCard.statusCode).toBe(200);
143
    expect(publicCard.json()).not.toHaveProperty('projectId');
144
    const a2aCard = await app.inject({
145
      method: 'GET',
146
      url: '/agents/agent-one/a2a-agent-card.json',
147
    });
148
    expect(a2aCard.statusCode).toBe(200);
149
    expect(a2aCard.json().supportedInterfaces[0]).toMatchObject({
150
      url: 'https://agent.example/a2a',
151
      protocolVersion: '1.0',
152
    });
153
    const automation = await app.inject({
154
      method: 'PUT',
155
      url: '/v0.1/agents/agent-one/automation',
156
      headers: { 'x-openclasp-operator': 'owner-one' },
157
      payload: {
158
        a2aEndpoint: 'https://agent.example/a2a',
159
        autoPublish: true,
160
        autoAcceptPolicy: 'safe_matching',
161
        autoAcceptTaskCategories: ['research'],
162
      },
163
    });
164
    expect(automation.statusCode).toBe(200);
165
    expect(automation.json()).toMatchObject({
166
      autoPublish: true,
167
      autoAcceptPolicy: 'safe_matching',
168
    });
169
    await app.close();
170
  });
171
});