@membranehq/cli 0.1.1 → 0.1.3
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/index.js +140 -140
- package/package.json +16 -4
- package/.turbo/turbo-build.log +0 -9
- package/CHANGELOG.md +0 -7
- package/scripts/add-shebang.sh +0 -6
- package/scripts/prepare-package-json.ts +0 -29
- package/src/agent.tsx +0 -50
- package/src/cli.ts +0 -72
- package/src/commands/open.command.ts +0 -51
- package/src/commands/pull.command.ts +0 -75
- package/src/commands/push.command.ts +0 -79
- package/src/commands/test.command.ts +0 -99
- package/src/components/AddMcpServerScreen.tsx +0 -215
- package/src/components/AgentStatus.tsx +0 -15
- package/src/components/Main.tsx +0 -64
- package/src/components/OverviewSection.tsx +0 -24
- package/src/components/PersonalAccessTokenInput.tsx +0 -56
- package/src/components/RecentChanges.tsx +0 -65
- package/src/components/SelectWorkspace.tsx +0 -112
- package/src/components/Setup.tsx +0 -121
- package/src/components/WorkspaceStatus.tsx +0 -61
- package/src/contexts/FileWatcherContext.tsx +0 -81
- package/src/index.ts +0 -27
- package/src/legacy/commands/pullWorkspace.ts +0 -70
- package/src/legacy/commands/pushWorkspace.ts +0 -246
- package/src/legacy/integrationElements.ts +0 -78
- package/src/legacy/push/types.ts +0 -17
- package/src/legacy/reader/index.ts +0 -113
- package/src/legacy/types.ts +0 -17
- package/src/legacy/util.ts +0 -149
- package/src/legacy/workspace-elements/connectors.ts +0 -397
- package/src/legacy/workspace-elements/index.ts +0 -27
- package/src/legacy/workspace-tools/commands/pullWorkspace.ts +0 -70
- package/src/legacy/workspace-tools/integrationElements.ts +0 -78
- package/src/legacy/workspace-tools/util.ts +0 -149
- package/src/mcp/server-status.ts +0 -27
- package/src/mcp/server.ts +0 -36
- package/src/mcp/tools/getTestAccessToken.ts +0 -32
- package/src/modules/api/account-api-client.ts +0 -89
- package/src/modules/api/index.ts +0 -3
- package/src/modules/api/membrane-api-client.ts +0 -116
- package/src/modules/api/workspace-api-client.ts +0 -11
- package/src/modules/config/cwd-context.tsx +0 -11
- package/src/modules/config/project/getAgentVersion.ts +0 -16
- package/src/modules/config/project/index.ts +0 -8
- package/src/modules/config/project/paths.ts +0 -25
- package/src/modules/config/project/readProjectConfig.ts +0 -27
- package/src/modules/config/project/useProjectConfig.tsx +0 -103
- package/src/modules/config/system/index.ts +0 -35
- package/src/modules/file-watcher/index.ts +0 -166
- package/src/modules/file-watcher/types.ts +0 -14
- package/src/modules/setup/steps.ts +0 -9
- package/src/modules/setup/useSetup.ts +0 -16
- package/src/modules/status/useStatus.ts +0 -16
- package/src/modules/workspace-element-service/constants.ts +0 -121
- package/src/modules/workspace-element-service/getTypeAndKeyFromPath.ts +0 -69
- package/src/modules/workspace-element-service/index.ts +0 -304
- package/src/testing/environment.ts +0 -172
- package/src/testing/runners/base.runner.ts +0 -27
- package/src/testing/runners/test.runner.ts +0 -123
- package/src/testing/scripts/generate-test-report.ts +0 -757
- package/src/testing/test-suites/base.ts +0 -92
- package/src/testing/test-suites/data-collection.ts +0 -128
- package/src/testing/testers/base.ts +0 -115
- package/src/testing/testers/create.ts +0 -273
- package/src/testing/testers/delete.ts +0 -155
- package/src/testing/testers/find-by-id.ts +0 -135
- package/src/testing/testers/list.ts +0 -110
- package/src/testing/testers/match.ts +0 -149
- package/src/testing/testers/search.ts +0 -148
- package/src/testing/testers/spec.ts +0 -30
- package/src/testing/testers/update.ts +0 -284
- package/src/utils/auth.ts +0 -19
- package/src/utils/constants.ts +0 -27
- package/src/utils/fields.ts +0 -83
- package/src/utils/logger.ts +0 -106
- package/src/utils/templating.ts +0 -50
- package/tsconfig.json +0 -21
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { DataCollectionSpec, extractIntegrationAppErrorData } from '@integration-app/sdk'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
|
|
4
|
-
import { TestEnvironment } from '../environment'
|
|
5
|
-
import { BaseTester } from './base'
|
|
6
|
-
|
|
7
|
-
interface DataCollectionDeleteTestConfig {
|
|
8
|
-
input: {
|
|
9
|
-
id: string
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class DataCollectionDeleteTester extends BaseTester<DataCollectionDeleteTestConfig> {
|
|
14
|
-
private dataCollectionKey: string
|
|
15
|
-
private dataCollection: DataCollectionSpec
|
|
16
|
-
|
|
17
|
-
constructor({
|
|
18
|
-
environment,
|
|
19
|
-
dataCollectionKey,
|
|
20
|
-
dataCollection,
|
|
21
|
-
}: {
|
|
22
|
-
environment: TestEnvironment
|
|
23
|
-
dataCollectionKey: string
|
|
24
|
-
dataCollection: DataCollectionSpec
|
|
25
|
-
}) {
|
|
26
|
-
super({
|
|
27
|
-
environment,
|
|
28
|
-
path: `data/${dataCollectionKey}/delete`,
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
this.dataCollectionKey = dataCollectionKey
|
|
32
|
-
this.dataCollection = dataCollection
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async run(config?: DataCollectionDeleteTestConfig): Promise<void> {
|
|
36
|
-
const id =
|
|
37
|
-
(
|
|
38
|
-
this.environment.state[this.dataCollectionKey] as {
|
|
39
|
-
createdRecordId: string
|
|
40
|
-
}
|
|
41
|
-
)?.createdRecordId || config?.input?.id
|
|
42
|
-
if (!id) {
|
|
43
|
-
throw new Error(`No ID found in state or config for ${this.dataCollectionKey} delete`)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
await this.environment.client
|
|
48
|
-
.connection(this.environment.connectionId)
|
|
49
|
-
.dataCollection(this.dataCollectionKey)
|
|
50
|
-
.delete({ id })
|
|
51
|
-
} catch (deleteError: any) {
|
|
52
|
-
throw deleteError
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (this.dataCollection.findById) {
|
|
56
|
-
try {
|
|
57
|
-
await this.environment.client
|
|
58
|
-
.connection(this.environment.connectionId)
|
|
59
|
-
.dataCollection(this.dataCollectionKey)
|
|
60
|
-
.findById({
|
|
61
|
-
id,
|
|
62
|
-
})
|
|
63
|
-
} catch (error: any) {
|
|
64
|
-
if (error?.data?.data?.response?.status === 404) {
|
|
65
|
-
await this.assert(() => true, 'Record is not found after deletion')
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
throw error
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
await this.assert(() => true, 'No findById capability, skipping verification')
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async generateConfig(): Promise<DataCollectionDeleteTestConfig> {
|
|
77
|
-
let id = (
|
|
78
|
-
this.environment.state[this.dataCollectionKey] as {
|
|
79
|
-
createdRecordId: string
|
|
80
|
-
}
|
|
81
|
-
)?.createdRecordId
|
|
82
|
-
|
|
83
|
-
if (!id) {
|
|
84
|
-
if (!this.dataCollection.list) {
|
|
85
|
-
throw new Error(
|
|
86
|
-
`Can't find a record to test delete operation for ${this.dataCollectionKey}. List operation is not implemented for this data collection`,
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const listResponse = await this.environment.client
|
|
91
|
-
.connection(this.environment.connectionId)
|
|
92
|
-
.dataCollection(this.dataCollectionKey)
|
|
93
|
-
.list({})
|
|
94
|
-
|
|
95
|
-
if (!listResponse?.records?.length) {
|
|
96
|
-
throw new Error(`No records found to test delete for ${this.dataCollectionKey}`)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
id = listResponse.records[0].id
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
input: {
|
|
104
|
-
id,
|
|
105
|
-
},
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
protected async fixTestCase({
|
|
110
|
-
config,
|
|
111
|
-
error,
|
|
112
|
-
}: {
|
|
113
|
-
config: DataCollectionDeleteTestConfig
|
|
114
|
-
error: any
|
|
115
|
-
}): Promise<DataCollectionDeleteTestConfig> {
|
|
116
|
-
const errorData = extractIntegrationAppErrorData(error)
|
|
117
|
-
|
|
118
|
-
const prompt = `I'm trying to delete a record from a data collection.
|
|
119
|
-
|
|
120
|
-
I tried to delete a record with this ID:
|
|
121
|
-
|
|
122
|
-
${JSON.stringify(config.input?.id, null, 2)}
|
|
123
|
-
|
|
124
|
-
But got this error:
|
|
125
|
-
|
|
126
|
-
${JSON.stringify(errorData)}
|
|
127
|
-
|
|
128
|
-
Please analyze the error and provide:
|
|
129
|
-
1. A fixed ID that would work (as a string)
|
|
130
|
-
2. A short explanation of what was wrong with the original ID
|
|
131
|
-
|
|
132
|
-
Format your response as a JSON object with two fields:
|
|
133
|
-
{
|
|
134
|
-
"explanation": "short explanation of what was wrong and how you fixed it",
|
|
135
|
-
"id": "fixed id"
|
|
136
|
-
}.
|
|
137
|
-
|
|
138
|
-
Only return the JSON object, no other text or wrapping (like \`\`\`json or \`\`\`).`
|
|
139
|
-
|
|
140
|
-
const response = await this.environment.llm.complete({
|
|
141
|
-
prompt,
|
|
142
|
-
maxTokens: 1000,
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
const result = JSON.parse(response.trim())
|
|
146
|
-
|
|
147
|
-
console.warn(chalk.bold.yellow('[auto-fix]'), `${this.path}:`, result.explanation)
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
input: {
|
|
151
|
-
id: result.id,
|
|
152
|
-
},
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { DataCollectionSpec, extractIntegrationAppErrorData } from '@integration-app/sdk'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
|
|
4
|
-
import { TestEnvironment } from '../environment'
|
|
5
|
-
import { BaseTester } from './base'
|
|
6
|
-
|
|
7
|
-
interface DataCollectionFindByIdTestConfig {
|
|
8
|
-
input: {
|
|
9
|
-
id: string
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class DataCollectionFindByIdTester extends BaseTester<DataCollectionFindByIdTestConfig> {
|
|
14
|
-
private dataCollectionKey: string
|
|
15
|
-
private dataCollection: DataCollectionSpec
|
|
16
|
-
|
|
17
|
-
constructor({
|
|
18
|
-
environment,
|
|
19
|
-
dataCollectionKey,
|
|
20
|
-
dataCollection,
|
|
21
|
-
}: {
|
|
22
|
-
environment: TestEnvironment
|
|
23
|
-
dataCollectionKey: string
|
|
24
|
-
dataCollection: DataCollectionSpec
|
|
25
|
-
}) {
|
|
26
|
-
super({
|
|
27
|
-
environment,
|
|
28
|
-
path: `data/${dataCollectionKey}/find-by-id`,
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
this.dataCollectionKey = dataCollectionKey
|
|
32
|
-
this.dataCollection = dataCollection
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async run(config?: DataCollectionFindByIdTestConfig): Promise<void> {
|
|
36
|
-
const id =
|
|
37
|
-
(
|
|
38
|
-
this.environment.state[this.dataCollectionKey] as {
|
|
39
|
-
createdRecordId: string
|
|
40
|
-
}
|
|
41
|
-
)?.createdRecordId || config?.input?.id
|
|
42
|
-
if (!id) {
|
|
43
|
-
throw new Error(`No ID found in state or config for ${this.dataCollectionKey} find-by-id`)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const findByIdResponse = await this.environment.client
|
|
47
|
-
.connection(this.environment.connectionId)
|
|
48
|
-
.dataCollection(this.dataCollectionKey)
|
|
49
|
-
.findById({ id })
|
|
50
|
-
|
|
51
|
-
await this.assert(() => !!findByIdResponse.record, 'Record is returned from findById')
|
|
52
|
-
|
|
53
|
-
await this.assert(() => findByIdResponse.record.id === id, 'Returned record ID matches requested ID')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async generateConfig(): Promise<DataCollectionFindByIdTestConfig> {
|
|
57
|
-
let id = (
|
|
58
|
-
this.environment.state[this.dataCollectionKey] as {
|
|
59
|
-
createdRecordId: string
|
|
60
|
-
}
|
|
61
|
-
)?.createdRecordId
|
|
62
|
-
|
|
63
|
-
if (!id) {
|
|
64
|
-
if (!this.dataCollection.list) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
`Can't find a record to test findById operation for ${this.dataCollectionKey}. List operation is not implemented for this data collection`,
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const listResponse = await this.environment.client
|
|
71
|
-
.connection(this.environment.connectionId)
|
|
72
|
-
.dataCollection(this.dataCollectionKey)
|
|
73
|
-
.list({})
|
|
74
|
-
|
|
75
|
-
if (!listResponse?.records?.length) {
|
|
76
|
-
throw new Error(`No records found to test findById for ${this.dataCollectionKey}`)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
id = listResponse.records[0].id
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
input: {
|
|
84
|
-
id,
|
|
85
|
-
},
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
protected async fixTestCase({
|
|
90
|
-
config,
|
|
91
|
-
error,
|
|
92
|
-
}: {
|
|
93
|
-
config: DataCollectionFindByIdTestConfig
|
|
94
|
-
error: any
|
|
95
|
-
}): Promise<DataCollectionFindByIdTestConfig> {
|
|
96
|
-
const errorData = extractIntegrationAppErrorData(error)
|
|
97
|
-
|
|
98
|
-
const prompt = `I'm trying to find a record by ID in a data collection.
|
|
99
|
-
|
|
100
|
-
I tried to find a record with this ID:
|
|
101
|
-
|
|
102
|
-
${JSON.stringify(config.input?.id, null, 2)}
|
|
103
|
-
|
|
104
|
-
But got this error:
|
|
105
|
-
|
|
106
|
-
${JSON.stringify(errorData)}
|
|
107
|
-
|
|
108
|
-
Please analyze the error and provide:
|
|
109
|
-
1. A fixed ID that would work (as a string)
|
|
110
|
-
2. A short explanation of what was wrong with the original ID
|
|
111
|
-
|
|
112
|
-
Format your response as a JSON object with two fields:
|
|
113
|
-
{
|
|
114
|
-
"explanation": "short explanation of what was wrong and how you fixed it",
|
|
115
|
-
"id": "fixed id"
|
|
116
|
-
}.
|
|
117
|
-
|
|
118
|
-
Only return the JSON object, no other text or wrapping (like \`\`\`json or \`\`\`).`
|
|
119
|
-
|
|
120
|
-
const response = await this.environment.llm.complete({
|
|
121
|
-
prompt,
|
|
122
|
-
maxTokens: 1000,
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
const result = JSON.parse(response.trim())
|
|
126
|
-
|
|
127
|
-
console.warn(chalk.bold.yellow('[auto-fix]'), `${this.path}:`, result.explanation)
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
input: {
|
|
131
|
-
id: result.id,
|
|
132
|
-
},
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { extractIntegrationAppErrorData } from '@integration-app/sdk'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
|
|
4
|
-
import { TestEnvironment } from '../environment'
|
|
5
|
-
import { BaseTester } from './base'
|
|
6
|
-
|
|
7
|
-
interface DataCollectionListTestConfig {
|
|
8
|
-
input: {
|
|
9
|
-
sort?: {
|
|
10
|
-
field: string
|
|
11
|
-
direction: 'asc' | 'desc'
|
|
12
|
-
}
|
|
13
|
-
filter?: Record<string, any>
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class DataCollectionListTester extends BaseTester<DataCollectionListTestConfig> {
|
|
18
|
-
private dataCollectionKey: string
|
|
19
|
-
|
|
20
|
-
constructor({ environment, dataCollectionKey }: { environment: TestEnvironment; dataCollectionKey: string }) {
|
|
21
|
-
super({
|
|
22
|
-
environment,
|
|
23
|
-
path: `data/${dataCollectionKey}/list`,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
this.dataCollectionKey = dataCollectionKey
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async run(config: DataCollectionListTestConfig): Promise<void> {
|
|
30
|
-
const listResponse = await this.environment.client
|
|
31
|
-
.connection(this.environment.connectionId)
|
|
32
|
-
.dataCollection(this.dataCollectionKey)
|
|
33
|
-
.list(config.input)
|
|
34
|
-
|
|
35
|
-
await this.assert(() => Array.isArray(listResponse.records), 'Response has records field as array')
|
|
36
|
-
|
|
37
|
-
if (config.input.sort) {
|
|
38
|
-
const { field, direction } = config.input.sort
|
|
39
|
-
const records = listResponse.records
|
|
40
|
-
|
|
41
|
-
if (records.length > 1) {
|
|
42
|
-
for (let i = 1; i < records.length; i++) {
|
|
43
|
-
const prev = records[i - 1].fields[field]
|
|
44
|
-
const curr = records[i].fields[field]
|
|
45
|
-
|
|
46
|
-
// Skip comparison if either value is null/undefined
|
|
47
|
-
if (prev == null || curr == null) continue
|
|
48
|
-
|
|
49
|
-
// Use proper comparison logic
|
|
50
|
-
if (direction === 'asc') {
|
|
51
|
-
await this.assert(() => prev <= curr, `Records are sorted by ${field} in ascending order`)
|
|
52
|
-
} else {
|
|
53
|
-
await this.assert(() => prev >= curr, `Records are sorted by ${field} in descending order`)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async generateConfig(): Promise<DataCollectionListTestConfig> {
|
|
61
|
-
return {
|
|
62
|
-
input: {},
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
protected async fixTestCase({
|
|
67
|
-
config,
|
|
68
|
-
error,
|
|
69
|
-
}: {
|
|
70
|
-
config: DataCollectionListTestConfig
|
|
71
|
-
error: any
|
|
72
|
-
}): Promise<DataCollectionListTestConfig> {
|
|
73
|
-
const errorData = extractIntegrationAppErrorData(error)
|
|
74
|
-
|
|
75
|
-
const prompt = `I'm trying to list records from a data collection.
|
|
76
|
-
|
|
77
|
-
I tried to list records with these parameters:
|
|
78
|
-
|
|
79
|
-
${JSON.stringify(config.input, null, 2)}
|
|
80
|
-
|
|
81
|
-
But got this error:
|
|
82
|
-
|
|
83
|
-
${JSON.stringify(errorData)}
|
|
84
|
-
|
|
85
|
-
Please analyze the error and provide:
|
|
86
|
-
1. Fixed parameters that would work (as a JSON object)
|
|
87
|
-
2. A short explanation of what was wrong with the original parameters
|
|
88
|
-
|
|
89
|
-
Format your response as a JSON object with two fields:
|
|
90
|
-
{
|
|
91
|
-
"explanation": "short explanation of what was wrong and how you fixed it",
|
|
92
|
-
"input": { ... fixed parameters ... }
|
|
93
|
-
}.
|
|
94
|
-
|
|
95
|
-
Only return the JSON object, no other text or wrapping (like \`\`\`json or \`\`\`).`
|
|
96
|
-
|
|
97
|
-
const response = await this.environment.llm.complete({
|
|
98
|
-
prompt,
|
|
99
|
-
maxTokens: 1000,
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
const result = JSON.parse(response.trim())
|
|
103
|
-
|
|
104
|
-
console.warn(chalk.bold.yellow('[auto-fix]'), `${this.path}:`, result.explanation)
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
input: result.input,
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { DataCollectionSpec, extractIntegrationAppErrorData } from '@integration-app/sdk'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
|
|
4
|
-
import { TestEnvironment } from '../environment'
|
|
5
|
-
import { BaseTester } from './base'
|
|
6
|
-
import { softCompare } from '../../utils/fields'
|
|
7
|
-
|
|
8
|
-
interface DataCollectionMatchTestConfig {
|
|
9
|
-
input: {
|
|
10
|
-
query: Record<string, any>
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class DataCollectionMatchTester extends BaseTester<DataCollectionMatchTestConfig> {
|
|
15
|
-
private dataCollectionKey: string
|
|
16
|
-
private dataCollection: DataCollectionSpec
|
|
17
|
-
|
|
18
|
-
constructor({
|
|
19
|
-
environment,
|
|
20
|
-
dataCollectionKey,
|
|
21
|
-
dataCollection,
|
|
22
|
-
}: {
|
|
23
|
-
environment: TestEnvironment
|
|
24
|
-
dataCollectionKey: string
|
|
25
|
-
dataCollection: DataCollectionSpec
|
|
26
|
-
}) {
|
|
27
|
-
super({
|
|
28
|
-
environment,
|
|
29
|
-
path: `data/${dataCollectionKey}/match`,
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
this.dataCollectionKey = dataCollectionKey
|
|
33
|
-
this.dataCollection = dataCollection
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async run(config: DataCollectionMatchTestConfig): Promise<void> {
|
|
37
|
-
const matchResponse = await this.environment.client
|
|
38
|
-
.connection(this.environment.connectionId)
|
|
39
|
-
.dataCollection(this.dataCollectionKey)
|
|
40
|
-
.match(config.input)
|
|
41
|
-
|
|
42
|
-
await this.assert(() => !!matchResponse.record, 'Response contains a record')
|
|
43
|
-
|
|
44
|
-
const matchedRecord = matchResponse.record
|
|
45
|
-
const fullRecordResponse = await this.environment.client
|
|
46
|
-
.connection(this.environment.connectionId)
|
|
47
|
-
.dataCollection(this.dataCollectionKey)
|
|
48
|
-
.findById({ id: matchedRecord.id })
|
|
49
|
-
|
|
50
|
-
const record = fullRecordResponse.record
|
|
51
|
-
for (const [key, value] of Object.entries(config.input.query)) {
|
|
52
|
-
const actualValue = record.fields[key]
|
|
53
|
-
await this.assert(
|
|
54
|
-
() => softCompare(actualValue, value),
|
|
55
|
-
`Record field ${key} matches input value - expected ${JSON.stringify(value)} got ${JSON.stringify(actualValue)}`,
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async generateConfig(): Promise<DataCollectionMatchTestConfig> {
|
|
61
|
-
if (!this.dataCollection.list) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`Can't find a record to test match operation for ${this.dataCollectionKey}. List operation is not implemented for this data collection`,
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const listResponse = await this.environment.client
|
|
68
|
-
.connection(this.environment.connectionId)
|
|
69
|
-
.dataCollection(this.dataCollectionKey)
|
|
70
|
-
.list({})
|
|
71
|
-
|
|
72
|
-
if (!listResponse?.records?.length) {
|
|
73
|
-
throw new Error('No records found to test match')
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const record = listResponse.records[0]
|
|
77
|
-
if (!record?.fields) {
|
|
78
|
-
throw new Error('First record has no fields to match against')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const query: Record<string, any> = {}
|
|
82
|
-
const fields = record.fields || {}
|
|
83
|
-
|
|
84
|
-
const fieldEntries = Object.entries(fields)
|
|
85
|
-
.filter(([key, value]) => key !== 'links' && value != null && value !== '')
|
|
86
|
-
.slice(0, 1)
|
|
87
|
-
|
|
88
|
-
if (fieldEntries.length === 0) {
|
|
89
|
-
throw new Error('No usable fields found in record for matching')
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
for (const [key, value] of fieldEntries) {
|
|
93
|
-
query[key] = value
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
input: {
|
|
98
|
-
query,
|
|
99
|
-
},
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
protected async fixTestCase({
|
|
104
|
-
config,
|
|
105
|
-
error,
|
|
106
|
-
}: {
|
|
107
|
-
config: DataCollectionMatchTestConfig
|
|
108
|
-
error: any
|
|
109
|
-
}): Promise<DataCollectionMatchTestConfig> {
|
|
110
|
-
const errorData = extractIntegrationAppErrorData(error)
|
|
111
|
-
|
|
112
|
-
const prompt = `I'm trying to match a record in a data collection.
|
|
113
|
-
|
|
114
|
-
I tried to match a record with this query:
|
|
115
|
-
|
|
116
|
-
${JSON.stringify(config.input?.query, null, 2)}
|
|
117
|
-
|
|
118
|
-
But got this error:
|
|
119
|
-
|
|
120
|
-
${JSON.stringify(errorData)}
|
|
121
|
-
|
|
122
|
-
Please analyze the error and provide:
|
|
123
|
-
1. Fixed query that would work (as a JSON object)
|
|
124
|
-
2. A short explanation of what was wrong with the original query
|
|
125
|
-
|
|
126
|
-
Format your response as a JSON object with two fields:
|
|
127
|
-
{
|
|
128
|
-
"explanation": "short explanation of what was wrong and how you fixed it",
|
|
129
|
-
"query": { ... fixed query ... }
|
|
130
|
-
}.
|
|
131
|
-
|
|
132
|
-
Only return the JSON object, no other text or wrapping (like \`\`\`json or \`\`\`).`
|
|
133
|
-
|
|
134
|
-
const response = await this.environment.llm.complete({
|
|
135
|
-
prompt,
|
|
136
|
-
maxTokens: 1000,
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
const result = JSON.parse(response.trim())
|
|
140
|
-
|
|
141
|
-
console.warn(chalk.bold.yellow('[auto-fix]'), `${this.path}:`, result.explanation)
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
input: {
|
|
145
|
-
query: result.query,
|
|
146
|
-
},
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { DataCollectionSpec, extractIntegrationAppErrorData } from '@integration-app/sdk'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
|
|
4
|
-
import { TestEnvironment } from '../environment'
|
|
5
|
-
import { BaseTester } from './base'
|
|
6
|
-
|
|
7
|
-
interface DataCollectionSearchTestConfig {
|
|
8
|
-
input: {
|
|
9
|
-
query: string
|
|
10
|
-
}
|
|
11
|
-
expectedRecordId: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class DataCollectionSearchTester extends BaseTester<DataCollectionSearchTestConfig> {
|
|
15
|
-
private dataCollectionKey: string
|
|
16
|
-
private dataCollection: DataCollectionSpec
|
|
17
|
-
|
|
18
|
-
constructor({
|
|
19
|
-
environment,
|
|
20
|
-
dataCollectionKey,
|
|
21
|
-
dataCollection,
|
|
22
|
-
}: {
|
|
23
|
-
environment: TestEnvironment
|
|
24
|
-
dataCollectionKey: string
|
|
25
|
-
dataCollection: DataCollectionSpec
|
|
26
|
-
}) {
|
|
27
|
-
super({
|
|
28
|
-
environment,
|
|
29
|
-
path: `data/${dataCollectionKey}/search`,
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
this.dataCollectionKey = dataCollectionKey
|
|
33
|
-
this.dataCollection = dataCollection
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async run(config: DataCollectionSearchTestConfig): Promise<void> {
|
|
37
|
-
const searchResponse = await this.environment.client
|
|
38
|
-
.connection(this.environment.connectionId)
|
|
39
|
-
.dataCollection(this.dataCollectionKey)
|
|
40
|
-
.search(config.input)
|
|
41
|
-
|
|
42
|
-
await this.assert(() => Array.isArray(searchResponse.records), 'Response has records field as array')
|
|
43
|
-
|
|
44
|
-
const foundExpectedRecord = searchResponse.records.some((r) => r.id === config.expectedRecordId)
|
|
45
|
-
|
|
46
|
-
if (!foundExpectedRecord) {
|
|
47
|
-
console.debug(chalk.yellow(`Search query "${config.input.query}" did not return expected record`))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
await this.assert(() => foundExpectedRecord, 'Search returns expected record')
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async generateConfig(): Promise<DataCollectionSearchTestConfig> {
|
|
54
|
-
if (!this.dataCollection.list) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
`Can't find a record to test search operation for ${this.dataCollectionKey}. List operation is not implemented for this data collection`,
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const listResponse = await this.environment.client
|
|
61
|
-
.connection(this.environment.connectionId)
|
|
62
|
-
.dataCollection(this.dataCollectionKey)
|
|
63
|
-
.list({})
|
|
64
|
-
|
|
65
|
-
if (!listResponse.records.length) {
|
|
66
|
-
throw new Error('No records found to test search')
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const record = listResponse.records[0]
|
|
70
|
-
|
|
71
|
-
// Ask LLM for candidate query string
|
|
72
|
-
const candidateQuery = await this.getSearchQueryWithLLM(record.fields)
|
|
73
|
-
|
|
74
|
-
if (!candidateQuery) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
'Unable to generate search query. LLM could not identify a suitable field value from the record to use as the search query.',
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
input: { query: candidateQuery },
|
|
82
|
-
expectedRecordId: record.id,
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private async getSearchQueryWithLLM(fields: Record<string, any>): Promise<string | null> {
|
|
87
|
-
const prompt = `You are given the fields of a record from a data collection. Identify the BEST single field value that a user is MOST likely to type when searching for this record (e.g. name, title, email, external id). Ignore null, empty strings, technical IDs, HTML, and timestamps unless they are obviously human-searchable. Make sure the field value is as unique as possible for this record. RETURN ONLY the field value as a plain string. No additional text, no JSON, no quotes.
|
|
88
|
-
|
|
89
|
-
Record fields:
|
|
90
|
-
${JSON.stringify(fields, null, 2)}
|
|
91
|
-
|
|
92
|
-
Field value:`
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const response = await this.environment.llm.complete({ prompt, maxTokens: 1000 })
|
|
96
|
-
return response.trim() || null
|
|
97
|
-
} catch (_) {
|
|
98
|
-
// TODO: it's a good idea to retry or change the prompt
|
|
99
|
-
return null
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
protected async fixTestCase({
|
|
104
|
-
config,
|
|
105
|
-
error,
|
|
106
|
-
}: {
|
|
107
|
-
config: DataCollectionSearchTestConfig
|
|
108
|
-
error: any
|
|
109
|
-
}): Promise<DataCollectionSearchTestConfig> {
|
|
110
|
-
const errorData = extractIntegrationAppErrorData(error)
|
|
111
|
-
|
|
112
|
-
const prompt = `I'm trying to search records in a data collection.
|
|
113
|
-
|
|
114
|
-
I tried to search records with these parameters:
|
|
115
|
-
|
|
116
|
-
${JSON.stringify(config.input, null, 2)}
|
|
117
|
-
|
|
118
|
-
But got this error:
|
|
119
|
-
|
|
120
|
-
${JSON.stringify(errorData)}
|
|
121
|
-
|
|
122
|
-
Please analyze the error and provide:
|
|
123
|
-
1. Fixed parameters that would work (as a JSON object)
|
|
124
|
-
2. A short explanation of what was wrong with the original parameters
|
|
125
|
-
|
|
126
|
-
Format your response as a JSON object with two fields:
|
|
127
|
-
{
|
|
128
|
-
"explanation": "short explanation of what was wrong and how you fixed it",
|
|
129
|
-
"input": { ... fixed parameters ... }
|
|
130
|
-
}.
|
|
131
|
-
|
|
132
|
-
Only return the JSON object, no other text or wrapping (like \`\`\`json or \`\`\`).`
|
|
133
|
-
|
|
134
|
-
const response = await this.environment.llm.complete({
|
|
135
|
-
prompt,
|
|
136
|
-
maxTokens: 1000,
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
const result = JSON.parse(response.trim())
|
|
140
|
-
|
|
141
|
-
console.warn(chalk.bold.yellow('[auto-fix]'), `${this.path}:`, result.explanation)
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
input: result.input,
|
|
145
|
-
expectedRecordId: config.expectedRecordId,
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|