@amodalai/amodal 0.3.27 → 0.3.28
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/CHANGELOG.md +20 -0
- package/dist/src/commands/dev.d.ts.map +1 -1
- package/dist/src/commands/dev.js +28 -11
- package/dist/src/commands/dev.js.map +1 -1
- package/dist/src/commands/eval.d.ts.map +1 -1
- package/dist/src/commands/eval.js +4 -2
- package/dist/src/commands/eval.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/commands/dev.ts +31 -11
- package/src/commands/eval.ts +4 -2
- package/src/e2e-commands.test.ts +9 -291
- package/src/e2e-subprocess.test.ts +153 -0
- package/dist/src/fixtures/incident-response.d.ts +0 -92
- package/dist/src/fixtures/incident-response.d.ts.map +0 -1
- package/dist/src/fixtures/incident-response.js +0 -209
- package/dist/src/fixtures/incident-response.js.map +0 -1
- package/dist/src/shared/find-free-port.d.ts +0 -21
- package/dist/src/shared/find-free-port.d.ts.map +0 -1
- package/dist/src/shared/find-free-port.js +0 -62
- package/dist/src/shared/find-free-port.js.map +0 -1
- package/src/e2e-automations.test.ts +0 -305
- package/src/e2e-incident-response.test.ts +0 -345
- package/src/e2e-plugin-connections.test.ts +0 -407
- package/src/e2e-plugins.test.ts +0 -491
- package/src/e2e.test.ts +0 -493
- package/src/fixtures/incident-response.ts +0 -233
- package/src/shared/find-free-port.ts +0 -67
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2026 Amodal Labs, Inc.
|
|
4
|
-
* SPDX-License-Identifier: MIT
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* End-to-end test: install a plugin connection → build → deploy (dry-run)
|
|
9
|
-
* and verify the connection appears in the snapshot.
|
|
10
|
-
*
|
|
11
|
-
* Uses real CLI functions (runInstallPkg, runBuild, runDeploy) against
|
|
12
|
-
* a real temp directory. The npm install step is simulated by writing
|
|
13
|
-
* package files directly (since we can't hit a real registry in tests),
|
|
14
|
-
* but the lock file, symlink, build, and deploy steps are all real.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import {describe, it, expect, beforeEach, afterEach} from 'vitest';
|
|
18
|
-
import {
|
|
19
|
-
mkdirSync,
|
|
20
|
-
writeFileSync,
|
|
21
|
-
readFileSync,
|
|
22
|
-
existsSync,
|
|
23
|
-
rmSync,
|
|
24
|
-
mkdtempSync,
|
|
25
|
-
} from 'node:fs';
|
|
26
|
-
import {join} from 'node:path';
|
|
27
|
-
import {tmpdir} from 'node:os';
|
|
28
|
-
|
|
29
|
-
import {runBuild} from './commands/build.js';
|
|
30
|
-
import {runDeploy} from './commands/deploy.js';
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Helpers
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Create a minimal amodal repo with amodal.json.
|
|
38
|
-
*/
|
|
39
|
-
function createRepo(dir: string): void {
|
|
40
|
-
writeFileSync(
|
|
41
|
-
join(dir, 'amodal.json'),
|
|
42
|
-
JSON.stringify(
|
|
43
|
-
{
|
|
44
|
-
name: 'plugin-e2e-test',
|
|
45
|
-
version: '1.0.0',
|
|
46
|
-
models: {main: {provider: 'anthropic', model: 'claude-sonnet-4-20250514'}},
|
|
47
|
-
},
|
|
48
|
-
null,
|
|
49
|
-
2,
|
|
50
|
-
),
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Simulate what `amodal install connection-<name>` does on disk:
|
|
56
|
-
* 1. Write the package files into amodal_packages/.npm/node_modules/@amodalai/connection-<name>/connections/<name>/
|
|
57
|
-
* 2. Write the lock file entry (keyed by npm name)
|
|
58
|
-
*
|
|
59
|
-
* No symlinks — the resolver reads directly from node_modules.
|
|
60
|
-
*/
|
|
61
|
-
function simulateInstallConnection(
|
|
62
|
-
repoDir: string,
|
|
63
|
-
name: string,
|
|
64
|
-
files: Record<string, string>,
|
|
65
|
-
version = '1.0.0',
|
|
66
|
-
): void {
|
|
67
|
-
const npmName = `@amodalai/connection-${name}`;
|
|
68
|
-
const pkgsRoot = join(repoDir, 'amodal_packages');
|
|
69
|
-
const connDir = join(pkgsRoot, '.npm', 'node_modules', '@amodalai', `connection-${name}`, 'connections', name);
|
|
70
|
-
mkdirSync(connDir, {recursive: true});
|
|
71
|
-
|
|
72
|
-
for (const [fname, content] of Object.entries(files)) {
|
|
73
|
-
writeFileSync(join(connDir, fname), content);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Write / update lock file
|
|
77
|
-
const lockPath = join(repoDir, 'amodal.lock');
|
|
78
|
-
let lock: {lockVersion: number; packages: Record<string, unknown>};
|
|
79
|
-
if (existsSync(lockPath)) {
|
|
80
|
-
lock = JSON.parse(readFileSync(lockPath, 'utf-8')) as typeof lock;
|
|
81
|
-
} else {
|
|
82
|
-
lock = {lockVersion: 2, packages: {}};
|
|
83
|
-
}
|
|
84
|
-
lock.packages[npmName] = {
|
|
85
|
-
version,
|
|
86
|
-
integrity: `sha256-test-${name}`,
|
|
87
|
-
};
|
|
88
|
-
writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
// Fixture data — mirrors real plugin package structure
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
const STRIPE_SPEC = JSON.stringify({
|
|
96
|
-
baseUrl: 'https://api.stripe.com',
|
|
97
|
-
specUrl: 'https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json',
|
|
98
|
-
format: 'openapi',
|
|
99
|
-
auth: {
|
|
100
|
-
type: 'bearer',
|
|
101
|
-
token: 'env:STRIPE_API_KEY',
|
|
102
|
-
header: 'Authorization',
|
|
103
|
-
prefix: 'Bearer',
|
|
104
|
-
},
|
|
105
|
-
sync: {auto: true, frequency: 'daily', notify_drift: true},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const STRIPE_ACCESS = JSON.stringify({
|
|
109
|
-
endpoints: {
|
|
110
|
-
'GET /v1/charges': {returns: ['id', 'amount', 'currency', 'status']},
|
|
111
|
-
'GET /v1/charges/:id': {returns: ['id', 'amount', 'currency', 'status', 'description']},
|
|
112
|
-
'POST /v1/charges': {returns: ['id'], confirm: true, reason: 'Creates a charge'},
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
const STRIPE_SURFACE = [
|
|
117
|
-
'## Included',
|
|
118
|
-
'',
|
|
119
|
-
'### GET /v1/charges',
|
|
120
|
-
'List charges with pagination and filters.',
|
|
121
|
-
'',
|
|
122
|
-
'### GET /v1/charges/:id',
|
|
123
|
-
'Retrieve a specific charge by ID.',
|
|
124
|
-
'',
|
|
125
|
-
'## Excluded',
|
|
126
|
-
'',
|
|
127
|
-
'### POST /v1/charges',
|
|
128
|
-
'Create a new charge (requires confirmation).',
|
|
129
|
-
].join('\n');
|
|
130
|
-
|
|
131
|
-
const STRIPE_ENTITIES = [
|
|
132
|
-
'# Entities',
|
|
133
|
-
'',
|
|
134
|
-
'## charge',
|
|
135
|
-
'A Stripe charge represents a payment.',
|
|
136
|
-
'',
|
|
137
|
-
'## customer',
|
|
138
|
-
'A Stripe customer.',
|
|
139
|
-
].join('\n');
|
|
140
|
-
|
|
141
|
-
const STRIPE_RULES = [
|
|
142
|
-
'# Rules',
|
|
143
|
-
'',
|
|
144
|
-
'- Always include `limit` parameter when listing charges.',
|
|
145
|
-
'- Never expose full card numbers in responses.',
|
|
146
|
-
].join('\n');
|
|
147
|
-
|
|
148
|
-
const SALESFORCE_SPEC = JSON.stringify({
|
|
149
|
-
baseUrl: 'env:SALESFORCE_INSTANCE_URL',
|
|
150
|
-
specUrl: 'https://developer.salesforce.com/docs',
|
|
151
|
-
format: 'openapi',
|
|
152
|
-
auth: {
|
|
153
|
-
type: 'bearer',
|
|
154
|
-
token: 'env:SALESFORCE_ACCESS_TOKEN',
|
|
155
|
-
header: 'Authorization',
|
|
156
|
-
prefix: 'Bearer',
|
|
157
|
-
},
|
|
158
|
-
sync: {auto: true, frequency: 'weekly', notify_drift: true},
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const SALESFORCE_ACCESS = JSON.stringify({
|
|
162
|
-
endpoints: {
|
|
163
|
-
'GET /services/data/v59.0/query': {returns: ['totalSize', 'done', 'records']},
|
|
164
|
-
'GET /services/data/v59.0/sobjects/Lead/:id': {returns: ['Id', 'FirstName', 'LastName', 'Email']},
|
|
165
|
-
},
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// ---------------------------------------------------------------------------
|
|
169
|
-
// Tests
|
|
170
|
-
// ---------------------------------------------------------------------------
|
|
171
|
-
|
|
172
|
-
describe('E2E: plugin connection install → build → deploy', () => {
|
|
173
|
-
let repoDir: string;
|
|
174
|
-
let stderrOutput: string;
|
|
175
|
-
const origWrite = process.stderr.write.bind(process.stderr);
|
|
176
|
-
|
|
177
|
-
beforeEach(() => {
|
|
178
|
-
repoDir = mkdtempSync(join(tmpdir(), 'amodal-e2e-plugin-'));
|
|
179
|
-
stderrOutput = '';
|
|
180
|
-
// Capture stderr to check CLI output
|
|
181
|
-
process.stderr.write = ((chunk: string | Uint8Array) => {
|
|
182
|
-
stderrOutput += String(chunk);
|
|
183
|
-
return true;
|
|
184
|
-
}) as typeof process.stderr.write;
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
afterEach(() => {
|
|
188
|
-
process.stderr.write = origWrite;
|
|
189
|
-
if (repoDir && existsSync(repoDir)) {
|
|
190
|
-
rmSync(repoDir, {recursive: true, force: true});
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('single plugin connection appears in build snapshot', async () => {
|
|
195
|
-
createRepo(repoDir);
|
|
196
|
-
simulateInstallConnection(repoDir, 'stripe', {
|
|
197
|
-
'spec.json': STRIPE_SPEC,
|
|
198
|
-
'access.json': STRIPE_ACCESS,
|
|
199
|
-
'surface.md': STRIPE_SURFACE,
|
|
200
|
-
'entities.md': STRIPE_ENTITIES,
|
|
201
|
-
'rules.md': STRIPE_RULES,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// Run build (same code path as `amodal build`)
|
|
205
|
-
const outputPath = join(repoDir, 'resolved-config.json');
|
|
206
|
-
const code = await runBuild({cwd: repoDir, output: outputPath});
|
|
207
|
-
|
|
208
|
-
expect(code).toBe(0);
|
|
209
|
-
expect(existsSync(outputPath)).toBe(true);
|
|
210
|
-
|
|
211
|
-
// Parse the snapshot and verify the connection is there
|
|
212
|
-
const snapshot = JSON.parse(readFileSync(outputPath, 'utf-8')) as Record<string, unknown>;
|
|
213
|
-
const connections = snapshot['connections'] as Record<string, Record<string, unknown>>;
|
|
214
|
-
|
|
215
|
-
expect(connections['stripe']).toBeDefined();
|
|
216
|
-
|
|
217
|
-
// Verify spec
|
|
218
|
-
const spec = connections['stripe']['spec'] as Record<string, unknown>;
|
|
219
|
-
expect(spec['baseUrl']).toBe('https://api.stripe.com');
|
|
220
|
-
expect((spec['auth'] as Record<string, unknown>)['type']).toBe('bearer');
|
|
221
|
-
expect((spec['auth'] as Record<string, unknown>)['token']).toBe('env:STRIPE_API_KEY');
|
|
222
|
-
|
|
223
|
-
// Verify access
|
|
224
|
-
const access = connections['stripe']['access'] as Record<string, unknown>;
|
|
225
|
-
const endpoints = access['endpoints'] as Record<string, unknown>;
|
|
226
|
-
expect(endpoints['GET /v1/charges']).toBeDefined();
|
|
227
|
-
expect(endpoints['POST /v1/charges']).toBeDefined();
|
|
228
|
-
|
|
229
|
-
// Verify surface
|
|
230
|
-
const surface = connections['stripe']['surface'] as string;
|
|
231
|
-
expect(surface).toContain('GET /v1/charges');
|
|
232
|
-
|
|
233
|
-
// Verify entities and rules
|
|
234
|
-
expect(connections['stripe']['entities']).toContain('charge');
|
|
235
|
-
expect(connections['stripe']['rules']).toContain('limit');
|
|
236
|
-
|
|
237
|
-
// Verify CLI output mentions the connection
|
|
238
|
-
expect(stderrOutput).toContain('Connections: 1');
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('multiple plugin connections appear in build snapshot', async () => {
|
|
242
|
-
createRepo(repoDir);
|
|
243
|
-
simulateInstallConnection(repoDir, 'stripe', {
|
|
244
|
-
'spec.json': STRIPE_SPEC,
|
|
245
|
-
'access.json': STRIPE_ACCESS,
|
|
246
|
-
});
|
|
247
|
-
simulateInstallConnection(repoDir, 'salesforce', {
|
|
248
|
-
'spec.json': SALESFORCE_SPEC,
|
|
249
|
-
'access.json': SALESFORCE_ACCESS,
|
|
250
|
-
}, '2.1.0');
|
|
251
|
-
|
|
252
|
-
const outputPath = join(repoDir, 'resolved-config.json');
|
|
253
|
-
const code = await runBuild({cwd: repoDir, output: outputPath});
|
|
254
|
-
|
|
255
|
-
expect(code).toBe(0);
|
|
256
|
-
|
|
257
|
-
const snapshot = JSON.parse(readFileSync(outputPath, 'utf-8')) as Record<string, unknown>;
|
|
258
|
-
const connections = snapshot['connections'] as Record<string, Record<string, unknown>>;
|
|
259
|
-
|
|
260
|
-
expect(Object.keys(connections)).toHaveLength(2);
|
|
261
|
-
expect(connections['stripe']).toBeDefined();
|
|
262
|
-
expect(connections['salesforce']).toBeDefined();
|
|
263
|
-
|
|
264
|
-
// Verify each has correct baseUrl
|
|
265
|
-
expect((connections['stripe']['spec'] as Record<string, unknown>)['baseUrl']).toBe('https://api.stripe.com');
|
|
266
|
-
expect((connections['salesforce']['spec'] as Record<string, unknown>)['baseUrl']).toBe('env:SALESFORCE_INSTANCE_URL');
|
|
267
|
-
|
|
268
|
-
expect(stderrOutput).toContain('Connections: 2');
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('plugin connections coexist with hand-written connections in snapshot', async () => {
|
|
272
|
-
createRepo(repoDir);
|
|
273
|
-
|
|
274
|
-
// Plugin connection
|
|
275
|
-
simulateInstallConnection(repoDir, 'stripe', {
|
|
276
|
-
'spec.json': STRIPE_SPEC,
|
|
277
|
-
'access.json': STRIPE_ACCESS,
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// Hand-written connection
|
|
281
|
-
const connDir = join(repoDir, 'connections', 'internal-api');
|
|
282
|
-
mkdirSync(connDir, {recursive: true});
|
|
283
|
-
writeFileSync(
|
|
284
|
-
join(connDir, 'spec.json'),
|
|
285
|
-
JSON.stringify({
|
|
286
|
-
baseUrl: 'https://internal.corp/v1',
|
|
287
|
-
specUrl: 'https://internal.corp/openapi.json',
|
|
288
|
-
format: 'openapi',
|
|
289
|
-
auth: {type: 'bearer', token: 'env:INTERNAL_TOKEN'},
|
|
290
|
-
}),
|
|
291
|
-
);
|
|
292
|
-
writeFileSync(
|
|
293
|
-
join(connDir, 'access.json'),
|
|
294
|
-
JSON.stringify({endpoints: {'GET /health': {returns: ['status']}}}),
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
const outputPath = join(repoDir, 'resolved-config.json');
|
|
298
|
-
const code = await runBuild({cwd: repoDir, output: outputPath});
|
|
299
|
-
|
|
300
|
-
expect(code).toBe(0);
|
|
301
|
-
|
|
302
|
-
const snapshot = JSON.parse(readFileSync(outputPath, 'utf-8')) as Record<string, unknown>;
|
|
303
|
-
const connections = snapshot['connections'] as Record<string, Record<string, unknown>>;
|
|
304
|
-
|
|
305
|
-
expect(Object.keys(connections)).toHaveLength(2);
|
|
306
|
-
expect(connections['stripe']).toBeDefined();
|
|
307
|
-
expect(connections['internal-api']).toBeDefined();
|
|
308
|
-
expect((connections['internal-api']['spec'] as Record<string, unknown>)['baseUrl']).toBe('https://internal.corp/v1');
|
|
309
|
-
|
|
310
|
-
expect(stderrOutput).toContain('Connections: 2');
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('deploy dry-run includes plugin connections', async () => {
|
|
314
|
-
createRepo(repoDir);
|
|
315
|
-
simulateInstallConnection(repoDir, 'stripe', {
|
|
316
|
-
'spec.json': STRIPE_SPEC,
|
|
317
|
-
'access.json': STRIPE_ACCESS,
|
|
318
|
-
'surface.md': STRIPE_SURFACE,
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
const code = await runDeploy({cwd: repoDir, dryRun: true, message: 'e2e plugin test'});
|
|
322
|
-
|
|
323
|
-
expect(code).toBe(0);
|
|
324
|
-
expect(stderrOutput).toContain('Connections: 1');
|
|
325
|
-
expect(stderrOutput).toContain('Dry run');
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it('plugin connection with weekly sync frequency builds successfully', async () => {
|
|
329
|
-
createRepo(repoDir);
|
|
330
|
-
simulateInstallConnection(repoDir, 'salesforce', {
|
|
331
|
-
'spec.json': SALESFORCE_SPEC,
|
|
332
|
-
'access.json': SALESFORCE_ACCESS,
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
const outputPath = join(repoDir, 'resolved-config.json');
|
|
336
|
-
const code = await runBuild({cwd: repoDir, output: outputPath});
|
|
337
|
-
|
|
338
|
-
expect(code).toBe(0);
|
|
339
|
-
|
|
340
|
-
const snapshot = JSON.parse(readFileSync(outputPath, 'utf-8')) as Record<string, unknown>;
|
|
341
|
-
const connections = snapshot['connections'] as Record<string, Record<string, unknown>>;
|
|
342
|
-
expect(connections['salesforce']).toBeDefined();
|
|
343
|
-
|
|
344
|
-
const spec = connections['salesforce']['spec'] as Record<string, unknown>;
|
|
345
|
-
const sync = spec['sync'] as Record<string, unknown>;
|
|
346
|
-
expect(sync['frequency']).toBe('weekly');
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
it('plugin connection with rest format builds successfully', async () => {
|
|
350
|
-
createRepo(repoDir);
|
|
351
|
-
simulateInstallConnection(repoDir, 'custom-rest', {
|
|
352
|
-
'spec.json': JSON.stringify({
|
|
353
|
-
baseUrl: 'https://api.custom.com',
|
|
354
|
-
specUrl: 'https://api.custom.com/docs',
|
|
355
|
-
format: 'rest',
|
|
356
|
-
}),
|
|
357
|
-
'access.json': JSON.stringify({
|
|
358
|
-
endpoints: {'GET /data': {returns: ['items']}},
|
|
359
|
-
}),
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
const outputPath = join(repoDir, 'resolved-config.json');
|
|
363
|
-
const code = await runBuild({cwd: repoDir, output: outputPath});
|
|
364
|
-
|
|
365
|
-
expect(code).toBe(0);
|
|
366
|
-
|
|
367
|
-
const snapshot = JSON.parse(readFileSync(outputPath, 'utf-8')) as Record<string, unknown>;
|
|
368
|
-
const connections = snapshot['connections'] as Record<string, Record<string, unknown>>;
|
|
369
|
-
expect(connections['custom-rest']).toBeDefined();
|
|
370
|
-
expect((connections['custom-rest']['spec'] as Record<string, unknown>)['format']).toBe('rest');
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
it('build produces valid snapshot that can be re-parsed', async () => {
|
|
374
|
-
createRepo(repoDir);
|
|
375
|
-
simulateInstallConnection(repoDir, 'stripe', {
|
|
376
|
-
'spec.json': STRIPE_SPEC,
|
|
377
|
-
'access.json': STRIPE_ACCESS,
|
|
378
|
-
'surface.md': STRIPE_SURFACE,
|
|
379
|
-
'entities.md': STRIPE_ENTITIES,
|
|
380
|
-
'rules.md': STRIPE_RULES,
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
const outputPath = join(repoDir, 'resolved-config.json');
|
|
384
|
-
const code = await runBuild({cwd: repoDir, output: outputPath});
|
|
385
|
-
expect(code).toBe(0);
|
|
386
|
-
|
|
387
|
-
// The snapshot should be valid JSON that can be loaded back
|
|
388
|
-
const raw = readFileSync(outputPath, 'utf-8');
|
|
389
|
-
const snapshot = JSON.parse(raw) as Record<string, unknown>;
|
|
390
|
-
|
|
391
|
-
// Verify top-level structure
|
|
392
|
-
expect(snapshot['deployId']).toMatch(/^deploy-[0-9a-f]{7}$/);
|
|
393
|
-
expect(snapshot['createdAt']).toBeDefined();
|
|
394
|
-
expect(snapshot['source']).toBe('cli');
|
|
395
|
-
expect((snapshot['config'] as Record<string, unknown>)['name']).toBe('plugin-e2e-test');
|
|
396
|
-
|
|
397
|
-
// Verify connection round-trips correctly
|
|
398
|
-
const connections = snapshot['connections'] as Record<string, Record<string, unknown>>;
|
|
399
|
-
const stripe = connections['stripe'];
|
|
400
|
-
expect(stripe).toBeDefined();
|
|
401
|
-
expect(typeof stripe['spec']).toBe('object');
|
|
402
|
-
expect(typeof stripe['access']).toBe('object');
|
|
403
|
-
expect(typeof stripe['surface']).toBe('string');
|
|
404
|
-
expect(typeof stripe['entities']).toBe('string');
|
|
405
|
-
expect(typeof stripe['rules']).toBe('string');
|
|
406
|
-
});
|
|
407
|
-
});
|