@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.
Files changed (79) hide show
  1. package/dist/credentials-index.d.ts +4 -21
  2. package/dist/credentials-index.d.ts.map +1 -1
  3. package/dist/credentials-index.js +407 -215
  4. package/dist/credentials-index.js.map +1 -1
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/loader.d.ts +38 -2
  10. package/dist/loader.d.ts.map +1 -1
  11. package/dist/loader.js +70 -16
  12. package/dist/loader.js.map +1 -1
  13. package/integrations/__tests__/liveHarness.ts +84 -0
  14. package/integrations/__tests__/usageParity.ts +54 -0
  15. package/integrations/airtable/__tests__/get_handlers.test.ts +18 -15
  16. package/integrations/airtable/__tests__/usage_parity.test.ts +3 -29
  17. package/integrations/airtable/__tests__/write_and_admin_handlers.test.ts +20 -17
  18. package/integrations/airtable/credentials.json +21 -16
  19. package/integrations/github/__tests__/get_handlers.test.ts +101 -108
  20. package/integrations/github/__tests__/usage_parity.test.ts +15 -27
  21. package/integrations/github/__tests__/write_handlers.test.ts +219 -306
  22. package/integrations/github/credentials.json +40 -15
  23. package/integrations/github/credentials_hint_classic_pat.md +8 -0
  24. package/integrations/github/credentials_hint_fine_grained_pat.md +9 -0
  25. package/integrations/github/manifest.json +2 -2
  26. package/integrations/google-calendar/__tests__/get_handlers.test.ts +21 -13
  27. package/integrations/google-calendar/__tests__/usage_parity.test.ts +3 -28
  28. package/integrations/google-calendar/__tests__/write_and_admin_handlers.test.ts +24 -17
  29. package/integrations/google-calendar/credentials.json +50 -29
  30. package/integrations/google-calendar/credentials_hint_oauth_token.md +8 -0
  31. package/integrations/google-calendar/credentials_hint_service_account.md +10 -0
  32. package/integrations/google-docs/__tests__/get_handlers.test.ts +87 -61
  33. package/integrations/google-docs/__tests__/usage_parity.test.ts +3 -28
  34. package/integrations/google-docs/__tests__/write_handlers.test.ts +248 -245
  35. package/integrations/google-docs/credentials.json +50 -29
  36. package/integrations/google-docs/credentials_hint_oauth_token.md +8 -0
  37. package/integrations/google-docs/credentials_hint_service_account.md +10 -0
  38. package/integrations/google-drive/credentials.json +57 -0
  39. package/integrations/google-drive/credentials_hint_oauth_token.md +8 -0
  40. package/integrations/google-drive/credentials_hint_service_account.md +10 -0
  41. package/integrations/google-drive/handlers/create_file.js +15 -0
  42. package/integrations/google-drive/handlers/create_folder.js +15 -0
  43. package/integrations/google-drive/handlers/delete_file.js +14 -0
  44. package/integrations/google-drive/handlers/get_file.js +7 -0
  45. package/integrations/google-drive/handlers/move_file.js +12 -0
  46. package/integrations/google-drive/manifest.json +42 -0
  47. package/integrations/google-drive/schemas/create_file.json +12 -0
  48. package/integrations/google-drive/schemas/create_folder.json +11 -0
  49. package/integrations/google-drive/schemas/delete_file.json +10 -0
  50. package/integrations/google-drive/schemas/get_file.json +10 -0
  51. package/integrations/google-drive/schemas/move_file.json +12 -0
  52. package/integrations/google-sheet/__tests__/get_handlers.test.ts +47 -55
  53. package/integrations/google-sheet/__tests__/usage_parity.test.ts +3 -29
  54. package/integrations/google-sheet/__tests__/write_handlers.test.ts +64 -63
  55. package/integrations/google-sheet/credentials.json +50 -29
  56. package/integrations/google-sheet/credentials_hint_oauth_token.md +8 -0
  57. package/integrations/google-sheet/credentials_hint_service_account.md +10 -0
  58. package/integrations/google-slides/__tests__/get_handlers.test.ts +37 -36
  59. package/integrations/google-slides/__tests__/usage_parity.test.ts +3 -28
  60. package/integrations/google-slides/__tests__/write_handlers.test.ts +65 -58
  61. package/integrations/google-slides/credentials.json +50 -29
  62. package/integrations/google-slides/credentials_hint_oauth_token.md +8 -0
  63. package/integrations/google-slides/credentials_hint_service_account.md +10 -0
  64. package/integrations/notion/__tests__/get_handlers.test.ts +18 -15
  65. package/integrations/notion/__tests__/usage_parity.test.ts +3 -28
  66. package/integrations/notion/__tests__/write_and_admin_handlers.test.ts +56 -60
  67. package/integrations/notion/credentials.json +22 -17
  68. package/integrations/trello/__tests__/get_handlers.test.ts +58 -73
  69. package/integrations/trello/__tests__/usage_parity.test.ts +3 -28
  70. package/integrations/trello/__tests__/write_and_admin_handlers.test.ts +49 -67
  71. package/integrations/trello/credentials.json +26 -21
  72. package/integrations/trello/handlers/close_board.js +6 -0
  73. package/integrations/trello/handlers/create_board.js +11 -0
  74. package/integrations/trello/handlers/delete_board.js +13 -0
  75. package/integrations/trello/manifest.json +21 -0
  76. package/integrations/trello/schemas/close_board.json +10 -0
  77. package/integrations/trello/schemas/create_board.json +12 -0
  78. package/integrations/trello/schemas/delete_board.json +10 -0
  79. 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 { loadIntegrationManifest } from '../../../src/integrations/dataLoader.js'
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 via build*(name)', () => {
13
- const manifest = loadIntegrationManifest('notion')!
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 { IntegrationProxy } from '../../../src/integrations/proxy.js'
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
- parentDatabaseId?: string
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
- 'COMMANDABLE_MANAGED_OAUTH_BASE_URL',
15
- 'COMMANDABLE_MANAGED_OAUTH_SECRET_KEY',
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 buildWrite: (name: string) => ((input: any) => Promise<any>)
24
- let buildRead: (name: string) => ((input: any) => Promise<any>)
19
+ let notion: ReturnType<typeof createToolbox>
25
20
 
26
21
  beforeAll(async () => {
27
- const { COMMANDABLE_MANAGED_OAUTH_BASE_URL, COMMANDABLE_MANAGED_OAUTH_SECRET_KEY, NOTION_TEST_CONNECTION_ID } = env
28
-
29
- const proxy = new IntegrationProxy({
30
- managedOAuthBaseUrl: COMMANDABLE_MANAGED_OAUTH_BASE_URL,
31
- managedOAuthSecretKey: COMMANDABLE_MANAGED_OAUTH_SECRET_KEY,
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
- const integrationNode = { id: 'node-notion', type: 'notion', label: 'Notion', connectionId: NOTION_TEST_CONNECTION_ID } as any
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.parentDatabaseId)
60
+ if (!ctx.testDatabaseId)
65
61
  return expect(true).toBe(true)
66
62
 
67
- const create_page = buildWrite('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.parentDatabaseId },
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 = buildRead('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 = buildWrite('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 = buildWrite('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 = buildRead('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 = buildWrite('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 = buildRead('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 = buildWrite('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 = buildWrite('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 = buildRead('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 = buildWrite('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 = buildWrite('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 = buildWrite('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 = buildRead('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
- "schema": {
3
- "type": "object",
4
- "properties": {
5
- "token": {
6
- "type": "string",
7
- "title": "Internal Integration Token",
8
- "description": "Notion internal integration token (starts with \"secret_\")."
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 { IntegrationProxy } from '../../../../server/src/integrations/proxy.js'
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 managed OAuth.
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
- // - TRELLO_TEST_CONNECTION_ID (an existing managed OAuth connectionId for provider 'trello')
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
- 'TRELLO_TEST_CONNECTION_ID',
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 buildHandler: (name: string) => ((input: any) => Promise<any>)
28
+ let trello: ReturnType<typeof createToolbox>
34
29
 
35
30
  beforeAll(async () => {
36
- const { COMMANDABLE_MANAGED_OAUTH_BASE_URL, COMMANDABLE_MANAGED_OAUTH_SECRET_KEY, TRELLO_API_KEY, TRELLO_TEST_CONNECTION_ID } = env
37
-
38
- const proxy = new IntegrationProxy({
39
- managedOAuthBaseUrl: COMMANDABLE_MANAGED_OAUTH_BASE_URL,
40
- managedOAuthSecretKey: COMMANDABLE_MANAGED_OAUTH_SECRET_KEY,
41
- trelloApiKey: TRELLO_API_KEY,
42
- })
43
- const integrationNode = { id: 'node-trello', type: 'trello', label: 'Trello', connectionId: TRELLO_TEST_CONNECTION_ID } as any
44
-
45
- const tools = loadIntegrationTools('trello')
46
- expect(tools).toBeTruthy()
47
-
48
- buildHandler = (name: string) => {
49
- const tool = tools!.read.find(t => t.name === name)
50
- expect(tool, `tool ${name} exists`).toBeTruthy()
51
- const integration = {
52
- fetch: (path: string, init?: RequestInit) => proxy.call(integrationNode, path, init),
53
- }
54
- const build = new Function('integration', `return (${tool!.handlerCode});`)
55
- return build(integration) as (input: any) => Promise<any>
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 = buildHandler('get_member')
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 = buildHandler('get_member_boards')
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 = buildHandler('get_member_organizations')
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 = buildHandler('get_board')
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 = buildHandler('get_board_lists')
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 = buildHandler('get_board_cards')
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 = buildHandler('get_board_members')
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 = buildHandler('get_board_labels')
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 = buildHandler('get_board_custom_fields')
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 = buildHandler('get_board_memberships')
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 = buildHandler('get_list')
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 = buildHandler('get_list_cards')
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 = buildHandler('get_card')
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 = buildHandler('get_card_members')
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 = buildHandler('get_card_attachments')
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 = buildHandler('get_card_actions')
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 = buildHandler('get_card_checklists')
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 = buildHandler('get_card_custom_field_items')
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 = buildHandler('get_organization')
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 = buildHandler('get_organization_boards')
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 = buildHandler('search')
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 { loadIntegrationManifest } from '../../../src/integrations/dataLoader.js'
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 via build*(name)', () => {
13
- const manifest = loadIntegrationManifest('trello')!
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
  })