fix(gdrive): validate folder access, run config test, harden Drive API (#321)

* fix(gdrive): validate folder access, run config test, harden Drive API

Connection test and setup validation now verify folder_id resolves to an accessible Drive folder before counting Docs, via a shared verifyGdriveFolderAndCountDocs helper, so a wrong or unshared folder fails instead of passing with 0 docs.

Move gdrive-config.test.ts under test/ so Vitest's test/** glob actually runs it; escape folder_id in the Drive query; add retry/backoff on transient Google API responses; and record skipped non-Google-Doc files in the staged manifest.

* chore: sync uv.lock to ktx-daemon/ktx-sl 0.13.1
This commit is contained in:
Andrey Avtomonov 2026-06-28 01:02:37 +02:00 committed by GitHub
parent 5645dc4d28
commit ca231df5fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 346 additions and 65 deletions

View file

@ -434,7 +434,15 @@ describe('runKtxConnection', () => {
],
nextPageToken: null,
}));
const createGdriveClient = vi.fn(async () => ({ listFiles }));
const getFile = vi.fn(async () => ({
id: 'folder-123',
name: 'Docs',
mimeType: 'application/vnd.google-apps.folder',
parents: [],
webViewLink: null,
modifiedTime: null,
}));
const createGdriveClient = vi.fn(async () => ({ listFiles, getFile }));
const io = makeIo();
await expect(
@ -442,12 +450,38 @@ describe('runKtxConnection', () => {
).resolves.toBe(0);
expect(createGdriveClient).toHaveBeenCalledWith(expect.objectContaining({ projectDir }), 'docs_drive');
expect(listFiles).toHaveBeenCalledWith({ q: "'folder-123' in parents and trashed = false" });
expect(getFile).toHaveBeenCalledWith('folder-123');
expect(listFiles).toHaveBeenCalledWith({ q: "'folder-123' in parents and trashed = false", pageToken: undefined });
expect(io.stdout()).toContain('Connection test passed: docs_drive');
expect(io.stdout()).toContain('Driver: gdrive');
expect(io.stdout()).toContain('Docs: 1');
});
it('fails a Google Drive connection test when the folder is not accessible', async () => {
const projectDir = join(tempDir, 'project');
await initKtxProject({ projectDir });
await writeConnections(projectDir, {
docs_drive: {
driver: 'gdrive',
service_account_key_ref: 'file:/tmp/gdrive-key.json', // pragma: allowlist secret
folder_id: 'missing-folder',
recursive: false,
},
});
const listFiles = vi.fn();
const getFile = vi.fn(async () => null);
const createGdriveClient = vi.fn(async () => ({ listFiles, getFile }));
const io = makeIo();
await expect(
runKtxConnection({ command: 'test', projectDir, connectionId: 'docs_drive' }, io.io, { createGdriveClient }),
).resolves.toBe(1);
expect(getFile).toHaveBeenCalledWith('missing-folder');
expect(listFiles).not.toHaveBeenCalled();
expect(io.stderr()).toContain('is not accessible');
});
it('tests a dbt connection via testRepoConnection (success)', async () => {
const projectDir = join(tempDir, 'project');
await initKtxProject({ projectDir });
@ -593,6 +627,14 @@ describe('runKtxConnection', () => {
],
nextPageToken: null,
})),
getFile: vi.fn(async () => ({
id: 'folder-123',
name: 'Docs',
mimeType: 'application/vnd.google-apps.folder',
parents: [],
webViewLink: null,
modifiedTime: null,
})),
}));
const io = makeIo();