@commandable/integration-data 0.0.1 → 0.0.4
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 +18 -15
- 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 +219 -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/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 +248 -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-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 +47 -55
- package/integrations/google-sheet/__tests__/usage_parity.test.ts +3 -29
- package/integrations/google-sheet/__tests__/write_handlers.test.ts +64 -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 +37 -36
- package/integrations/google-slides/__tests__/usage_parity.test.ts +3 -28
- package/integrations/google-slides/__tests__/write_handlers.test.ts +65 -58
- 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
|
@@ -1,34 +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('notion 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
|
-
const nameRe = new RegExp(`build(?:Read|Write|Admin)?(?:Handler)?\\(\\s*['\"\`]${escapeRegExp(name)}['\"\`]\\s*\\)`, 'm')
|
|
27
|
-
const found = fileContents.some(src => nameRe.test(src))
|
|
28
|
-
if (!found)
|
|
29
|
-
missing.push(name)
|
|
30
|
-
}
|
|
31
|
-
|
|
5
|
+
it('every manifest tool is referenced in tests', () => {
|
|
6
|
+
const missing = getMissingToolUsages({ integrationName: 'notion', importMetaUrl: import.meta.url })
|
|
32
7
|
expect(missing, `Missing handler usages in tests: ${missing.join(', ')}`).toEqual([])
|
|
33
8
|
})
|
|
34
9
|
})
|
|
@@ -1,73 +1,69 @@
|
|
|
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
4
|
interface Ctx {
|
|
6
5
|
createdPageId?: string
|
|
7
|
-
|
|
6
|
+
testDatabaseId?: string
|
|
8
7
|
createdDatabaseId?: string
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
const env = process.env as Record<string, string>
|
|
12
|
-
const hasEnv = (...keys: string[]) => keys.every(k => !!env[k] && env[k].trim().length > 0)
|
|
13
10
|
const suite = hasEnv(
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'NOTION_TEST_CONNECTION_ID',
|
|
11
|
+
'NOTION_TOKEN',
|
|
12
|
+
'NOTION_TEST_PARENT_PAGE_ID',
|
|
17
13
|
)
|
|
18
14
|
? describe
|
|
19
15
|
: describe.skip
|
|
20
16
|
|
|
21
17
|
suite('notion write handlers (live)', () => {
|
|
22
18
|
const ctx: Ctx = {}
|
|
23
|
-
let
|
|
24
|
-
let buildRead: (name: string) => ((input: any) => Promise<any>)
|
|
19
|
+
let notion: ReturnType<typeof createToolbox>
|
|
25
20
|
|
|
26
21
|
beforeAll(async () => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const proxy =
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
const env = process.env as Record<string, string | undefined>
|
|
23
|
+
const credentialStore = createCredentialStore(async () => ({ token: env.NOTION_TOKEN || '' }))
|
|
24
|
+
const proxy = createProxy(credentialStore)
|
|
25
|
+
notion = createToolbox('notion', proxy, createIntegrationNode('notion', { label: 'Notion', credentialId: 'notion-creds' }))
|
|
26
|
+
|
|
27
|
+
// Create a dedicated database under a known parent page for this run
|
|
28
|
+
const create_database = notion.write('create_database')
|
|
29
|
+
const createdDb = await create_database({
|
|
30
|
+
parent: { page_id: env.NOTION_TEST_PARENT_PAGE_ID },
|
|
31
|
+
title: [{ type: 'text', text: { content: `CmdTest DB ${Date.now()}` } }],
|
|
32
|
+
properties: {
|
|
33
|
+
Name: { title: {} },
|
|
34
|
+
Status: { select: { options: [{ name: 'Open' }, { name: 'Done' }] } },
|
|
35
|
+
},
|
|
32
36
|
})
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const tools = loadIntegrationTools('notion')
|
|
36
|
-
expect(tools).toBeTruthy()
|
|
37
|
-
|
|
38
|
-
buildWrite = (name: string) => {
|
|
39
|
-
const tool = tools!.write.find(t => t.name === name)
|
|
40
|
-
expect(tool, `write tool ${name} exists`).toBeTruthy()
|
|
41
|
-
const integration = { fetch: (path: string, init?: RequestInit) => proxy.call(integrationNode, path, init) }
|
|
42
|
-
const build = new Function('integration', `return (${tool!.handlerCode});`)
|
|
43
|
-
return build(integration) as (input: any) => Promise<any>
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
buildRead = (name: string) => {
|
|
47
|
-
const tool = tools!.read.find(t => t.name === name)
|
|
48
|
-
expect(tool, `read tool ${name} exists`).toBeTruthy()
|
|
49
|
-
const integration = { fetch: (path: string, init?: RequestInit) => proxy.call(integrationNode, path, init) }
|
|
50
|
-
const build = new Function('integration', `return (${tool!.handlerCode});`)
|
|
51
|
-
return build(integration) as (input: any) => Promise<any>
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Try to find a database to create a page in by searching
|
|
55
|
-
try {
|
|
56
|
-
const search = buildRead('search')
|
|
57
|
-
const res = await search({ query: '', filter: { value: 'database', property: 'object' }, page_size: 1 })
|
|
58
|
-
ctx.parentDatabaseId = res?.results?.[0]?.id
|
|
59
|
-
}
|
|
60
|
-
catch {}
|
|
37
|
+
ctx.testDatabaseId = createdDb?.id
|
|
38
|
+
expect(ctx.testDatabaseId).toBeTruthy()
|
|
61
39
|
}, 60000)
|
|
62
40
|
|
|
41
|
+
afterAll(async () => {
|
|
42
|
+
// Archive any created page + any created databases from this run
|
|
43
|
+
await safeCleanup(async () => {
|
|
44
|
+
if (!ctx.createdPageId)
|
|
45
|
+
return
|
|
46
|
+
const update_page_properties = notion.write('update_page_properties')
|
|
47
|
+
await update_page_properties({ page_id: ctx.createdPageId, properties: {}, archived: true })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
await safeCleanup(async () => {
|
|
51
|
+
const update_database = notion.write('update_database')
|
|
52
|
+
if (ctx.createdDatabaseId)
|
|
53
|
+
await update_database({ database_id: ctx.createdDatabaseId, archived: true })
|
|
54
|
+
if (ctx.testDatabaseId)
|
|
55
|
+
await update_database({ database_id: ctx.testDatabaseId, archived: true })
|
|
56
|
+
})
|
|
57
|
+
}, 60_000)
|
|
58
|
+
|
|
63
59
|
it('create_page -> retrieve_page -> update_page_properties', async () => {
|
|
64
|
-
if (!ctx.
|
|
60
|
+
if (!ctx.testDatabaseId)
|
|
65
61
|
return expect(true).toBe(true)
|
|
66
62
|
|
|
67
|
-
const create_page =
|
|
63
|
+
const create_page = notion.write('create_page')
|
|
68
64
|
const titleText = `CmdTest ${Date.now()}`
|
|
69
65
|
const created = await create_page({
|
|
70
|
-
parent: { database_id: ctx.
|
|
66
|
+
parent: { database_id: ctx.testDatabaseId },
|
|
71
67
|
properties: {
|
|
72
68
|
Name: { title: [{ type: 'text', text: { content: titleText } }] },
|
|
73
69
|
},
|
|
@@ -76,11 +72,11 @@ suite('notion write handlers (live)', () => {
|
|
|
76
72
|
expect(pageId).toBeTruthy()
|
|
77
73
|
ctx.createdPageId = pageId
|
|
78
74
|
|
|
79
|
-
const retrieve_page =
|
|
75
|
+
const retrieve_page = notion.read('retrieve_page')
|
|
80
76
|
const got = await retrieve_page({ page_id: pageId })
|
|
81
77
|
expect(got?.id).toBe(pageId)
|
|
82
78
|
|
|
83
|
-
const update_page_properties =
|
|
79
|
+
const update_page_properties = notion.write('update_page_properties')
|
|
84
80
|
const newTitle = `${titleText} Updated`
|
|
85
81
|
const updated = await update_page_properties({ page_id: pageId, properties: { Name: { title: [{ type: 'text', text: { content: newTitle } }] } } })
|
|
86
82
|
expect(updated?.id).toBe(pageId)
|
|
@@ -94,7 +90,7 @@ suite('notion write handlers (live)', () => {
|
|
|
94
90
|
it('append_block_children on created page', async () => {
|
|
95
91
|
if (!ctx.createdPageId)
|
|
96
92
|
return expect(true).toBe(true)
|
|
97
|
-
const append_block_children =
|
|
93
|
+
const append_block_children = notion.write('append_block_children')
|
|
98
94
|
const contentText = 'Hello from test'
|
|
99
95
|
const res = await append_block_children({
|
|
100
96
|
block_id: ctx.createdPageId,
|
|
@@ -105,7 +101,7 @@ suite('notion write handlers (live)', () => {
|
|
|
105
101
|
expect(res).toBeTruthy()
|
|
106
102
|
|
|
107
103
|
// Verify via list_block_children
|
|
108
|
-
const list_block_children =
|
|
104
|
+
const list_block_children = notion.read('list_block_children')
|
|
109
105
|
const listed = await list_block_children({ block_id: ctx.createdPageId })
|
|
110
106
|
const found = (listed?.results || listed || []).some((b: any) => b?.paragraph?.rich_text?.some((t: any) => (t?.plain_text || t?.text?.content) === contentText))
|
|
111
107
|
expect(found).toBe(true)
|
|
@@ -114,13 +110,13 @@ suite('notion write handlers (live)', () => {
|
|
|
114
110
|
it('create_comment on created page', async () => {
|
|
115
111
|
if (!ctx.createdPageId)
|
|
116
112
|
return expect(true).toBe(true)
|
|
117
|
-
const create_comment =
|
|
113
|
+
const create_comment = notion.write('create_comment')
|
|
118
114
|
const commentText = 'Test comment'
|
|
119
115
|
const res = await create_comment({ parent: { block_id: ctx.createdPageId }, rich_text: [{ type: 'text', text: { content: commentText } }] })
|
|
120
116
|
expect(res?.object === 'comment' || res?.results).toBeTruthy()
|
|
121
117
|
|
|
122
118
|
// Verify via list_comments
|
|
123
|
-
const list_comments =
|
|
119
|
+
const list_comments = notion.read('list_comments')
|
|
124
120
|
const comments = await list_comments({ block_id: ctx.createdPageId })
|
|
125
121
|
const hasComment = (comments?.results || comments || []).some((c: any) => c?.rich_text?.some((t: any) => (t?.plain_text || t?.text?.content)?.includes(commentText)))
|
|
126
122
|
expect(hasComment).toBe(true)
|
|
@@ -130,7 +126,7 @@ suite('notion write handlers (live)', () => {
|
|
|
130
126
|
if (!ctx.createdPageId)
|
|
131
127
|
return expect(true).toBe(true)
|
|
132
128
|
// Create a specific block to edit
|
|
133
|
-
const append_block_children =
|
|
129
|
+
const append_block_children = notion.write('append_block_children')
|
|
134
130
|
const appended = await append_block_children({
|
|
135
131
|
block_id: ctx.createdPageId,
|
|
136
132
|
children: [
|
|
@@ -141,18 +137,18 @@ suite('notion write handlers (live)', () => {
|
|
|
141
137
|
if (!blockId)
|
|
142
138
|
return expect(true).toBe(true)
|
|
143
139
|
|
|
144
|
-
const update_block =
|
|
140
|
+
const update_block = notion.write('update_block')
|
|
145
141
|
const editedText = 'Edited'
|
|
146
142
|
const updated = await update_block({ block_id: blockId, body: { paragraph: { rich_text: [{ type: 'text', text: { content: editedText } }] } } })
|
|
147
143
|
expect(updated?.id).toBe(blockId)
|
|
148
144
|
|
|
149
145
|
// Verify via retrieve_block
|
|
150
|
-
const retrieve_block =
|
|
146
|
+
const retrieve_block = notion.read('retrieve_block')
|
|
151
147
|
const gotBlock = await retrieve_block({ block_id: blockId })
|
|
152
148
|
const gotEdited = gotBlock?.paragraph?.rich_text?.some((t: any) => (t?.plain_text || t?.text?.content) === editedText)
|
|
153
149
|
expect(gotEdited).toBe(true)
|
|
154
150
|
|
|
155
|
-
const delete_block =
|
|
151
|
+
const delete_block = notion.write('delete_block')
|
|
156
152
|
const del = await delete_block({ block_id: blockId })
|
|
157
153
|
expect(del?.archived === true || del?.id === blockId).toBe(true)
|
|
158
154
|
|
|
@@ -164,7 +160,7 @@ suite('notion write handlers (live)', () => {
|
|
|
164
160
|
it('create_database then update_database under created page (optional)', async () => {
|
|
165
161
|
if (!ctx.createdPageId)
|
|
166
162
|
return expect(true).toBe(true)
|
|
167
|
-
const create_database =
|
|
163
|
+
const create_database = notion.write('create_database')
|
|
168
164
|
const created = await create_database({
|
|
169
165
|
parent: { page_id: ctx.createdPageId },
|
|
170
166
|
title: [{ type: 'text', text: { content: `CmdDB ${Date.now()}` } }],
|
|
@@ -177,12 +173,12 @@ suite('notion write handlers (live)', () => {
|
|
|
177
173
|
ctx.createdDatabaseId = dbId
|
|
178
174
|
expect(dbId).toBeTruthy()
|
|
179
175
|
|
|
180
|
-
const update_database =
|
|
176
|
+
const update_database = notion.write('update_database')
|
|
181
177
|
const updated = await update_database({ database_id: dbId, title: [{ type: 'text', text: { content: 'CmdDB Updated' } }] })
|
|
182
178
|
expect(updated?.id).toBe(dbId)
|
|
183
179
|
|
|
184
180
|
// Verify via retrieve_database
|
|
185
|
-
const retrieve_database =
|
|
181
|
+
const retrieve_database = notion.read('retrieve_database')
|
|
186
182
|
const gotDb = await retrieve_database({ database_id: dbId })
|
|
187
183
|
const titleText = gotDb?.title?.[0]?.plain_text || gotDb?.title?.[0]?.text?.content
|
|
188
184
|
expect(titleText?.includes('Updated')).toBe(true)
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
"type": "
|
|
7
|
-
"
|
|
8
|
-
|
|
2
|
+
"variants": {
|
|
3
|
+
"internal_integration": {
|
|
4
|
+
"label": "Internal Integration Token",
|
|
5
|
+
"schema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"token": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"title": "Internal Integration Token",
|
|
11
|
+
"description": "Notion internal integration token (starts with \"secret_\")."
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"required": ["token"],
|
|
15
|
+
"additionalProperties": false
|
|
16
|
+
},
|
|
17
|
+
"injection": {
|
|
18
|
+
"headers": {
|
|
19
|
+
"Authorization": "Bearer {{token}}",
|
|
20
|
+
"Notion-Version": "2022-06-28"
|
|
21
|
+
}
|
|
9
22
|
}
|
|
10
|
-
},
|
|
11
|
-
"required": ["token"],
|
|
12
|
-
"additionalProperties": false
|
|
13
|
-
},
|
|
14
|
-
"injection": {
|
|
15
|
-
"headers": {
|
|
16
|
-
"Authorization": "Bearer {{token}}",
|
|
17
|
-
"Notion-Version": "2022-06-28"
|
|
18
23
|
}
|
|
19
|
-
}
|
|
24
|
+
},
|
|
25
|
+
"default": "internal_integration"
|
|
20
26
|
}
|
|
21
|
-
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
import { loadIntegrationTools } from '../../../../server/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
|
-
// This is a LIVE integration test suite that hits Trello using
|
|
4
|
+
// This is a LIVE integration test suite that hits Trello using credentials.
|
|
6
5
|
// Required env vars:
|
|
7
|
-
// - COMMANDABLE_MANAGED_OAUTH_BASE_URL
|
|
8
|
-
// - COMMANDABLE_MANAGED_OAUTH_SECRET_KEY
|
|
9
6
|
// - TRELLO_API_KEY
|
|
10
|
-
// -
|
|
7
|
+
// - TRELLO_API_TOKEN
|
|
11
8
|
|
|
12
9
|
interface Ids {
|
|
13
10
|
boardId?: string
|
|
@@ -16,68 +13,56 @@ interface Ids {
|
|
|
16
13
|
orgId?: string
|
|
17
14
|
}
|
|
18
15
|
|
|
19
|
-
const env = process.env as Record<string, string>
|
|
20
|
-
const hasEnv = (...keys: string[]) => keys.every(k => !!env[k] && env[k].trim().length > 0)
|
|
21
16
|
const suite = hasEnv(
|
|
22
|
-
'COMMANDABLE_MANAGED_OAUTH_BASE_URL',
|
|
23
|
-
'COMMANDABLE_MANAGED_OAUTH_SECRET_KEY',
|
|
24
17
|
'TRELLO_API_KEY',
|
|
25
|
-
'
|
|
18
|
+
'TRELLO_API_TOKEN',
|
|
26
19
|
)
|
|
27
20
|
? describe
|
|
28
21
|
: describe.skip
|
|
29
22
|
|
|
30
23
|
suite('trello read handlers (live)', () => {
|
|
31
24
|
const ids: Ids = {}
|
|
25
|
+
let boardId: string | undefined
|
|
26
|
+
let listId: string | undefined
|
|
32
27
|
|
|
33
|
-
let
|
|
28
|
+
let trello: ReturnType<typeof createToolbox>
|
|
34
29
|
|
|
35
30
|
beforeAll(async () => {
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const proxy =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
expect(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Discover a board, list, card, and org for subsequent tests
|
|
59
|
-
const get_member_boards = buildHandler('get_member_boards')
|
|
60
|
-
const boards = await get_member_boards({})
|
|
61
|
-
expect(Array.isArray(boards)).toBe(true)
|
|
62
|
-
ids.boardId = boards[0]?.id
|
|
63
|
-
|
|
64
|
-
if (ids.boardId) {
|
|
65
|
-
const get_board_lists = buildHandler('get_board_lists')
|
|
66
|
-
const lists = await get_board_lists({ boardId: ids.boardId })
|
|
67
|
-
ids.listId = lists[0]?.id
|
|
68
|
-
|
|
69
|
-
const get_board_cards = buildHandler('get_board_cards')
|
|
70
|
-
const cards = await get_board_cards({ boardId: ids.boardId })
|
|
71
|
-
ids.cardId = cards[0]?.id
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const get_member_organizations = buildHandler('get_member_organizations')
|
|
31
|
+
const env = process.env as Record<string, string | undefined>
|
|
32
|
+
const credentialStore = createCredentialStore(async () => ({ apiKey: env.TRELLO_API_KEY || '', apiToken: env.TRELLO_API_TOKEN || '' }))
|
|
33
|
+
const proxy = createProxy(credentialStore)
|
|
34
|
+
const node = createIntegrationNode('trello', { label: 'Trello', credentialId: 'trello-creds' })
|
|
35
|
+
trello = createToolbox('trello', proxy, node)
|
|
36
|
+
|
|
37
|
+
// Create an isolated board/list/card for this run so tests don’t touch random user boards.
|
|
38
|
+
const board = await trello.write('create_board')({ name: `CmdTest Trello Read ${Date.now()}`, defaultLists: false })
|
|
39
|
+
boardId = board?.id
|
|
40
|
+
ids.boardId = boardId
|
|
41
|
+
expect(ids.boardId).toBeTruthy()
|
|
42
|
+
|
|
43
|
+
const list = await trello.write('create_list')({ idBoard: boardId, name: 'CmdTest List' })
|
|
44
|
+
listId = list?.id
|
|
45
|
+
ids.listId = listId
|
|
46
|
+
expect(ids.listId).toBeTruthy()
|
|
47
|
+
|
|
48
|
+
const card = await trello.write('create_card')({ idList: listId, name: `CmdTest Card ${Date.now()}` })
|
|
49
|
+
ids.cardId = card?.id
|
|
50
|
+
expect(ids.cardId).toBeTruthy()
|
|
51
|
+
|
|
52
|
+
const get_member_organizations = trello.read('get_member_organizations')
|
|
75
53
|
const orgs = await get_member_organizations({})
|
|
76
54
|
ids.orgId = orgs[0]?.id
|
|
77
55
|
}, 60000)
|
|
78
56
|
|
|
57
|
+
afterAll(async () => {
|
|
58
|
+
if (!boardId)
|
|
59
|
+
return
|
|
60
|
+
await safeCleanup(async () => trello.write('close_board')({ boardId }))
|
|
61
|
+
await safeCleanup(async () => trello.write('delete_board')({ boardId }))
|
|
62
|
+
}, 60_000)
|
|
63
|
+
|
|
79
64
|
it('get_member returns current member', async () => {
|
|
80
|
-
const handler =
|
|
65
|
+
const handler = trello.read('get_member')
|
|
81
66
|
const result = await handler({})
|
|
82
67
|
expect(result).toBeTruthy()
|
|
83
68
|
expect(typeof result.id).toBe('string')
|
|
@@ -85,13 +70,13 @@ suite('trello read handlers (live)', () => {
|
|
|
85
70
|
}, 30000)
|
|
86
71
|
|
|
87
72
|
it('get_member_boards returns an array', async () => {
|
|
88
|
-
const handler =
|
|
73
|
+
const handler = trello.read('get_member_boards')
|
|
89
74
|
const result = await handler({})
|
|
90
75
|
expect(Array.isArray(result)).toBe(true)
|
|
91
76
|
}, 30000)
|
|
92
77
|
|
|
93
78
|
it('get_member_organizations returns an array', async () => {
|
|
94
|
-
const handler =
|
|
79
|
+
const handler = trello.read('get_member_organizations')
|
|
95
80
|
const result = await handler({})
|
|
96
81
|
expect(Array.isArray(result)).toBe(true)
|
|
97
82
|
}, 30000)
|
|
@@ -99,7 +84,7 @@ suite('trello read handlers (live)', () => {
|
|
|
99
84
|
it('get_board works with boardId', async () => {
|
|
100
85
|
if (!ids.boardId)
|
|
101
86
|
return expect(true).toBe(true)
|
|
102
|
-
const handler =
|
|
87
|
+
const handler = trello.read('get_board')
|
|
103
88
|
const result = await handler({ boardId: ids.boardId })
|
|
104
89
|
expect(result?.id).toBe(ids.boardId)
|
|
105
90
|
}, 30000)
|
|
@@ -107,7 +92,7 @@ suite('trello read handlers (live)', () => {
|
|
|
107
92
|
it('get_board_lists returns lists', async () => {
|
|
108
93
|
if (!ids.boardId)
|
|
109
94
|
return expect(true).toBe(true)
|
|
110
|
-
const handler =
|
|
95
|
+
const handler = trello.read('get_board_lists')
|
|
111
96
|
const result = await handler({ boardId: ids.boardId })
|
|
112
97
|
expect(Array.isArray(result)).toBe(true)
|
|
113
98
|
}, 30000)
|
|
@@ -115,7 +100,7 @@ suite('trello read handlers (live)', () => {
|
|
|
115
100
|
it('get_board_cards returns cards', async () => {
|
|
116
101
|
if (!ids.boardId)
|
|
117
102
|
return expect(true).toBe(true)
|
|
118
|
-
const handler =
|
|
103
|
+
const handler = trello.read('get_board_cards')
|
|
119
104
|
const result = await handler({ boardId: ids.boardId })
|
|
120
105
|
expect(Array.isArray(result)).toBe(true)
|
|
121
106
|
}, 30000)
|
|
@@ -123,7 +108,7 @@ suite('trello read handlers (live)', () => {
|
|
|
123
108
|
it('get_board_members returns members', async () => {
|
|
124
109
|
if (!ids.boardId)
|
|
125
110
|
return expect(true).toBe(true)
|
|
126
|
-
const handler =
|
|
111
|
+
const handler = trello.read('get_board_members')
|
|
127
112
|
const result = await handler({ boardId: ids.boardId })
|
|
128
113
|
expect(Array.isArray(result)).toBe(true)
|
|
129
114
|
}, 30000)
|
|
@@ -131,7 +116,7 @@ suite('trello read handlers (live)', () => {
|
|
|
131
116
|
it('get_board_labels returns labels', async () => {
|
|
132
117
|
if (!ids.boardId)
|
|
133
118
|
return expect(true).toBe(true)
|
|
134
|
-
const handler =
|
|
119
|
+
const handler = trello.read('get_board_labels')
|
|
135
120
|
const result = await handler({ boardId: ids.boardId })
|
|
136
121
|
expect(Array.isArray(result)).toBe(true)
|
|
137
122
|
}, 30000)
|
|
@@ -139,7 +124,7 @@ suite('trello read handlers (live)', () => {
|
|
|
139
124
|
it('get_board_custom_fields returns custom fields', async () => {
|
|
140
125
|
if (!ids.boardId)
|
|
141
126
|
return expect(true).toBe(true)
|
|
142
|
-
const handler =
|
|
127
|
+
const handler = trello.read('get_board_custom_fields')
|
|
143
128
|
const result = await handler({ boardId: ids.boardId })
|
|
144
129
|
expect(Array.isArray(result)).toBe(true)
|
|
145
130
|
}, 30000)
|
|
@@ -147,7 +132,7 @@ suite('trello read handlers (live)', () => {
|
|
|
147
132
|
it('get_board_memberships returns memberships', async () => {
|
|
148
133
|
if (!ids.boardId)
|
|
149
134
|
return expect(true).toBe(true)
|
|
150
|
-
const handler =
|
|
135
|
+
const handler = trello.read('get_board_memberships')
|
|
151
136
|
const result = await handler({ boardId: ids.boardId })
|
|
152
137
|
expect(Array.isArray(result)).toBe(true)
|
|
153
138
|
}, 30000)
|
|
@@ -155,7 +140,7 @@ suite('trello read handlers (live)', () => {
|
|
|
155
140
|
it('get_list returns a list', async () => {
|
|
156
141
|
if (!ids.listId)
|
|
157
142
|
return expect(true).toBe(true)
|
|
158
|
-
const handler =
|
|
143
|
+
const handler = trello.read('get_list')
|
|
159
144
|
const result = await handler({ listId: ids.listId })
|
|
160
145
|
expect(result?.id).toBe(ids.listId)
|
|
161
146
|
}, 30000)
|
|
@@ -163,7 +148,7 @@ suite('trello read handlers (live)', () => {
|
|
|
163
148
|
it('get_list_cards returns cards in a list', async () => {
|
|
164
149
|
if (!ids.listId)
|
|
165
150
|
return expect(true).toBe(true)
|
|
166
|
-
const handler =
|
|
151
|
+
const handler = trello.read('get_list_cards')
|
|
167
152
|
const result = await handler({ listId: ids.listId })
|
|
168
153
|
expect(Array.isArray(result)).toBe(true)
|
|
169
154
|
}, 30000)
|
|
@@ -171,7 +156,7 @@ suite('trello read handlers (live)', () => {
|
|
|
171
156
|
it('get_card returns a card', async () => {
|
|
172
157
|
if (!ids.cardId)
|
|
173
158
|
return expect(true).toBe(true)
|
|
174
|
-
const handler =
|
|
159
|
+
const handler = trello.read('get_card')
|
|
175
160
|
const result = await handler({ cardId: ids.cardId })
|
|
176
161
|
expect(result?.id).toBe(ids.cardId)
|
|
177
162
|
}, 30000)
|
|
@@ -179,7 +164,7 @@ suite('trello read handlers (live)', () => {
|
|
|
179
164
|
it('get_card_members returns members for a card', async () => {
|
|
180
165
|
if (!ids.cardId)
|
|
181
166
|
return expect(true).toBe(true)
|
|
182
|
-
const handler =
|
|
167
|
+
const handler = trello.read('get_card_members')
|
|
183
168
|
const result = await handler({ cardId: ids.cardId })
|
|
184
169
|
expect(Array.isArray(result)).toBe(true)
|
|
185
170
|
}, 30000)
|
|
@@ -187,7 +172,7 @@ suite('trello read handlers (live)', () => {
|
|
|
187
172
|
it('get_card_attachments returns attachments for a card', async () => {
|
|
188
173
|
if (!ids.cardId)
|
|
189
174
|
return expect(true).toBe(true)
|
|
190
|
-
const handler =
|
|
175
|
+
const handler = trello.read('get_card_attachments')
|
|
191
176
|
const result = await handler({ cardId: ids.cardId })
|
|
192
177
|
expect(Array.isArray(result)).toBe(true)
|
|
193
178
|
}, 30000)
|
|
@@ -195,7 +180,7 @@ suite('trello read handlers (live)', () => {
|
|
|
195
180
|
it('get_card_actions returns actions for a card', async () => {
|
|
196
181
|
if (!ids.cardId)
|
|
197
182
|
return expect(true).toBe(true)
|
|
198
|
-
const handler =
|
|
183
|
+
const handler = trello.read('get_card_actions')
|
|
199
184
|
const result = await handler({ cardId: ids.cardId })
|
|
200
185
|
expect(Array.isArray(result)).toBe(true)
|
|
201
186
|
}, 30000)
|
|
@@ -203,7 +188,7 @@ suite('trello read handlers (live)', () => {
|
|
|
203
188
|
it('get_card_checklists returns checklists for a card', async () => {
|
|
204
189
|
if (!ids.cardId)
|
|
205
190
|
return expect(true).toBe(true)
|
|
206
|
-
const handler =
|
|
191
|
+
const handler = trello.read('get_card_checklists')
|
|
207
192
|
const result = await handler({ cardId: ids.cardId })
|
|
208
193
|
expect(Array.isArray(result)).toBe(true)
|
|
209
194
|
}, 30000)
|
|
@@ -211,7 +196,7 @@ suite('trello read handlers (live)', () => {
|
|
|
211
196
|
it('get_card_custom_field_items returns custom field items', async () => {
|
|
212
197
|
if (!ids.cardId)
|
|
213
198
|
return expect(true).toBe(true)
|
|
214
|
-
const handler =
|
|
199
|
+
const handler = trello.read('get_card_custom_field_items')
|
|
215
200
|
const result = await handler({ cardId: ids.cardId })
|
|
216
201
|
expect(Array.isArray(result)).toBe(true)
|
|
217
202
|
}, 30000)
|
|
@@ -219,7 +204,7 @@ suite('trello read handlers (live)', () => {
|
|
|
219
204
|
it('get_organization returns an organization', async () => {
|
|
220
205
|
if (!ids.orgId)
|
|
221
206
|
return expect(true).toBe(true)
|
|
222
|
-
const handler =
|
|
207
|
+
const handler = trello.read('get_organization')
|
|
223
208
|
const result = await handler({ orgId: ids.orgId })
|
|
224
209
|
expect(result?.id).toBe(ids.orgId)
|
|
225
210
|
}, 30000)
|
|
@@ -227,13 +212,13 @@ suite('trello read handlers (live)', () => {
|
|
|
227
212
|
it('get_organization_boards returns boards in an org', async () => {
|
|
228
213
|
if (!ids.orgId)
|
|
229
214
|
return expect(true).toBe(true)
|
|
230
|
-
const handler =
|
|
215
|
+
const handler = trello.read('get_organization_boards')
|
|
231
216
|
const result = await handler({ orgId: ids.orgId })
|
|
232
217
|
expect(Array.isArray(result)).toBe(true)
|
|
233
218
|
}, 30000)
|
|
234
219
|
|
|
235
220
|
it('search returns results for a generic query', async () => {
|
|
236
|
-
const handler =
|
|
221
|
+
const handler = trello.read('search')
|
|
237
222
|
const result = await handler({ query: 'test' })
|
|
238
223
|
expect(result).toBeTruthy()
|
|
239
224
|
}, 30000)
|
|
@@ -1,34 +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('trello 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
|
-
const nameRe = new RegExp(`build(?:Read|Write|Admin)?(?:Handler)?\\(\\s*['\"\`]${escapeRegExp(name)}['\"\`]\\s*\\)`, 'm')
|
|
27
|
-
const found = fileContents.some(src => nameRe.test(src))
|
|
28
|
-
if (!found)
|
|
29
|
-
missing.push(name)
|
|
30
|
-
}
|
|
31
|
-
|
|
5
|
+
it('every manifest tool is referenced in tests', () => {
|
|
6
|
+
const missing = getMissingToolUsages({ integrationName: 'trello', importMetaUrl: import.meta.url })
|
|
32
7
|
expect(missing, `Missing handler usages in tests: ${missing.join(', ')}`).toEqual([])
|
|
33
8
|
})
|
|
34
9
|
})
|