agent-credentials-e2e.test.mjs
127 lines 5.2 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 9 days ago
1 /**
2 * Phase C + Lane D — e2e: session mint + list + rotate + revoke; list health envelope.
3 */
4
5 import { describe, it } from 'node:test';
6 import assert from 'node:assert/strict';
7 import fs from 'node:fs/promises';
8 import os from 'node:os';
9 import path from 'node:path';
10 import http from 'node:http';
11 import jwt from 'jsonwebtoken';
12 import express from 'express';
13 import { createAgentCredentialRouter } from '../hub/gateway/agent-credential-routes.mjs';
14 import { parseAgentCredential } from '../hub/lib/agent-credential-core.mjs';
15
16 const SECRET = 'phase-c-e2e-test-secret-value-32bytes!';
17
18 describe('Phase C e2e — agent credential lifecycle', () => {
19 it('mint list rotate revoke', async () => {
20 const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kt-agent-e2e-'));
21 process.env.KNOWTATION_GATEWAY_DATA_DIR = dir;
22 const app = express();
23 const session = jwt.sign({ sub: 'github:42', type: 'session' }, SECRET, { expiresIn: '1h' });
24 const { router } = createAgentCredentialRouter({
25 sessionSecret: SECRET,
26 getSessionSub: () => 'github:42',
27 getSessionPayload: () => ({ sub: 'github:42', type: 'session' }),
28 grantedScopes: () => ['vault:read', 'vault:write'],
29 });
30 app.use('/api/v1/auth/agent', router);
31 const server = http.createServer(app);
32 await new Promise((r) => server.listen(0, r));
33 const base = `http://127.0.0.1:${server.address().port}`;
34 try {
35 const mint = await fetch(`${base}/api/v1/auth/agent/credentials`, {
36 method: 'POST',
37 headers: { Authorization: `Bearer ${session}`, 'Content-Type': 'application/json' },
38 body: JSON.stringify({ name: 'e2e', vault_ids: ['default'] }),
39 });
40 assert.equal(mint.status, 201);
41 const m = await mint.json();
42 const list = await (await fetch(`${base}/api/v1/auth/agent/credentials`, {
43 headers: { Authorization: `Bearer ${session}` },
44 })).json();
45 assert.equal(list.credentials.length, 1);
46 assert.equal(list.credentials[0].id, m.id);
47 assert.equal(list.credentials[0].credential, undefined);
48 assert.ok('revoked_at' in list.credentials[0]);
49 assert.ok('last_failure_code' in list.credentials[0]);
50 assert.ok(list.store && list.store.wipe_required === false);
51 assert.ok(list.store.inconsistent === false);
52
53 const rot = await fetch(`${base}/api/v1/auth/agent/credentials/${m.id}/rotate`, {
54 method: 'POST',
55 headers: { Authorization: `Bearer ${session}` },
56 });
57 assert.equal(rot.status, 200);
58 const r = await rot.json();
59 assert.ok(parseAgentCredential(r.credential));
60 assert.notEqual(r.credential, m.credential);
61
62 const oldTok = await fetch(`${base}/api/v1/auth/agent/token`, {
63 method: 'POST',
64 headers: { 'Content-Type': 'application/json' },
65 body: JSON.stringify({ credential: m.credential }),
66 });
67 assert.equal(oldTok.status, 401);
68
69 const newTok = await fetch(`${base}/api/v1/auth/agent/token`, {
70 method: 'POST',
71 headers: { 'Content-Type': 'application/json' },
72 body: JSON.stringify({ credential: r.credential }),
73 });
74 assert.equal(newTok.status, 200);
75
76 await fetch(`${base}/api/v1/auth/agent/credentials/${m.id}`, {
77 method: 'DELETE',
78 headers: { Authorization: `Bearer ${session}` },
79 });
80 const after = await (await fetch(`${base}/api/v1/auth/agent/token`, {
81 method: 'POST',
82 headers: { 'Content-Type': 'application/json' },
83 body: JSON.stringify({ credential: r.credential }),
84 })).json();
85 assert.equal(after.code, 'AGENT_CREDENTIAL_INVALID');
86 } finally {
87 await new Promise((r) => server.close(r));
88 delete process.env.KNOWTATION_GATEWAY_DATA_DIR;
89 await fs.rm(dir, { recursive: true, force: true });
90 }
91 });
92
93 it('list 503 returns code body not empty 200 credentials', async () => {
94 const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kt-agent-e2e-503-'));
95 process.env.KNOWTATION_GATEWAY_DATA_DIR = dir;
96 await fs.writeFile(
97 path.join(dir, 'hosted_agent_credentials.meta.json'),
98 JSON.stringify({ schema_version: 1, nonempty_seen: true, count: 1, updated_at: Date.now() }),
99 'utf8'
100 );
101 const app = express();
102 const session = jwt.sign({ sub: 'github:42', type: 'session' }, SECRET, { expiresIn: '1h' });
103 const { router } = createAgentCredentialRouter({
104 sessionSecret: SECRET,
105 getSessionSub: () => 'github:42',
106 getSessionPayload: () => ({ sub: 'github:42', type: 'session' }),
107 grantedScopes: () => ['vault:read'],
108 });
109 app.use('/api/v1/auth/agent', router);
110 const server = http.createServer(app);
111 await new Promise((r) => server.listen(0, r));
112 const base = `http://127.0.0.1:${server.address().port}`;
113 try {
114 const res = await fetch(`${base}/api/v1/auth/agent/credentials`, {
115 headers: { Authorization: `Bearer ${session}` },
116 });
117 assert.equal(res.status, 503);
118 const body = await res.json();
119 assert.equal(body.code, 'AGENT_CREDENTIAL_STORE_INCONSISTENT');
120 assert.notEqual(body.credentials, []);
121 } finally {
122 await new Promise((r) => server.close(r));
123 delete process.env.KNOWTATION_GATEWAY_DATA_DIR;
124 await fs.rm(dir, { recursive: true, force: true });
125 }
126 });
127 });
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 9 days ago