docs-oauth-connector-e2e.test.mjs
214 lines 7.3 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago
1 import assert from 'node:assert/strict';
2 import fs from 'node:fs';
3 import os from 'node:os';
4 import path from 'node:path';
5 import test from 'node:test';
6
7 import { createProposal, getProposal, updateProposalStatus } from '../hub/proposals-store.mjs';
8 import { writeNote } from '../lib/write.mjs';
9 import { readNote as readVaultNote } from '../lib/vault.mjs';
10 import {
11 createFakeGoogleDriveClient,
12 handleBeginDocsConnector,
13 handleDocsConnectorCallback,
14 handleImportDocsConnectorFiles,
15 handleListDocsConnectorFiles,
16 handleRevokeDocsConnector,
17 DOCS_OAUTH_GOOGLE_AUTHORIZED,
18 } from '../lib/docs/google-drive-connector.mjs';
19 import {
20 createFakeNotionClient,
21 handleBeginNotionConnector,
22 handleImportNotionConnectorFiles,
23 handleListNotionConnectorFiles,
24 handleRevokeNotionConnector,
25 DOCS_NOTION_HUB_KEY_AUTHORIZED,
26 } from '../lib/docs/notion-hub-connector.mjs';
27 import {
28 handleBeginDocsProvider,
29 handleListAllDocsConnectors,
30 } from '../lib/docs/docs-api.mjs';
31
32 const pageId = '01234567-89ab-cdef-0123-456789abcdef';
33 const driveEnv = {
34 GOOGLE_DRIVE_OAUTH_CLIENT_ID: 'client',
35 GOOGLE_DRIVE_OAUTH_CLIENT_SECRET: 'credential',
36 KNOWTATION_DOCS_OAUTH_SECRET: 'v'.repeat(32),
37 DOCS_OAUTH_REDIRECT_URI: 'https://hub.example/api/v1/docs/connectors/callback',
38 SCOOLING_RETURN_URL_ALLOWLIST: 'https://school.example/connect',
39 };
40
41 test('e2e: Notion Hub-key list, review import, and revoke keep notes untouched', async () => {
42 const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kn-docs-e2e-data-'));
43 const vaultPath = fs.mkdtempSync(path.join(os.tmpdir(), 'kn-docs-e2e-vault-'));
44 const env = { NOTION_API_KEY: 'server-only-value' };
45 const notionClient = createFakeNotionClient({
46 results: [{
47 object: 'page',
48 id: pageId,
49 last_edited_time: '2026-08-17T00:00:00Z',
50 properties: { Name: { type: 'title', title: [{ plain_text: 'Roadmap' }] } },
51 }],
52 markdownByPage: { [pageId]: '# Roadmap' },
53 });
54 const begin = handleBeginNotionConnector({
55 dataDir,
56 vaultId: 'vault-n',
57 body: { provider: 'notion', return_url: 'ignored' },
58 env,
59 authorizedOverride: true,
60 });
61 assert.equal(begin.payload.status, 'connected');
62 const listed = await handleListNotionConnectorFiles({
63 dataDir,
64 vaultId: 'vault-n',
65 connectorId: begin.payload.connector_id,
66 query: {},
67 env,
68 notionClient,
69 authorizedOverride: true,
70 });
71 assert.equal(listed.payload.files[0].name, 'Roadmap');
72 const proposals = [];
73 const imported = await handleImportNotionConnectorFiles({
74 dataDir,
75 vaultPath,
76 vaultId: 'vault-n',
77 connectorId: begin.payload.connector_id,
78 body: { file_ids: [pageId] },
79 env,
80 notionClient,
81 authorizedOverride: true,
82 createProposalFn: (_dir, input) => {
83 proposals.push(input);
84 return { ...input, proposal_id: 'notion-proposal', status: 'proposed' };
85 },
86 loadProposalsFn: () => [],
87 listMarkdownFilesFn: () => [],
88 });
89 assert.equal(imported.payload.proposed, 1);
90 assert.equal(proposals[0].path, `imports/notion/${pageId}.md`);
91 assert.equal(proposals[0].review_queue, 'docs-sync');
92 fs.writeFileSync(path.join(vaultPath, 'kept.md'), '# Canonical note', 'utf8');
93 const revoked = handleRevokeNotionConnector({
94 dataDir,
95 vaultId: 'vault-n',
96 connectorId: begin.payload.connector_id,
97 authorizedOverride: true,
98 });
99 assert.equal(revoked.payload.revoked, true);
100 assert.equal(fs.existsSync(path.join(vaultPath, 'kept.md')), true);
101 });
102
103 test('e2e: Drive connect → list metadata → docs-sync proposal → apply note → revoke keeps note', async () => {
104 assert.equal(DOCS_OAUTH_GOOGLE_AUTHORIZED, true);
105 assert.equal(DOCS_NOTION_HUB_KEY_AUTHORIZED, true);
106
107 const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kn-docs-e2e-drive-data-'));
108 const vaultPath = fs.mkdtempSync(path.join(os.tmpdir(), 'kn-docs-e2e-drive-vault-'));
109 const client = createFakeGoogleDriveClient({
110 files: [{
111 id: 'drive_file_e2e',
112 name: 'Brief',
113 mimeType: 'text/markdown',
114 modifiedTime: '2026-08-17T00:00:00Z',
115 size: '12',
116 }],
117 contents: { drive_file_e2e: '# Brief body\n' },
118 });
119
120 const denied = handleBeginDocsProvider({
121 dataDir,
122 vaultId: 'vault-d',
123 body: { provider: 'google-drive', return_url: 'https://school.example/connect' },
124 env: driveEnv,
125 authorizedOverride: false,
126 });
127 assert.equal(denied.code, 'NOT_AUTHORIZED');
128 assert.equal(denied.status, 501);
129
130 const begin = handleBeginDocsConnector({
131 dataDir,
132 vaultId: 'vault-d',
133 body: { provider: 'google-drive', return_url: 'https://school.example/connect' },
134 env: driveEnv,
135 authorizedOverride: true,
136 now: 1_000,
137 });
138 assert.equal(begin.ok, true);
139 const state = new URL(begin.payload.authorization_url).searchParams.get('state');
140 const callback = await handleDocsConnectorCallback({
141 dataDir,
142 query: { state, code: 'auth-code' },
143 googleClient: client,
144 env: driveEnv,
145 authorizedOverride: true,
146 now: 2_000,
147 });
148 assert.equal(callback.ok, true);
149 assert.match(callback.redirect, /connect=ok/);
150 assert.doesNotMatch(callback.redirect, /refresh|access_token|code_verifier/i);
151
152 const listedConnectors = handleListAllDocsConnectors({
153 dataDir,
154 vaultId: 'vault-d',
155 authorizedOverride: true,
156 });
157 assert.equal(listedConnectors.payload.connectors.length, 1);
158 assert.equal(listedConnectors.payload.connectors[0].status, 'connected');
159 assert.equal(Object.hasOwn(listedConnectors.payload.connectors[0], 'oauth_ref'), false);
160 assert.equal(Object.hasOwn(listedConnectors.payload.connectors[0], 'sync_cursor'), false);
161
162 const files = await handleListDocsConnectorFiles({
163 dataDir,
164 vaultId: 'vault-d',
165 connectorId: begin.payload.connector_id,
166 query: {},
167 googleClient: client,
168 env: driveEnv,
169 authorizedOverride: true,
170 });
171 assert.equal(files.payload.files[0].importable, true);
172 assert.equal(Object.hasOwn(files.payload.files[0], 'body'), false);
173
174 const imported = await handleImportDocsConnectorFiles({
175 dataDir,
176 vaultPath,
177 vaultId: 'vault-d',
178 connectorId: begin.payload.connector_id,
179 body: { file_ids: ['drive_file_e2e'] },
180 googleClient: client,
181 env: driveEnv,
182 authorizedOverride: true,
183 createProposalFn: createProposal,
184 });
185 assert.equal(imported.payload.proposed, 1);
186 assert.equal(imported.payload.proposal_ids.length, 1);
187
188 const proposal = getProposal(dataDir, imported.payload.proposal_ids[0]);
189 assert.equal(proposal.review_queue, 'docs-sync');
190 assert.equal(proposal.source, 'import');
191 assert.equal(proposal.frontmatter.source, 'google-drive');
192 assert.equal(proposal.frontmatter.source_id, 'drive_file_e2e');
193
194 // Human approve/apply via existing proposal path — live import never writeNote'd.
195 writeNote(vaultPath, proposal.path, {
196 body: proposal.body,
197 frontmatter: proposal.frontmatter,
198 });
199 updateProposalStatus(dataDir, proposal.proposal_id, 'approved', {});
200 const note = readVaultNote(vaultPath, proposal.path);
201 assert.equal(note.frontmatter.source, 'google-drive');
202 assert.match(note.body, /Brief body/);
203
204 const revoked = await handleRevokeDocsConnector({
205 dataDir,
206 vaultId: 'vault-d',
207 connectorId: begin.payload.connector_id,
208 googleClient: client,
209 env: driveEnv,
210 authorizedOverride: true,
211 });
212 assert.equal(revoked.payload.revoked, true);
213 assert.equal(fs.existsSync(path.join(vaultPath, proposal.path)), true);
214 });
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago