durable-mcp-oauth-integration.test.mjs
90 lines 3.5 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Phase A — durable MCP OAuth: integration tier.
3 * authorize → token → refresh → restart (new provider, same store) still works;
4 * offline-lock mount guard asserted via shouldMountDurableAgentAuth + server source.
5 */
6
7 import { describe, it, afterEach } from 'node:test';
8 import assert from 'node:assert/strict';
9 import fs from 'node:fs';
10 import path from 'node:path';
11 import { fileURLToPath } from 'node:url';
12 import {
13 createDurableMcpProvider,
14 mintMcpTokens,
15 TEST_SECRET,
16 createTempStrongStore,
17 } from './helpers/durable-mcp-oauth-harness.mjs';
18 import { KnowtationOAuthProvider } from '../hub/gateway/mcp-oauth-provider.mjs';
19 import { shouldMountDurableAgentAuth } from '../hub/gateway/access-token-authz.mjs';
20 import { createGatewayRefreshStore } from '../hub/gateway/refresh-token-store.mjs';
21
22 const __dirname = path.dirname(fileURLToPath(import.meta.url));
23 const cleanups = [];
24 afterEach(async () => {
25 while (cleanups.length) await cleanups.pop()();
26 });
27
28 describe('Phase A integration — durable MCP refresh across restart', () => {
29 it('refresh survives provider destroy + new provider on same store dir', async () => {
30 const tmp = await createTempStrongStore();
31 cleanups.push(tmp.cleanup);
32
33 const provider1 = new KnowtationOAuthProvider({
34 sessionSecret: TEST_SECRET,
35 baseUrl: 'http://localhost:3340',
36 refreshStore: tmp.store,
37 });
38 const { client, tokens } = await mintMcpTokens(provider1, {
39 scopes: ['vault:read', 'vault:write'],
40 clientName: 'Hermes',
41 });
42 provider1.destroy();
43
44 // Simulate gateway restart: new provider instance, same strong store.
45 const store2 = createGatewayRefreshStore({ consistency: 'strong' });
46 const provider2 = new KnowtationOAuthProvider({
47 sessionSecret: TEST_SECRET,
48 baseUrl: 'http://localhost:3340',
49 refreshStore: store2,
50 });
51 // Client registration is in-memory — re-register same id is not possible; use original
52 // client object shape with same client_id for token exchange (SDK would re-discover).
53 const refreshed = await provider2.exchangeRefreshToken(client, tokens.refresh_token);
54 assert.ok(refreshed.access_token);
55 assert.ok(refreshed.refresh_token);
56 assert.notEqual(refreshed.refresh_token, tokens.refresh_token);
57 });
58
59 it('strong store ignores blob global (MCP refresh never uses eventual blob)', async () => {
60 const { provider, store, cleanup } = await createDurableMcpProvider();
61 cleanups.push(cleanup);
62 assert.equal(store.consistency, 'strong');
63 const blobWrites = { n: 0 };
64 globalThis.__knowtation_gateway_auth_blob = {
65 async get() { return null; },
66 async setJSON() { blobWrites.n += 1; },
67 };
68 try {
69 await mintMcpTokens(provider);
70 assert.equal(blobWrites.n, 0, 'strong store must not write to blob');
71 } finally {
72 delete globalThis.__knowtation_gateway_auth_blob;
73 }
74 });
75
76 it('offline-lock: durable agent auth must not mount', () => {
77 assert.equal(shouldMountDurableAgentAuth({
78 sessionSecret: 'secret',
79 netlify: false,
80 offlineLockedActive: true,
81 }), false);
82 const src = fs.readFileSync(
83 path.join(__dirname, '..', 'hub/gateway/server.mjs'),
84 'utf8'
85 );
86 assert.ok(src.includes('shouldMountDurableAgentAuth'), 'server must gate on helper');
87 assert.ok(src.includes('offline-locked mode'), 'server documents offline-lock skip');
88 assert.ok(src.includes('refreshStore'), 'MCP provider must receive refreshStore');
89 });
90 });
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago