@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,92 +0,0 @@
|
|
|
1
|
-
import { extractIntegrationAppErrorData, setValueAtLocator } from '@integration-app/sdk'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
import merge from 'lodash/merge.js'
|
|
4
|
-
|
|
5
|
-
import { handleTemplate } from '../../utils/templating'
|
|
6
|
-
import { TestEnvironment } from '../environment'
|
|
7
|
-
import { BaseTester } from '../testers/base'
|
|
8
|
-
|
|
9
|
-
export class BaseTestSuite {
|
|
10
|
-
private result: any
|
|
11
|
-
protected environment: TestEnvironment
|
|
12
|
-
|
|
13
|
-
constructor({ environment }: { environment: TestEnvironment }) {
|
|
14
|
-
this.environment = environment
|
|
15
|
-
this.result = {}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async run() {}
|
|
19
|
-
|
|
20
|
-
getResult() {
|
|
21
|
-
return this.result
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
protected async runTest(tester: BaseTester) {
|
|
25
|
-
console.debug(`${chalk.bold.cyan('[start]')} ${chalk.yellow(tester.path)}`)
|
|
26
|
-
|
|
27
|
-
let done = false
|
|
28
|
-
let triedToFix = false
|
|
29
|
-
|
|
30
|
-
let result = {}
|
|
31
|
-
let testCase = {}
|
|
32
|
-
let processedTestCase
|
|
33
|
-
|
|
34
|
-
while (!done) {
|
|
35
|
-
try {
|
|
36
|
-
testCase = await tester.readTestCase()
|
|
37
|
-
|
|
38
|
-
// Create test file if it doesn't exist
|
|
39
|
-
if (!testCase) {
|
|
40
|
-
testCase = await tester.generateConfig()
|
|
41
|
-
await this.environment.writeYaml(tester.getTestCasePath(), testCase)
|
|
42
|
-
console.debug(`${chalk.bold.yellow('[initialized]')} ${chalk.yellow(tester.path)}`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
processedTestCase = handleTemplate(testCase, this.environment.state)
|
|
46
|
-
|
|
47
|
-
await tester.run(processedTestCase)
|
|
48
|
-
|
|
49
|
-
console.debug(`${chalk.bold.green('[success]')} ${chalk.yellow(tester.path)}`)
|
|
50
|
-
done = true
|
|
51
|
-
result = tester.getResult()
|
|
52
|
-
|
|
53
|
-
setValueAtLocator(this.result, tester.path, result)
|
|
54
|
-
} catch (e) {
|
|
55
|
-
console.error(`${chalk.bold.red('[error]')} ${chalk.yellow(tester.path)}: ${e}`)
|
|
56
|
-
|
|
57
|
-
if (this.environment.options.fix && !triedToFix) {
|
|
58
|
-
triedToFix = true
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
await tester.fix(e as Error)
|
|
62
|
-
continue
|
|
63
|
-
} catch (e) {
|
|
64
|
-
console.error(`${chalk.bold.red('[fix fail]')} ${chalk.yellow(tester.path)}: ${e}`)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
setValueAtLocator(this.result, tester.path, {
|
|
69
|
-
error: extractIntegrationAppErrorData(e),
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
done = true
|
|
73
|
-
result = {
|
|
74
|
-
error: extractIntegrationAppErrorData(e),
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const updatedTestCase = {
|
|
80
|
-
...processedTestCase,
|
|
81
|
-
result,
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
await this.environment.writeYaml(tester.getTestCasePath(), updatedTestCase)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
protected async runTestSuite(suite: BaseTestSuite) {
|
|
88
|
-
await suite.run()
|
|
89
|
-
|
|
90
|
-
merge(this.result, suite.getResult())
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { DataCollectionSpec } from '@integration-app/sdk'
|
|
2
|
-
|
|
3
|
-
import { TestEnvironment } from '../environment'
|
|
4
|
-
import { BaseTestSuite } from './base'
|
|
5
|
-
import { Logger } from '../../utils/logger'
|
|
6
|
-
import { DataCollectionCreateTester } from '../testers/create'
|
|
7
|
-
import { DataCollectionDeleteTester } from '../testers/delete'
|
|
8
|
-
import { DataCollectionFindByIdTester } from '../testers/find-by-id'
|
|
9
|
-
import { DataCollectionListTester } from '../testers/list'
|
|
10
|
-
import { DataCollectionMatchTester } from '../testers/match'
|
|
11
|
-
import { DataCollectionSearchTester } from '../testers/search'
|
|
12
|
-
import { DataCollectionSpecTester } from '../testers/spec'
|
|
13
|
-
import { DataCollectionUpdateTester } from '../testers/update'
|
|
14
|
-
|
|
15
|
-
interface TesterConfig {
|
|
16
|
-
testerClass: any
|
|
17
|
-
operationKey?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const DATA_COLLECTION_TESTERS = {
|
|
21
|
-
spec: { testerClass: DataCollectionSpecTester },
|
|
22
|
-
create: { testerClass: DataCollectionCreateTester, operationKey: 'create' },
|
|
23
|
-
'find-by-id': { testerClass: DataCollectionFindByIdTester, operationKey: 'findById' },
|
|
24
|
-
list: { testerClass: DataCollectionListTester, operationKey: 'list' },
|
|
25
|
-
match: { testerClass: DataCollectionMatchTester, operationKey: 'match' },
|
|
26
|
-
search: { testerClass: DataCollectionSearchTester, operationKey: 'search' },
|
|
27
|
-
update: { testerClass: DataCollectionUpdateTester, operationKey: 'update' },
|
|
28
|
-
delete: { testerClass: DataCollectionDeleteTester, operationKey: 'delete' },
|
|
29
|
-
} as const
|
|
30
|
-
|
|
31
|
-
type TestMethod = keyof typeof DATA_COLLECTION_TESTERS
|
|
32
|
-
|
|
33
|
-
export class DataCollectionTestSuite extends BaseTestSuite {
|
|
34
|
-
private readonly dataCollectionKey: string
|
|
35
|
-
private readonly testMethod?: string
|
|
36
|
-
|
|
37
|
-
constructor({
|
|
38
|
-
environment,
|
|
39
|
-
dataCollectionKey,
|
|
40
|
-
testMethod,
|
|
41
|
-
}: {
|
|
42
|
-
environment: TestEnvironment
|
|
43
|
-
dataCollectionKey: string
|
|
44
|
-
testMethod?: string
|
|
45
|
-
}) {
|
|
46
|
-
super({ environment })
|
|
47
|
-
this.dataCollectionKey = dataCollectionKey
|
|
48
|
-
this.testMethod = testMethod
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async run(): Promise<void> {
|
|
52
|
-
const dataCollection = await this.fetchDataCollection()
|
|
53
|
-
const testersToRun = this.testMethod
|
|
54
|
-
? [this.createTester(this.testMethod as TestMethod, dataCollection)]
|
|
55
|
-
: this.createAllAvailableTesters(dataCollection)
|
|
56
|
-
|
|
57
|
-
Logger.info('🚀 Running tests...')
|
|
58
|
-
|
|
59
|
-
for (const tester of testersToRun) {
|
|
60
|
-
await this.runTest(tester)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
this.displayTestSummary(testersToRun.length)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private async fetchDataCollection() {
|
|
67
|
-
return this.environment.client
|
|
68
|
-
.connection(this.environment.connectionId)
|
|
69
|
-
.dataCollection(this.dataCollectionKey)
|
|
70
|
-
.get()
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private isOperationAvailable(dataCollection: DataCollectionSpec, operationKey?: string): boolean {
|
|
74
|
-
// If no operation is required (e.g., spec validation), tester is always available
|
|
75
|
-
if (!operationKey) return true
|
|
76
|
-
|
|
77
|
-
return !!dataCollection[operationKey]
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private createTester(testMethod: TestMethod, dataCollection: any) {
|
|
81
|
-
const config = DATA_COLLECTION_TESTERS[testMethod]
|
|
82
|
-
|
|
83
|
-
if (!config) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
`Unknown test method "${testMethod}". Available: ${this.getAvailableTestMethods(dataCollection).join(', ')}`,
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const operationKey = 'operationKey' in config ? config.operationKey : undefined
|
|
90
|
-
if (!this.isOperationAvailable(dataCollection, operationKey)) {
|
|
91
|
-
throw new Error(`${operationKey} operation not available for data collection "${this.dataCollectionKey}"`)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return this.instantiateTester(config, dataCollection)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private createAllAvailableTesters(dataCollection: any) {
|
|
98
|
-
return Object.entries(DATA_COLLECTION_TESTERS)
|
|
99
|
-
.filter(([, config]) => {
|
|
100
|
-
const operationKey = 'operationKey' in config ? config.operationKey : undefined
|
|
101
|
-
return this.isOperationAvailable(dataCollection, operationKey)
|
|
102
|
-
})
|
|
103
|
-
.map(([, config]) => this.instantiateTester(config, dataCollection))
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private instantiateTester(config: TesterConfig, dataCollection: any) {
|
|
107
|
-
const testerConfig = {
|
|
108
|
-
environment: this.environment,
|
|
109
|
-
dataCollectionKey: this.dataCollectionKey,
|
|
110
|
-
dataCollection,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return new config.testerClass(testerConfig)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private getAvailableTestMethods(dataCollection: any): string[] {
|
|
117
|
-
return Object.entries(DATA_COLLECTION_TESTERS)
|
|
118
|
-
.filter(([, config]) => {
|
|
119
|
-
const operationKey = 'operationKey' in config ? config.operationKey : undefined
|
|
120
|
-
return this.isOperationAvailable(dataCollection, operationKey)
|
|
121
|
-
})
|
|
122
|
-
.map(([method]) => method)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private displayTestSummary(testCount: number): void {
|
|
126
|
-
Logger.info(`📊 ${testCount} test${testCount === 1 ? '' : 's'} executed`)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { extractIntegrationAppErrorData } from '@integration-app/sdk'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
|
|
4
|
-
import { TestEnvironment } from '../environment'
|
|
5
|
-
|
|
6
|
-
export type TestRunnerOptions = {
|
|
7
|
-
environment: TestEnvironment
|
|
8
|
-
level: number
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type TestRunnerFixConfigParams<ConfigClass> = {
|
|
12
|
-
config: ConfigClass
|
|
13
|
-
error: Error
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type TestRunAssertion = {
|
|
17
|
-
message: string
|
|
18
|
-
result: boolean
|
|
19
|
-
details?: any
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type TestRunnerResult = {
|
|
23
|
-
logs: any[]
|
|
24
|
-
assertions: TestRunAssertion[]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export abstract class BaseTester<ConfigClass = any> {
|
|
28
|
-
protected environment: TestEnvironment
|
|
29
|
-
protected level: number
|
|
30
|
-
protected logs: any[]
|
|
31
|
-
protected assertions: TestRunAssertion[]
|
|
32
|
-
protected resultsLocator: string
|
|
33
|
-
public path: string
|
|
34
|
-
|
|
35
|
-
constructor({ environment, path }: { environment: TestEnvironment; path: string }) {
|
|
36
|
-
this.environment = environment
|
|
37
|
-
this.logs = []
|
|
38
|
-
this.assertions = []
|
|
39
|
-
this.path = path
|
|
40
|
-
this.level = 0
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
abstract run(config: ConfigClass): Promise<void>
|
|
44
|
-
|
|
45
|
-
async fix(error: Error) {
|
|
46
|
-
const testCase = await this.readTestCase()
|
|
47
|
-
|
|
48
|
-
if (!testCase) {
|
|
49
|
-
throw new Error(`No config found for test ${this.path}`)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const fixedTestCase = await this.fixTestCase({
|
|
53
|
-
config: testCase,
|
|
54
|
-
error,
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
console.debug(chalk.bold.yellow('[auto-fix]'), this.path)
|
|
58
|
-
await this.environment.writeYaml(this.getTestCasePath(), fixedTestCase)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async readTestCase(): Promise<ConfigClass | undefined> {
|
|
62
|
-
const configPath = this.getTestCasePath()
|
|
63
|
-
return this.environment.readYaml<ConfigClass>(configPath)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async generateConfig(): Promise<ConfigClass> {
|
|
67
|
-
return {} as ConfigClass
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
protected async fixTestCase(_params: TestRunnerFixConfigParams<ConfigClass>): Promise<ConfigClass> {
|
|
71
|
-
throw new Error(`Auto-fix is not implemented for test ${this.path}`)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
getResult(): TestRunnerResult {
|
|
75
|
-
return {
|
|
76
|
-
logs: this.logs,
|
|
77
|
-
assertions: this.assertions,
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
protected async assert(condition: () => Promise<boolean> | boolean, message: string, details?: any) {
|
|
82
|
-
try {
|
|
83
|
-
const result = await condition()
|
|
84
|
-
if (result) {
|
|
85
|
-
this.logMsg(`✅ ${message}`)
|
|
86
|
-
this.assertions.push({
|
|
87
|
-
message,
|
|
88
|
-
result,
|
|
89
|
-
})
|
|
90
|
-
} else {
|
|
91
|
-
this.logMsg(`❌ ${message}`)
|
|
92
|
-
this.assertions.push({
|
|
93
|
-
message,
|
|
94
|
-
result: false,
|
|
95
|
-
details,
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
} catch (e) {
|
|
99
|
-
this.assertions.push({
|
|
100
|
-
message,
|
|
101
|
-
result: false,
|
|
102
|
-
details: extractIntegrationAppErrorData(e),
|
|
103
|
-
})
|
|
104
|
-
this.logMsg(`❌ ${message}: ${(e as Error).message}`)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
protected logMsg(message: string) {
|
|
109
|
-
console.debug(`${' '.repeat(this.level * 2)}${message}`)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
public getTestCasePath() {
|
|
113
|
-
return `${this.path}.test.yml`
|
|
114
|
-
}
|
|
115
|
-
}
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DataCollectionSpec,
|
|
3
|
-
excludeWriteOnlyFieldsFromSchema,
|
|
4
|
-
getDataCollectionCreateFields,
|
|
5
|
-
valueToSchema,
|
|
6
|
-
extractIntegrationAppErrorData,
|
|
7
|
-
makeDataLocationPath,
|
|
8
|
-
getRequiredFieldsFromSchema,
|
|
9
|
-
getValueAtLocator,
|
|
10
|
-
setValueAtLocator,
|
|
11
|
-
walkSchema,
|
|
12
|
-
} from '@integration-app/sdk'
|
|
13
|
-
import chalk from 'chalk'
|
|
14
|
-
|
|
15
|
-
import { TestEnvironment } from '../environment'
|
|
16
|
-
import { BaseTester } from './base'
|
|
17
|
-
import { getNotMatchingSubFields } from '../../utils/fields'
|
|
18
|
-
|
|
19
|
-
interface DataCollectionCreateTestConfig {
|
|
20
|
-
input: {
|
|
21
|
-
fields: Record<string, any>
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface ReferenceCollection {
|
|
26
|
-
key: string
|
|
27
|
-
parameters?: Record<string, any>
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface ExampleRecord {
|
|
31
|
-
id: string
|
|
32
|
-
fields: Record<string, any>
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class DataCollectionCreateTester extends BaseTester<DataCollectionCreateTestConfig> {
|
|
36
|
-
private dataCollectionKey: string
|
|
37
|
-
private dataCollection: DataCollectionSpec
|
|
38
|
-
|
|
39
|
-
constructor({
|
|
40
|
-
environment,
|
|
41
|
-
dataCollectionKey,
|
|
42
|
-
dataCollection,
|
|
43
|
-
}: {
|
|
44
|
-
environment: TestEnvironment
|
|
45
|
-
dataCollectionKey: string
|
|
46
|
-
dataCollection: DataCollectionSpec
|
|
47
|
-
}) {
|
|
48
|
-
super({
|
|
49
|
-
environment,
|
|
50
|
-
path: `data/${dataCollectionKey}/create`,
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
this.dataCollectionKey = dataCollectionKey
|
|
54
|
-
this.dataCollection = dataCollection
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async run(config: DataCollectionCreateTestConfig): Promise<void> {
|
|
58
|
-
const createResponse = await this.environment.client
|
|
59
|
-
.connection(this.environment.connectionId)
|
|
60
|
-
.dataCollection(this.dataCollectionKey)
|
|
61
|
-
.create(config.input)
|
|
62
|
-
|
|
63
|
-
await this.assert(() => !!createResponse.id, 'Returned ID of a created record')
|
|
64
|
-
|
|
65
|
-
if (createResponse.id) {
|
|
66
|
-
this.environment.state[this.dataCollectionKey] = {
|
|
67
|
-
createdRecordId: createResponse.id,
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const createFieldsSchema = getDataCollectionCreateFields(this.dataCollection)
|
|
72
|
-
|
|
73
|
-
if (this.dataCollection.findById) {
|
|
74
|
-
const findByIdResponse = await this.environment.client
|
|
75
|
-
.connection(this.environment.connectionId)
|
|
76
|
-
.dataCollection(this.dataCollectionKey)
|
|
77
|
-
.findById({
|
|
78
|
-
id: createResponse.id,
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
await this.assert(() => !!findByIdResponse.record, 'Record is returned from findById')
|
|
82
|
-
|
|
83
|
-
if (findByIdResponse.record) {
|
|
84
|
-
this.environment.state[this.dataCollectionKey] = {
|
|
85
|
-
createdRecordId: createResponse.id,
|
|
86
|
-
createdRecord: findByIdResponse.record.fields,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const schemaToCompare = excludeWriteOnlyFieldsFromSchema(createFieldsSchema)
|
|
91
|
-
const sentFieldsToCheck = valueToSchema(config.input.fields, schemaToCompare, {
|
|
92
|
-
skipUnknownFields: true,
|
|
93
|
-
})
|
|
94
|
-
const receivedFieldsToCheck = valueToSchema(findByIdResponse.record.fields, schemaToCompare)
|
|
95
|
-
|
|
96
|
-
const diff = getNotMatchingSubFields(receivedFieldsToCheck, sentFieldsToCheck)
|
|
97
|
-
|
|
98
|
-
await this.assert(() => !diff, 'Returned fields match created fields', {
|
|
99
|
-
difference: diff,
|
|
100
|
-
sentFields: sentFieldsToCheck,
|
|
101
|
-
receivedFields: receivedFieldsToCheck,
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const requiredFields = getRequiredFieldsFromSchema(createFieldsSchema)
|
|
106
|
-
|
|
107
|
-
if (requiredFields.length > 0) {
|
|
108
|
-
const requiredPayload: Record<string, any> = {}
|
|
109
|
-
|
|
110
|
-
requiredFields.forEach((path) => {
|
|
111
|
-
const value = getValueAtLocator(config.input.fields, path)
|
|
112
|
-
if (typeof value !== 'undefined') {
|
|
113
|
-
setValueAtLocator(requiredPayload, path, value)
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
const result = await this.environment.client
|
|
118
|
-
.connection(this.environment.connectionId)
|
|
119
|
-
.dataCollection(this.dataCollectionKey)
|
|
120
|
-
.create({ fields: requiredPayload })
|
|
121
|
-
|
|
122
|
-
await this.assert(() => !!result.id, 'Record can be created with schema-defined required fields only')
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async generateConfig(): Promise<DataCollectionCreateTestConfig> {
|
|
127
|
-
const createFieldsSchema = getDataCollectionCreateFields(this.dataCollection)
|
|
128
|
-
|
|
129
|
-
if (!createFieldsSchema?.properties) {
|
|
130
|
-
throw new Error('No fields schema found for data collection')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const fields = await this.generateFieldsWithLLM(createFieldsSchema)
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
input: {
|
|
137
|
-
fields,
|
|
138
|
-
},
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
protected async fixTestCase({
|
|
143
|
-
config,
|
|
144
|
-
error,
|
|
145
|
-
}: {
|
|
146
|
-
config: DataCollectionCreateTestConfig
|
|
147
|
-
error: any
|
|
148
|
-
}): Promise<DataCollectionCreateTestConfig> {
|
|
149
|
-
const errorData = extractIntegrationAppErrorData(error)
|
|
150
|
-
|
|
151
|
-
const createFieldsSchema = getDataCollectionCreateFields(this.dataCollection)
|
|
152
|
-
|
|
153
|
-
const examples = await this.getExampleRecordsForSchema(createFieldsSchema)
|
|
154
|
-
|
|
155
|
-
const prompt = `I'm trying to create a data record in a data collection with the following fields schema:
|
|
156
|
-
|
|
157
|
-
${JSON.stringify(createFieldsSchema, null, 2)}
|
|
158
|
-
|
|
159
|
-
I tried to create a record with these fields:
|
|
160
|
-
|
|
161
|
-
${JSON.stringify(config.input?.fields, null, 2)}
|
|
162
|
-
|
|
163
|
-
But got this error:
|
|
164
|
-
|
|
165
|
-
${JSON.stringify(errorData)}
|
|
166
|
-
|
|
167
|
-
${this.getExampleRecordsPrompt(examples)}
|
|
168
|
-
|
|
169
|
-
Please analyze the error and provide:
|
|
170
|
-
1. A fixed fields object that would work (as a JSON object)
|
|
171
|
-
2. A short explanation of what was wrong with the original fields
|
|
172
|
-
|
|
173
|
-
Format your response as a JSON object with two fields:
|
|
174
|
-
{
|
|
175
|
-
"explanation": "short explanation of what was wrong and how you fixed it",
|
|
176
|
-
"fields": { ... fixed fields ... }
|
|
177
|
-
}.
|
|
178
|
-
|
|
179
|
-
Only return the JSON object, no other text or wrapping (like \`\`\`json or \`\`\`).`
|
|
180
|
-
|
|
181
|
-
const response = await this.environment.llm.complete({
|
|
182
|
-
prompt,
|
|
183
|
-
maxTokens: 10000,
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
const result = JSON.parse(response.trim())
|
|
187
|
-
console.warn(chalk.bold.yellow('[auto-fix]'), `${this.path}:`, result.explanation)
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
input: {
|
|
191
|
-
fields: result.fields,
|
|
192
|
-
},
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private async generateFieldsWithLLM(schema: any): Promise<Record<string, any>> {
|
|
197
|
-
const examples = await this.getExampleRecordsForSchema(schema)
|
|
198
|
-
|
|
199
|
-
const prompt = `Generate a valid JSON object that matches this JSONSchema. Return only the JSON object, no other text.
|
|
200
|
-
|
|
201
|
-
${this.getExampleRecordsPrompt(examples)}
|
|
202
|
-
|
|
203
|
-
JSONSchema:
|
|
204
|
-
${JSON.stringify(schema, null, 2)}.
|
|
205
|
-
|
|
206
|
-
Do not populate fields with null or empty strings, unless the schema explicitly requires it.
|
|
207
|
-
|
|
208
|
-
Only return the JSON object, no other text or wrapping (like \`\`\`json or \`\`\`).`
|
|
209
|
-
|
|
210
|
-
const response = await this.environment.llm.complete({
|
|
211
|
-
prompt,
|
|
212
|
-
maxTokens: 10000,
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
const fields = JSON.parse(response.trim())
|
|
216
|
-
return fields
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
private getExampleRecordsPrompt(examples: Record<string, ExampleRecord[]>): string {
|
|
220
|
-
const examplesText = Object.entries(examples)
|
|
221
|
-
.map(([key, records]) => {
|
|
222
|
-
return `Example records from collection "${key}":\n${JSON.stringify(records, null, 2)}`
|
|
223
|
-
})
|
|
224
|
-
.join('\n\n')
|
|
225
|
-
|
|
226
|
-
return `When a field has a "referenceCollection" property, its value should be an ID of a record from that collection. Here are some example records you can use as references:
|
|
227
|
-
|
|
228
|
-
${examplesText}.
|
|
229
|
-
|
|
230
|
-
If you don't have an example for a given collection, leave the field empty instead of coming up with a fake record id.`
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
private async findReferenceCollections(schema: any): Promise<ReferenceCollection[]> {
|
|
234
|
-
const references = new Set<string>()
|
|
235
|
-
const result: ReferenceCollection[] = []
|
|
236
|
-
|
|
237
|
-
walkSchema(schema, (obj) => {
|
|
238
|
-
if (obj.referenceCollection) {
|
|
239
|
-
const key = obj.referenceCollection.key
|
|
240
|
-
const parameters = obj.referenceCollection.parameters
|
|
241
|
-
const refKey = `${key}:${JSON.stringify(parameters || {})}`
|
|
242
|
-
|
|
243
|
-
if (!references.has(refKey)) {
|
|
244
|
-
references.add(refKey)
|
|
245
|
-
result.push({ key, parameters })
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return obj
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
return result
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private async fetchExampleRecords(collection: ReferenceCollection): Promise<ExampleRecord[]> {
|
|
255
|
-
const response = await this.environment.client
|
|
256
|
-
.connection(this.environment.connectionId)
|
|
257
|
-
.dataCollection(collection.key)
|
|
258
|
-
.list({
|
|
259
|
-
parameters: collection.parameters,
|
|
260
|
-
})
|
|
261
|
-
return response.records.map((r) => ({ id: r.id, fields: r.fields }))
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
private async getExampleRecordsForSchema(schema: any): Promise<Record<string, ExampleRecord[]>> {
|
|
265
|
-
const collections = await this.findReferenceCollections(schema)
|
|
266
|
-
const result: Record<string, ExampleRecord[]> = {}
|
|
267
|
-
for (const collection of collections) {
|
|
268
|
-
const locationPath = makeDataLocationPath(collection)
|
|
269
|
-
result[locationPath] = await this.fetchExampleRecords(collection)
|
|
270
|
-
}
|
|
271
|
-
return result
|
|
272
|
-
}
|
|
273
|
-
}
|