calendar-oauth-connector-e2e.test.mjs
120 lines 4.0 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Tier 3 — E2E: connect → timeline events → revoke purge (handler-level walkthrough).
3 */
4 import { describe, it, beforeEach, afterEach } from 'node:test';
5 import assert from 'node:assert/strict';
6 import fs from 'node:fs';
7 import path from 'node:path';
8 import { fileURLToPath } from 'node:url';
9 import {
10 handleBeginGoogleConnector,
11 handleGoogleConnectorCallback,
12 handleRevokeGoogleConnector,
13 createFakeGoogleClient,
14 findPendingConnectorByState,
15 } from '../lib/calendar/google-oauth-connector.mjs';
16 import { buildCalendarTimeline } from '../lib/calendar/timeline.mjs';
17 import { queryStoredEvents } from '../lib/calendar/event-store.mjs';
18
19 const __dirname = path.dirname(fileURLToPath(import.meta.url));
20 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-calendar-oauth-e2e');
21 const RETURN_URL = 'https://app.scooling.local:5174/settings/calendar/connect/return';
22 const testEnv = {
23 GOOGLE_CALENDAR_OAUTH_CLIENT_ID: 'cid',
24 GOOGLE_CALENDAR_OAUTH_CLIENT_SECRET: 'sec',
25 KNOWTATION_CALENDAR_OAUTH_SECRET: 'z'.repeat(40),
26 CALENDAR_OAUTH_REDIRECT_URI: 'https://hub.local/callback',
27 SCOOLING_RETURN_URL_ALLOWLIST: RETURN_URL,
28 };
29
30 describe('calendar oauth e2e walkthrough', () => {
31 let dataDir;
32
33 beforeEach(() => {
34 fs.rmSync(tmpRoot, { recursive: true, force: true });
35 dataDir = path.join(tmpRoot, 'data');
36 fs.mkdirSync(dataDir, { recursive: true });
37 });
38
39 afterEach(() => {
40 fs.rmSync(tmpRoot, { recursive: true, force: true });
41 });
42
43 it('connected Google events appear on timeline then revoke empties store', async () => {
44 const begin = handleBeginGoogleConnector({
45 dataDir,
46 vaultId: 'default',
47 body: { provider: 'google', return_url: RETURN_URL },
48 env: testEnv,
49 authorizedOverride: true,
50 });
51 const state = new URL(begin.payload.authorization_url).searchParams.get('state');
52 const located = findPendingConnectorByState(dataDir, state);
53 const googleClient = createFakeGoogleClient();
54 await handleGoogleConnectorCallback({
55 dataDir,
56 query: { code: 'code', state: located.connector.oauth_pending.state },
57 googleClient,
58 env: testEnv,
59 authorizedOverride: true,
60 });
61
62 const before = queryStoredEvents(dataDir, 'default', {
63 fromIso: '2026-06-01T00:00:00.000Z',
64 toIso: '2026-06-30T23:59:59.999Z',
65 displayOnly: true,
66 });
67 assert.ok(before.length >= 1);
68
69 await handleRevokeGoogleConnector({
70 dataDir,
71 vaultId: 'default',
72 connectorId: begin.payload.connector_id,
73 googleClient,
74 env: testEnv,
75 authorizedOverride: true,
76 });
77
78 const after = queryStoredEvents(dataDir, 'default', {
79 fromIso: '2026-06-01T00:00:00.000Z',
80 toIso: '2026-06-30T23:59:59.999Z',
81 });
82 assert.equal(after.length, 0);
83 });
84
85 it('invalid_grant on refresh sets needs_reauth', async () => {
86 const begin = handleBeginGoogleConnector({
87 dataDir,
88 vaultId: 'default',
89 body: { provider: 'google', return_url: RETURN_URL },
90 env: testEnv,
91 authorizedOverride: true,
92 });
93 const state = new URL(begin.payload.authorization_url).searchParams.get('state');
94 const located = findPendingConnectorByState(dataDir, state);
95 const googleClient = createFakeGoogleClient();
96 await handleGoogleConnectorCallback({
97 dataDir,
98 query: { code: 'code', state: located.connector.oauth_pending.state },
99 googleClient,
100 env: testEnv,
101 authorizedOverride: true,
102 });
103
104 const badClient = createFakeGoogleClient({
105 tokenResponse: { error: 'invalid_grant' },
106 });
107 const { handleSyncGoogleConnector } = await import('../lib/calendar/google-oauth-connector.mjs');
108 const sync = await handleSyncGoogleConnector({
109 dataDir,
110 vaultId: 'default',
111 connectorId: begin.payload.connector_id,
112 googleClient: badClient,
113 env: testEnv,
114 now: Date.now() + 120_000,
115 authorizedOverride: true,
116 });
117 assert.equal(sync.ok, false);
118 assert.equal(sync.code, 'AUTH_EXPIRED');
119 });
120 });
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago