@commandable/integration-data 0.0.1 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/credentials-index.d.ts +4 -21
- package/dist/credentials-index.d.ts.map +1 -1
- package/dist/credentials-index.js +407 -215
- package/dist/credentials-index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +38 -2
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +70 -16
- package/dist/loader.js.map +1 -1
- package/integrations/__tests__/liveHarness.ts +84 -0
- package/integrations/__tests__/usageParity.ts +54 -0
- package/integrations/airtable/__tests__/get_handlers.test.ts +43 -31
- package/integrations/airtable/__tests__/usage_parity.test.ts +3 -29
- package/integrations/airtable/__tests__/write_and_admin_handlers.test.ts +20 -17
- package/integrations/airtable/credentials.json +21 -16
- package/integrations/github/__tests__/get_handlers.test.ts +101 -108
- package/integrations/github/__tests__/usage_parity.test.ts +15 -27
- package/integrations/github/__tests__/write_handlers.test.ts +223 -306
- package/integrations/github/credentials.json +40 -15
- package/integrations/github/credentials_hint_classic_pat.md +8 -0
- package/integrations/github/credentials_hint_fine_grained_pat.md +9 -0
- package/integrations/github/handlers/create_commit.js +2 -17
- package/integrations/github/manifest.json +2 -2
- package/integrations/google-calendar/__tests__/get_handlers.test.ts +21 -13
- package/integrations/google-calendar/__tests__/usage_parity.test.ts +3 -28
- package/integrations/google-calendar/__tests__/write_and_admin_handlers.test.ts +24 -17
- package/integrations/google-calendar/credentials.json +50 -29
- package/integrations/google-calendar/credentials_hint_oauth_token.md +8 -0
- package/integrations/google-calendar/credentials_hint_service_account.md +10 -0
- package/integrations/google-docs/__tests__/get_handlers.test.ts +87 -61
- package/integrations/google-docs/__tests__/usage_parity.test.ts +3 -28
- package/integrations/google-docs/__tests__/write_handlers.test.ts +251 -245
- package/integrations/google-docs/credentials.json +50 -29
- package/integrations/google-docs/credentials_hint_oauth_token.md +8 -0
- package/integrations/google-docs/credentials_hint_service_account.md +10 -0
- package/integrations/google-docs/handlers/insert_inline_image_after_first_match.js +1 -1
- package/integrations/google-docs/schemas/insert_inline_image_after_first_match.json +0 -1
- package/integrations/google-drive/__tests__/handlers.test.ts +102 -0
- package/integrations/google-drive/credentials.json +57 -0
- package/integrations/google-drive/credentials_hint_oauth_token.md +8 -0
- package/integrations/google-drive/credentials_hint_service_account.md +10 -0
- package/integrations/google-drive/handlers/create_file.js +15 -0
- package/integrations/google-drive/handlers/create_folder.js +15 -0
- package/integrations/google-drive/handlers/delete_file.js +14 -0
- package/integrations/google-drive/handlers/get_file.js +7 -0
- package/integrations/google-drive/handlers/move_file.js +12 -0
- package/integrations/google-drive/manifest.json +42 -0
- package/integrations/google-drive/schemas/create_file.json +12 -0
- package/integrations/google-drive/schemas/create_folder.json +11 -0
- package/integrations/google-drive/schemas/delete_file.json +10 -0
- package/integrations/google-drive/schemas/get_file.json +10 -0
- package/integrations/google-drive/schemas/move_file.json +12 -0
- package/integrations/google-sheet/__tests__/get_handlers.test.ts +48 -55
- package/integrations/google-sheet/__tests__/usage_parity.test.ts +3 -29
- package/integrations/google-sheet/__tests__/write_handlers.test.ts +65 -63
- package/integrations/google-sheet/credentials.json +50 -29
- package/integrations/google-sheet/credentials_hint_oauth_token.md +8 -0
- package/integrations/google-sheet/credentials_hint_service_account.md +10 -0
- package/integrations/google-slides/__tests__/get_handlers.test.ts +38 -36
- package/integrations/google-slides/__tests__/usage_parity.test.ts +3 -28
- package/integrations/google-slides/__tests__/write_handlers.test.ts +65 -59
- package/integrations/google-slides/credentials.json +50 -29
- package/integrations/google-slides/credentials_hint_oauth_token.md +8 -0
- package/integrations/google-slides/credentials_hint_service_account.md +10 -0
- package/integrations/notion/__tests__/get_handlers.test.ts +18 -15
- package/integrations/notion/__tests__/usage_parity.test.ts +3 -28
- package/integrations/notion/__tests__/write_and_admin_handlers.test.ts +56 -60
- package/integrations/notion/credentials.json +22 -17
- package/integrations/trello/__tests__/get_handlers.test.ts +58 -73
- package/integrations/trello/__tests__/usage_parity.test.ts +3 -28
- package/integrations/trello/__tests__/write_and_admin_handlers.test.ts +49 -67
- package/integrations/trello/credentials.json +26 -21
- package/integrations/trello/handlers/close_board.js +6 -0
- package/integrations/trello/handlers/create_board.js +11 -0
- package/integrations/trello/handlers/delete_board.js +13 -0
- package/integrations/trello/manifest.json +21 -0
- package/integrations/trello/schemas/close_board.json +10 -0
- package/integrations/trello/schemas/create_board.json +12 -0
- package/integrations/trello/schemas/delete_board.json +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
+
import { createCredentialStore, createIntegrationNode, createProxy, createToolbox, safeCleanup } from '../../__tests__/liveHarness.js'
|
|
3
|
+
|
|
4
|
+
// LIVE Google Drive tests -- runs once per available credential variant.
|
|
5
|
+
// Required env vars (at least one):
|
|
6
|
+
// - GOOGLE_SERVICE_ACCOUNT_JSON (service_account variant)
|
|
7
|
+
// - GOOGLE_TOKEN (oauth_token variant)
|
|
8
|
+
|
|
9
|
+
const env = process.env as Record<string, string | undefined>
|
|
10
|
+
|
|
11
|
+
interface VariantConfig {
|
|
12
|
+
key: string
|
|
13
|
+
credentials: () => Record<string, string>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const variants: VariantConfig[] = [
|
|
17
|
+
{
|
|
18
|
+
key: 'service_account',
|
|
19
|
+
credentials: () => ({ serviceAccountJson: env.GOOGLE_SERVICE_ACCOUNT_JSON || '', subject: env.GOOGLE_IMPERSONATE_SUBJECT || '' }),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
key: 'oauth_token',
|
|
23
|
+
credentials: () => ({ token: env.GOOGLE_TOKEN || '' }),
|
|
24
|
+
},
|
|
25
|
+
].filter(v => Object.values(v.credentials()).some(val => val.trim().length > 0))
|
|
26
|
+
|
|
27
|
+
const suiteOrSkip = variants.length > 0 ? describe : describe.skip
|
|
28
|
+
|
|
29
|
+
suiteOrSkip('google-drive handlers (live)', () => {
|
|
30
|
+
for (const variant of variants) {
|
|
31
|
+
describe(`variant: ${variant.key}`, () => {
|
|
32
|
+
const ctx: { folderId?: string, fileId?: string, destFolderId?: string } = {}
|
|
33
|
+
let drive: ReturnType<typeof createToolbox>
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
const credentialStore = createCredentialStore(async () => variant.credentials())
|
|
37
|
+
const proxy = createProxy(credentialStore)
|
|
38
|
+
drive = createToolbox(
|
|
39
|
+
'google-drive',
|
|
40
|
+
proxy,
|
|
41
|
+
createIntegrationNode('google-drive', { label: 'Google Drive', credentialId: 'google-drive-creds', credentialVariant: variant.key }),
|
|
42
|
+
variant.key,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const folder = await drive.write('create_folder')({ name: `CmdTest Drive ${Date.now()}` })
|
|
46
|
+
ctx.folderId = folder?.id
|
|
47
|
+
expect(ctx.folderId).toBeTruthy()
|
|
48
|
+
|
|
49
|
+
const destFolder = await drive.write('create_folder')({ name: `CmdTest Drive Dest ${Date.now()}` })
|
|
50
|
+
ctx.destFolderId = destFolder?.id
|
|
51
|
+
expect(ctx.destFolderId).toBeTruthy()
|
|
52
|
+
|
|
53
|
+
const file = await drive.write('create_file')({
|
|
54
|
+
name: `CmdTest File ${Date.now()}`,
|
|
55
|
+
mimeType: 'application/vnd.google-apps.document',
|
|
56
|
+
parentId: ctx.folderId,
|
|
57
|
+
})
|
|
58
|
+
ctx.fileId = file?.id
|
|
59
|
+
expect(ctx.fileId).toBeTruthy()
|
|
60
|
+
}, 60000)
|
|
61
|
+
|
|
62
|
+
afterAll(async () => {
|
|
63
|
+
await safeCleanup(async () => ctx.fileId ? drive.write('delete_file')({ fileId: ctx.fileId }) : Promise.resolve())
|
|
64
|
+
await safeCleanup(async () => ctx.folderId ? drive.write('delete_file')({ fileId: ctx.folderId }) : Promise.resolve())
|
|
65
|
+
await safeCleanup(async () => ctx.destFolderId ? drive.write('delete_file')({ fileId: ctx.destFolderId }) : Promise.resolve())
|
|
66
|
+
}, 60000)
|
|
67
|
+
|
|
68
|
+
it('get_file returns file metadata', async () => {
|
|
69
|
+
if (!ctx.fileId)
|
|
70
|
+
return expect(true).toBe(true)
|
|
71
|
+
const result = await drive.read('get_file')({ fileId: ctx.fileId })
|
|
72
|
+
expect(result?.id).toBe(ctx.fileId)
|
|
73
|
+
expect(typeof result?.name).toBe('string')
|
|
74
|
+
expect(typeof result?.mimeType).toBe('string')
|
|
75
|
+
}, 30000)
|
|
76
|
+
|
|
77
|
+
it('move_file moves the file to a different folder', async () => {
|
|
78
|
+
if (!ctx.fileId || !ctx.destFolderId || !ctx.folderId)
|
|
79
|
+
return expect(true).toBe(true)
|
|
80
|
+
const result = await drive.write('move_file')({
|
|
81
|
+
fileId: ctx.fileId,
|
|
82
|
+
addParents: ctx.destFolderId,
|
|
83
|
+
removeParents: ctx.folderId,
|
|
84
|
+
})
|
|
85
|
+
expect(result?.id).toBe(ctx.fileId)
|
|
86
|
+
const meta = await drive.read('get_file')({ fileId: ctx.fileId })
|
|
87
|
+
expect(meta?.parents).toContain(ctx.destFolderId)
|
|
88
|
+
}, 30000)
|
|
89
|
+
|
|
90
|
+
it('delete_file deletes a file permanently', async () => {
|
|
91
|
+
const tempFile = await drive.write('create_file')({
|
|
92
|
+
name: `CmdTest Temp ${Date.now()}`,
|
|
93
|
+
mimeType: 'application/vnd.google-apps.document',
|
|
94
|
+
})
|
|
95
|
+
const tempId = tempFile?.id
|
|
96
|
+
expect(tempId).toBeTruthy()
|
|
97
|
+
await drive.write('delete_file')({ fileId: tempId })
|
|
98
|
+
await expect(drive.read('get_file')({ fileId: tempId })).rejects.toThrow()
|
|
99
|
+
}, 30000)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"variants": {
|
|
3
|
+
"service_account": {
|
|
4
|
+
"label": "Service Account (recommended)",
|
|
5
|
+
"schema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"serviceAccountJson": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"title": "Service Account JSON",
|
|
11
|
+
"description": "Full service account key JSON (contents of the downloaded JSON file from Google Cloud)."
|
|
12
|
+
},
|
|
13
|
+
"subject": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"title": "Subject / impersonated user (optional)",
|
|
16
|
+
"description": "Optional user email to impersonate when using Google Workspace domain-wide delegation."
|
|
17
|
+
},
|
|
18
|
+
"scopes": {
|
|
19
|
+
"type": "array",
|
|
20
|
+
"title": "OAuth scopes (optional)",
|
|
21
|
+
"description": "Optional override for OAuth scopes. Defaults to drive.",
|
|
22
|
+
"items": { "type": "string" }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": ["serviceAccountJson"],
|
|
26
|
+
"additionalProperties": false
|
|
27
|
+
},
|
|
28
|
+
"injection": {
|
|
29
|
+
"headers": {
|
|
30
|
+
"Authorization": "Bearer {{token}}"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"preprocess": "google_service_account"
|
|
34
|
+
},
|
|
35
|
+
"oauth_token": {
|
|
36
|
+
"label": "OAuth Access Token (short-lived)",
|
|
37
|
+
"schema": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"properties": {
|
|
40
|
+
"token": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"title": "OAuth Access Token",
|
|
43
|
+
"description": "Short-lived Google OAuth access token with drive scope."
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"required": ["token"],
|
|
47
|
+
"additionalProperties": false
|
|
48
|
+
},
|
|
49
|
+
"injection": {
|
|
50
|
+
"headers": {
|
|
51
|
+
"Authorization": "Bearer {{token}}"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"default": "service_account"
|
|
57
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Obtain a short-lived Google OAuth access token:
|
|
2
|
+
|
|
3
|
+
1. Use the Google OAuth 2.0 Playground (`https://developers.google.com/oauthplayground/`) or your own OAuth flow
|
|
4
|
+
2. Select the scope: `https://www.googleapis.com/auth/drive`
|
|
5
|
+
3. Exchange the authorization code for an access token
|
|
6
|
+
4. Paste the access token here
|
|
7
|
+
|
|
8
|
+
Note: OAuth access tokens are short-lived (typically 1 hour). For long-running use, prefer the Service Account variant.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Set up a Google Cloud Service Account:
|
|
2
|
+
|
|
3
|
+
1. Open the [Google Cloud Console](https://console.cloud.google.com/)
|
|
4
|
+
2. Enable the **Google Drive API** for your project
|
|
5
|
+
3. Go to **IAM & Admin → Service Accounts** and create a new service account
|
|
6
|
+
4. Under **Keys**, click **Add Key → Create new key → JSON** and download the file
|
|
7
|
+
5. Paste the full contents of the JSON file here
|
|
8
|
+
6. Share the Drive folders/files you want to access with the service account's `client_email`
|
|
9
|
+
|
|
10
|
+
For Google Workspace users: optionally configure domain-wide delegation and set `subject` to the user's email.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = {
|
|
3
|
+
name: input.name,
|
|
4
|
+
mimeType: input.mimeType,
|
|
5
|
+
}
|
|
6
|
+
if (input.parentId)
|
|
7
|
+
body.parents = [input.parentId]
|
|
8
|
+
|
|
9
|
+
const res = await integration.fetch('/files', {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
body,
|
|
12
|
+
})
|
|
13
|
+
return await res.json()
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = {
|
|
3
|
+
name: input.name,
|
|
4
|
+
mimeType: 'application/vnd.google-apps.folder',
|
|
5
|
+
}
|
|
6
|
+
if (input.parentId)
|
|
7
|
+
body.parents = [input.parentId]
|
|
8
|
+
|
|
9
|
+
const res = await integration.fetch('/files', {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
body,
|
|
12
|
+
})
|
|
13
|
+
return await res.json()
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const res = await integration.fetch(`/files/${encodeURIComponent(input.fileId)}`, {
|
|
3
|
+
method: 'DELETE',
|
|
4
|
+
})
|
|
5
|
+
if (res.status === 204)
|
|
6
|
+
return { success: true, status: 204 }
|
|
7
|
+
try {
|
|
8
|
+
return await res.json()
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return { success: res.ok, status: res.status }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
params.set('addParents', input.addParents)
|
|
4
|
+
if (input.removeParents)
|
|
5
|
+
params.set('removeParents', input.removeParents)
|
|
6
|
+
|
|
7
|
+
const res = await integration.fetch(`/files/${encodeURIComponent(input.fileId)}?${params.toString()}`, {
|
|
8
|
+
method: 'PATCH',
|
|
9
|
+
})
|
|
10
|
+
return await res.json()
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "google-drive",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"tools": [
|
|
5
|
+
{
|
|
6
|
+
"name": "create_folder",
|
|
7
|
+
"description": "Create a folder in Google Drive.",
|
|
8
|
+
"inputSchema": "schemas/create_folder.json",
|
|
9
|
+
"handler": "handlers/create_folder.js",
|
|
10
|
+
"scope": "write"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "create_file",
|
|
14
|
+
"description": "Create a Drive file (including Google Docs/Sheets/Slides) optionally inside a parent folder.",
|
|
15
|
+
"inputSchema": "schemas/create_file.json",
|
|
16
|
+
"handler": "handlers/create_file.js",
|
|
17
|
+
"scope": "write"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "move_file",
|
|
21
|
+
"description": "Move a file to a different parent folder (add/remove parents).",
|
|
22
|
+
"inputSchema": "schemas/move_file.json",
|
|
23
|
+
"handler": "handlers/move_file.js",
|
|
24
|
+
"scope": "write"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "get_file",
|
|
28
|
+
"description": "Get a Drive file’s metadata by fileId.",
|
|
29
|
+
"inputSchema": "schemas/get_file.json",
|
|
30
|
+
"handler": "handlers/get_file.js",
|
|
31
|
+
"scope": "read"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "delete_file",
|
|
35
|
+
"description": "Permanently delete a Drive file or folder by fileId.",
|
|
36
|
+
"inputSchema": "schemas/delete_file.json",
|
|
37
|
+
"handler": "handlers/delete_file.js",
|
|
38
|
+
"scope": "write"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"required": ["name", "mimeType"],
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": { "type": "string" },
|
|
8
|
+
"mimeType": { "type": "string", "description": "Drive mimeType (e.g. application/vnd.google-apps.document)" },
|
|
9
|
+
"parentId": { "type": "string", "description": "Optional parent folder fileId" }
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"required": ["name"],
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": { "type": "string" },
|
|
8
|
+
"parentId": { "type": "string", "description": "Optional parent folder fileId" }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"required": ["fileId", "addParents"],
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"properties": {
|
|
7
|
+
"fileId": { "type": "string" },
|
|
8
|
+
"addParents": { "type": "string", "description": "Comma-separated parent IDs to add" },
|
|
9
|
+
"removeParents": { "type": "string", "description": "Comma-separated parent IDs to remove (optional)" }
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -1,128 +1,121 @@
|
|
|
1
|
-
import { beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
import { loadIntegrationTools } from '../../../src/integrations/dataLoader.js'
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
+
import { createCredentialStore, createIntegrationNode, createProxy, createToolbox, hasEnv, safeCleanup } from '../../__tests__/liveHarness.js'
|
|
4
3
|
|
|
5
|
-
// LIVE Google Sheets read tests using
|
|
4
|
+
// LIVE Google Sheets read tests using credentials
|
|
6
5
|
// Required env vars:
|
|
7
|
-
// -
|
|
8
|
-
// - COMMANDABLE_MANAGED_OAUTH_SECRET_KEY
|
|
9
|
-
// - GSHEETS_TEST_CONNECTION_ID (managed OAuth connection for provider 'google-sheet')
|
|
10
|
-
// - GSHEETS_TEST_SPREADSHEET_ID (an accessible spreadsheet ID)
|
|
6
|
+
// - Either GOOGLE_TOKEN, OR GOOGLE_SERVICE_ACCOUNT_JSON
|
|
11
7
|
|
|
12
|
-
const
|
|
13
|
-
const hasEnv = (...keys: string[]) => keys.every(k => !!env[k] && env[k].trim().length > 0)
|
|
14
|
-
const suite = hasEnv(
|
|
15
|
-
'COMMANDABLE_MANAGED_OAUTH_BASE_URL',
|
|
16
|
-
'COMMANDABLE_MANAGED_OAUTH_SECRET_KEY',
|
|
17
|
-
'GSHEETS_TEST_CONNECTION_ID',
|
|
18
|
-
)
|
|
8
|
+
const suite = (hasEnv('GOOGLE_TOKEN') || hasEnv('GOOGLE_SERVICE_ACCOUNT_JSON'))
|
|
19
9
|
? describe
|
|
20
10
|
: describe.skip
|
|
21
11
|
|
|
22
12
|
suite('google-sheet read handlers (live)', () => {
|
|
23
|
-
let
|
|
13
|
+
let sheets: ReturnType<typeof createToolbox>
|
|
14
|
+
let drive: ReturnType<typeof createToolbox>
|
|
24
15
|
let sheetTitle: string | undefined
|
|
16
|
+
let folderId: string | undefined
|
|
17
|
+
let spreadsheetId: string | undefined
|
|
25
18
|
|
|
26
19
|
beforeAll(async () => {
|
|
27
|
-
const
|
|
20
|
+
const env = process.env as Record<string, string | undefined>
|
|
21
|
+
const credentialStore = createCredentialStore(async () => ({
|
|
22
|
+
token: env.GOOGLE_TOKEN || '',
|
|
23
|
+
serviceAccountJson: env.GOOGLE_SERVICE_ACCOUNT_JSON || '',
|
|
24
|
+
subject: env.GOOGLE_IMPERSONATE_SUBJECT || '',
|
|
25
|
+
}))
|
|
26
|
+
const proxy = createProxy(credentialStore)
|
|
27
|
+
sheets = createToolbox('google-sheet', proxy, createIntegrationNode('google-sheet', { label: 'Google Sheets', credentialId: 'google-sheet-creds' }))
|
|
28
|
+
drive = createToolbox('google-drive', proxy, createIntegrationNode('google-drive', { label: 'Google Drive', credentialId: 'google-drive-creds' }))
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const integrationNode = { id: 'node-gsheets', type: 'google-sheet', label: 'Google Sheets', connectionId: GSHEETS_TEST_CONNECTION_ID } as any
|
|
30
|
+
// Create dedicated folder + spreadsheet for this run
|
|
31
|
+
const folder = await drive.write('create_folder')({ name: `CmdTest Sheets ${Date.now()}` })
|
|
32
|
+
folderId = folder?.id
|
|
33
|
+
expect(folderId).toBeTruthy()
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
|
|
35
|
+
const created = await drive.write('create_file')({
|
|
36
|
+
name: `CmdTest Sheet ${Date.now()}`,
|
|
37
|
+
mimeType: 'application/vnd.google-apps.spreadsheet',
|
|
38
|
+
parentId: folderId,
|
|
39
|
+
})
|
|
40
|
+
spreadsheetId = created?.id
|
|
41
|
+
expect(spreadsheetId).toBeTruthy()
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const build = new Function('integration', `return (${tool!.handlerCode});`)
|
|
43
|
-
return build(integration) as (input: any) => Promise<any>
|
|
43
|
+
try {
|
|
44
|
+
const get_spreadsheet = sheets.read('get_spreadsheet')
|
|
45
|
+
const meta = await get_spreadsheet({ spreadsheetId })
|
|
46
|
+
sheetTitle = meta?.sheets?.[0]?.properties?.title
|
|
44
47
|
}
|
|
48
|
+
catch {}
|
|
49
|
+
}, 60000)
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const meta = await get_spreadsheet({ spreadsheetId })
|
|
52
|
-
sheetTitle = meta?.sheets?.[0]?.properties?.title
|
|
53
|
-
}
|
|
54
|
-
catch {}
|
|
55
|
-
}
|
|
51
|
+
afterAll(async () => {
|
|
52
|
+
if (!folderId)
|
|
53
|
+
return
|
|
54
|
+
await safeCleanup(async () => spreadsheetId ? drive.write('delete_file')({ fileId: spreadsheetId }) : Promise.resolve())
|
|
55
|
+
await safeCleanup(async () => drive.write('delete_file')({ fileId: folderId }))
|
|
56
56
|
}, 60000)
|
|
57
57
|
|
|
58
58
|
it('get_spreadsheet returns metadata', async () => {
|
|
59
|
-
const spreadsheetId = env.GSHEETS_TEST_SPREADSHEET_ID
|
|
60
59
|
if (!spreadsheetId)
|
|
61
60
|
return expect(true).toBe(true)
|
|
62
|
-
const handler =
|
|
61
|
+
const handler = sheets.read('get_spreadsheet')
|
|
63
62
|
const result = await handler({ spreadsheetId })
|
|
64
63
|
expect(result?.spreadsheetId || result?.sheets).toBeTruthy()
|
|
65
64
|
}, 30000)
|
|
66
65
|
|
|
67
66
|
it('get_values returns a value range', async () => {
|
|
68
|
-
const spreadsheetId = env.GSHEETS_TEST_SPREADSHEET_ID
|
|
69
67
|
if (!spreadsheetId)
|
|
70
68
|
return expect(true).toBe(true)
|
|
71
|
-
const handler =
|
|
69
|
+
const handler = sheets.read('get_values')
|
|
72
70
|
const range = sheetTitle ? `${sheetTitle}!A1:B5` : 'A1:B5'
|
|
73
71
|
const result = await handler({ spreadsheetId, range })
|
|
74
72
|
expect(result?.range || result?.values).toBeTruthy()
|
|
75
73
|
}, 30000)
|
|
76
74
|
|
|
77
75
|
it('batch_get_values returns multiple ranges', async () => {
|
|
78
|
-
const spreadsheetId = env.GSHEETS_TEST_SPREADSHEET_ID
|
|
79
76
|
if (!spreadsheetId)
|
|
80
77
|
return expect(true).toBe(true)
|
|
81
|
-
const handler =
|
|
78
|
+
const handler = sheets.read('batch_get_values')
|
|
82
79
|
const aTitle = sheetTitle || 'Sheet1'
|
|
83
80
|
const result = await handler({ spreadsheetId, ranges: [`${aTitle}!A1:A3`, `${aTitle}!B1:B3`] })
|
|
84
81
|
expect(Array.isArray(result?.valueRanges)).toBe(true)
|
|
85
82
|
}, 30000)
|
|
86
83
|
|
|
87
84
|
it('get_spreadsheet_by_data_filter returns metadata', async () => {
|
|
88
|
-
const spreadsheetId = env.GSHEETS_TEST_SPREADSHEET_ID
|
|
89
85
|
if (!spreadsheetId)
|
|
90
86
|
return expect(true).toBe(true)
|
|
91
|
-
const handler =
|
|
87
|
+
const handler = sheets.read('get_spreadsheet_by_data_filter')
|
|
92
88
|
const aTitle = sheetTitle || 'Sheet1'
|
|
93
89
|
const result = await handler({ spreadsheetId, dataFilters: [{ a1Range: `${aTitle}!A1:A1` }] })
|
|
94
90
|
expect(result?.spreadsheetId || result?.sheets).toBeTruthy()
|
|
95
91
|
}, 30000)
|
|
96
92
|
|
|
97
93
|
it('get_values_by_data_filter returns values', async () => {
|
|
98
|
-
const spreadsheetId = env.GSHEETS_TEST_SPREADSHEET_ID
|
|
99
94
|
if (!spreadsheetId)
|
|
100
95
|
return expect(true).toBe(true)
|
|
101
|
-
const handler =
|
|
96
|
+
const handler = sheets.read('get_values_by_data_filter')
|
|
102
97
|
const aTitle = sheetTitle || 'Sheet1'
|
|
103
98
|
const result = await handler({ spreadsheetId, dataFilters: [{ a1Range: `${aTitle}!A1:B2` }] })
|
|
104
99
|
expect(Array.isArray(result?.valueRanges)).toBe(true)
|
|
105
100
|
}, 30000)
|
|
106
101
|
|
|
107
102
|
it('search_developer_metadata returns results or empty', async () => {
|
|
108
|
-
const spreadsheetId = env.GSHEETS_TEST_SPREADSHEET_ID
|
|
109
103
|
if (!spreadsheetId)
|
|
110
104
|
return expect(true).toBe(true)
|
|
111
|
-
const handler =
|
|
105
|
+
const handler = sheets.read('search_developer_metadata')
|
|
112
106
|
const result = await handler({ spreadsheetId, dataFilters: [{ developerMetadataLookup: { visibility: 'DOCUMENT' } }] })
|
|
113
107
|
expect(result !== undefined).toBe(true)
|
|
114
108
|
}, 30000)
|
|
115
109
|
|
|
116
110
|
it('get_developer_metadata retrieves by id when available', async () => {
|
|
117
|
-
const spreadsheetId = env.GSHEETS_TEST_SPREADSHEET_ID
|
|
118
111
|
if (!spreadsheetId)
|
|
119
112
|
return expect(true).toBe(true)
|
|
120
|
-
const search =
|
|
113
|
+
const search = sheets.read('search_developer_metadata')
|
|
121
114
|
const list = await search({ spreadsheetId, dataFilters: [{ developerMetadataLookup: { visibility: 'DOCUMENT' } }] })
|
|
122
115
|
const first = list?.matchedDeveloperMetadata?.[0]?.developerMetadata || list?.developerMetadata?.[0]
|
|
123
116
|
if (!first?.metadataId)
|
|
124
117
|
return expect(true).toBe(true)
|
|
125
|
-
const getdm =
|
|
118
|
+
const getdm = sheets.read('get_developer_metadata')
|
|
126
119
|
const got = await getdm({ spreadsheetId, metadataId: first.metadataId })
|
|
127
120
|
expect(got?.metadataId).toBe(first.metadataId)
|
|
128
121
|
}, 30000)
|
|
@@ -1,35 +1,9 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
1
|
import { describe, expect, it } from 'vitest'
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
function escapeRegExp(str: string): string {
|
|
8
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
9
|
-
}
|
|
2
|
+
import { getMissingToolUsages } from '../../__tests__/usageParity.js'
|
|
10
3
|
|
|
11
4
|
describe('google-sheet static usage parity', () => {
|
|
12
|
-
it('every manifest tool is referenced in tests
|
|
13
|
-
const
|
|
14
|
-
const toolNames = (manifest.tools as any[]).map(t => t.name)
|
|
15
|
-
|
|
16
|
-
const testsDir = fileURLToPath(new URL('.', import.meta.url))
|
|
17
|
-
expect(existsSync(testsDir)).toBe(true)
|
|
18
|
-
const testFiles = readdirSync(testsDir)
|
|
19
|
-
.filter(f => /\.test\.(t|j)s$/.test(f) && !f.includes('usage_parity.test'))
|
|
20
|
-
.map(f => resolve(testsDir, f))
|
|
21
|
-
|
|
22
|
-
const fileContents = testFiles.map(f => readFileSync(f, 'utf8'))
|
|
23
|
-
|
|
24
|
-
const missing: string[] = []
|
|
25
|
-
for (const name of toolNames) {
|
|
26
|
-
// Matches buildRead('x'), buildWrite('x'), buildAdmin('x'), buildHandler('x'), buildReadHandler('x'), buildWriteHandler('x')
|
|
27
|
-
const nameRe = new RegExp(`build(?:Read|Write|Admin)?(?:Handler)?\\(\\s*['\"\`]${escapeRegExp(name)}['\"\`]\\s*\\)`, 'm')
|
|
28
|
-
const found = fileContents.some(src => nameRe.test(src))
|
|
29
|
-
if (!found)
|
|
30
|
-
missing.push(name)
|
|
31
|
-
}
|
|
32
|
-
|
|
5
|
+
it('every manifest tool is referenced in tests', () => {
|
|
6
|
+
const missing = getMissingToolUsages({ integrationName: 'google-sheet', importMetaUrl: import.meta.url })
|
|
33
7
|
expect(missing, `Missing handler usages in tests: ${missing.join(', ')}`).toEqual([])
|
|
34
8
|
})
|
|
35
9
|
})
|