@postman-cse/onboarding-bootstrap 0.11.0 → 0.12.0

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.
@@ -1,1301 +0,0 @@
1
- import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
- import { stringify as stringifyYaml } from 'yaml';
3
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
-
5
- import {
6
- lintSpecViaCli,
7
- normalizeSpecDocument,
8
- readActionInputs,
9
- runBootstrap,
10
- type CoreLike,
11
- type ExecLike,
12
- type IOLike,
13
- type ResolvedInputs
14
- } from '../src/index.js';
15
-
16
- function createCoreStub(values: Record<string, string> = {}) {
17
- const outputs: Record<string, string> = {};
18
- const secrets: string[] = [];
19
- const warnings: string[] = [];
20
- const infos: string[] = [];
21
- const errors: string[] = [];
22
-
23
- const core: CoreLike = {
24
- error: (message: string) => {
25
- errors.push(message);
26
- },
27
- getInput: (name: string, options?: { required?: boolean }) => {
28
- const value = values[name] ?? '';
29
- if (options?.required && !value) {
30
- throw new Error(`Input required and not supplied: ${name}`);
31
- }
32
- return value;
33
- },
34
- group: async (_name: string, fn: () => Promise<any>) => fn(),
35
- info: (message: string) => {
36
- infos.push(message);
37
- },
38
- setFailed: vi.fn(),
39
- setOutput: (name: string, value: string) => {
40
- outputs[name] = value;
41
- },
42
- setSecret: (secret: string) => {
43
- secrets.push(secret);
44
- },
45
- warning: (message: string) => {
46
- warnings.push(message);
47
- }
48
- };
49
-
50
- return {
51
- core,
52
- errors,
53
- infos,
54
- outputs,
55
- secrets,
56
- warnings
57
- };
58
- }
59
-
60
- function createInputs(overrides: Partial<ResolvedInputs> = {}): ResolvedInputs {
61
- return {
62
- projectName: 'core-payments',
63
- syncExamples: true,
64
- collectionSyncMode: 'refresh',
65
- specSyncMode: 'update',
66
- releaseLabel: undefined,
67
- domain: 'core-banking',
68
- domainCode: 'AF',
69
- requesterEmail: 'owner@example.com',
70
- workspaceAdminUserIds: '101,102',
71
- repoUrl: 'https://github.com/postman-cs/bootstrap-action-test',
72
- specUrl: 'https://example.test/openapi.yaml',
73
- governanceMappingJson: '{"core-banking":"Core Banking"}',
74
- postmanApiKey: 'pmak-test',
75
- postmanAccessToken: 'postman-access-token',
76
- integrationBackend: 'bifrost',
77
- githubRefName: undefined,
78
- githubHeadRef: undefined,
79
- githubRef: undefined,
80
- githubSha: undefined,
81
- ...overrides
82
- };
83
- }
84
-
85
- function createExecStub(stdout = '{"violations":[]}'): ExecLike {
86
- return {
87
- exec: vi.fn().mockResolvedValue(0),
88
- getExecOutput: vi.fn().mockResolvedValue({
89
- exitCode: 0,
90
- stdout,
91
- stderr: ''
92
- })
93
- };
94
- }
95
-
96
- function createIoStub(): IOLike {
97
- return {
98
- which: vi.fn().mockResolvedValue('/usr/local/bin/postman')
99
- };
100
- }
101
-
102
- describe('bootstrap action', () => {
103
- afterEach(() => {
104
- rmSync('.postman', { recursive: true, force: true });
105
- });
106
-
107
- it('marks secrets as early as input resolution', () => {
108
- const { core, secrets } = createCoreStub({
109
- 'project-name': 'core-payments',
110
- 'spec-url': 'https://example.test/openapi.yaml',
111
- 'postman-api-key': 'pmak-test',
112
- 'postman-access-token': 'postman-access-token'
113
- });
114
-
115
- const inputs = readActionInputs(core);
116
-
117
- expect(inputs.postmanApiKey).toBe('pmak-test');
118
- expect(secrets).toEqual([
119
- 'pmak-test',
120
- 'postman-access-token'
121
- ]);
122
- });
123
-
124
- it('runs the bootstrap flow end to end and emits outputs', async () => {
125
- const { core, outputs } = createCoreStub();
126
- const execStub = createExecStub();
127
- const ioStub = createIoStub();
128
- const order: string[] = [];
129
- const postman = {
130
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
131
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
132
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
133
- generateCollection: vi
134
- .fn()
135
- .mockImplementation(async (_specId: string, _projectName: string, prefix: string) => {
136
- order.push(prefix);
137
- if (prefix === '[Baseline]') return 'col-baseline';
138
- if (prefix === '[Smoke]') return 'col-smoke';
139
- return 'col-contract';
140
- }),
141
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
142
- getTeams: vi.fn().mockResolvedValue([]),
143
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
144
- injectTests: vi.fn().mockResolvedValue(undefined),
145
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
146
- tagCollection: vi.fn().mockResolvedValue(undefined),
147
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
148
- updateSpec: vi.fn().mockResolvedValue(undefined),
149
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
150
- };
151
- const internalIntegration = {
152
- assignWorkspaceToGovernanceGroup: vi.fn().mockResolvedValue(undefined),
153
- linkCollectionsToSpecification: vi.fn().mockResolvedValue(undefined),
154
- syncCollection: vi.fn().mockResolvedValue(undefined)
155
- };
156
- const specFetcher = vi.fn<typeof fetch>().mockResolvedValue(
157
- new Response('openapi: 3.1.0', {
158
- status: 200
159
- })
160
- );
161
-
162
- const result = await runBootstrap(createInputs(), {
163
- core,
164
- exec: execStub,
165
- io: ioStub,
166
- internalIntegration,
167
- postman,
168
- specFetcher
169
- });
170
-
171
- expect(execStub.exec).toHaveBeenCalledWith('postman', ['login', '--with-api-key', 'pmak-test']);
172
- expect(internalIntegration.assignWorkspaceToGovernanceGroup).toHaveBeenCalledWith(
173
- 'ws-123',
174
- 'core-banking',
175
- '{"core-banking":"Core Banking"}'
176
- );
177
- expect(postman.inviteRequesterToWorkspace).toHaveBeenCalledWith(
178
- 'ws-123',
179
- 'owner@example.com'
180
- );
181
- expect(postman.addAdminsToWorkspace).toHaveBeenCalledWith('ws-123', '101,102');
182
- expect(order).toEqual(['[Baseline]', '[Smoke]', '[Contract]']);
183
- expect(internalIntegration.linkCollectionsToSpecification).toHaveBeenCalledWith(
184
- 'spec-123',
185
- [
186
- { collectionId: 'col-baseline', syncOptions: { syncExamples: true } },
187
- { collectionId: 'col-smoke', syncOptions: { syncExamples: true } },
188
- { collectionId: 'col-contract', syncOptions: { syncExamples: true } }
189
- ]
190
- );
191
- expect(internalIntegration.syncCollection).toHaveBeenNthCalledWith(
192
- 1,
193
- 'spec-123',
194
- 'col-baseline'
195
- );
196
- expect(internalIntegration.syncCollection).toHaveBeenNthCalledWith(
197
- 2,
198
- 'spec-123',
199
- 'col-smoke'
200
- );
201
- expect(internalIntegration.syncCollection).toHaveBeenNthCalledWith(
202
- 3,
203
- 'spec-123',
204
- 'col-contract'
205
- );
206
- expect(result).toMatchObject({
207
- 'workspace-id': 'ws-123',
208
- 'workspace-name': '[AF] core-payments',
209
- 'spec-id': 'spec-123',
210
- 'baseline-collection-id': 'col-baseline',
211
- 'smoke-collection-id': 'col-smoke',
212
- 'contract-collection-id': 'col-contract'
213
- });
214
- expect(outputs['collections-json']).toBe(
215
- JSON.stringify({
216
- baseline: 'col-baseline',
217
- contract: 'col-contract',
218
- smoke: 'col-smoke'
219
- })
220
- );
221
- expect(outputs['lint-summary-json']).toBe(
222
- JSON.stringify({
223
- errors: 0,
224
- total: 0,
225
- violations: [],
226
- warnings: 0
227
- })
228
- );
229
- });
230
-
231
- it('fails when spec lint returns errors', async () => {
232
- const { core } = createCoreStub();
233
- const execStub = createExecStub(
234
- JSON.stringify({
235
- violations: [
236
- {
237
- issue: 'Missing operationId',
238
- path: '$.paths./payments.get',
239
- severity: 'ERROR'
240
- }
241
- ]
242
- })
243
- );
244
- const ioStub = createIoStub();
245
- const postman = {
246
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
247
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
248
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
249
- generateCollection: vi.fn(),
250
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
251
- getTeams: vi.fn().mockResolvedValue([]),
252
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
253
- injectTests: vi.fn(),
254
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
255
- tagCollection: vi.fn(),
256
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
257
- updateSpec: vi.fn().mockResolvedValue(undefined),
258
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
259
- };
260
-
261
- await expect(
262
- runBootstrap(createInputs(), {
263
- core,
264
- exec: execStub,
265
- io: ioStub,
266
- postman,
267
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
268
- new Response('openapi: 3.1.0', { status: 200 })
269
- )
270
- })
271
- ).rejects.toThrow('Spec lint found 1 errors');
272
-
273
- expect(postman.generateCollection).not.toHaveBeenCalled();
274
- });
275
-
276
- it('restores previous spec content when lint fails after an update', async () => {
277
- const { core } = createCoreStub();
278
- const execStub = createExecStub(
279
- JSON.stringify({
280
- violations: [
281
- {
282
- issue: 'Broken schema',
283
- path: '$.paths./payments.get',
284
- severity: 'ERROR'
285
- }
286
- ]
287
- })
288
- );
289
- const ioStub = createIoStub();
290
- const postman = {
291
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
292
- createWorkspace: vi.fn(),
293
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
294
- generateCollection: vi.fn(),
295
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
296
- getTeams: vi.fn().mockResolvedValue([]),
297
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.0.0\ninfo:\n title: old\n'),
298
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
299
- injectTests: vi.fn(),
300
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
301
- tagCollection: vi.fn(),
302
- uploadSpec: vi.fn(),
303
- updateSpec: vi.fn().mockResolvedValue(undefined)
304
- };
305
-
306
- await expect(
307
- runBootstrap(
308
- createInputs({ workspaceId: 'ws-existing', specId: 'spec-existing' }),
309
- {
310
- core,
311
- exec: execStub,
312
- io: ioStub,
313
- postman,
314
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
315
- new Response('openapi: 3.1.0', { status: 200 })
316
- )
317
- }
318
- )
319
- ).rejects.toThrow('Spec lint found 1 errors');
320
-
321
- expect(postman.getSpecContent).toHaveBeenCalledWith('spec-existing');
322
- expect(postman.updateSpec).toHaveBeenNthCalledWith(
323
- 1,
324
- 'spec-existing',
325
- 'openapi: 3.1.0',
326
- 'ws-existing'
327
- );
328
- expect(postman.updateSpec).toHaveBeenNthCalledWith(
329
- 2,
330
- 'spec-existing',
331
- 'openapi: 3.0.0\ninfo:\n title: old\n',
332
- 'ws-existing'
333
- );
334
- });
335
-
336
- it('validates normalized spec structure before upload or update', async () => {
337
- const { core } = createCoreStub();
338
- const execStub = createExecStub();
339
- const ioStub = createIoStub();
340
- const postman = {
341
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
342
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
343
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
344
- generateCollection: vi.fn(),
345
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
346
- getTeams: vi.fn().mockResolvedValue([]),
347
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0'),
348
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
349
- injectTests: vi.fn(),
350
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
351
- tagCollection: vi.fn(),
352
- uploadSpec: vi.fn(),
353
- updateSpec: vi.fn()
354
- };
355
-
356
- await expect(
357
- runBootstrap(createInputs(), {
358
- core,
359
- exec: execStub,
360
- io: ioStub,
361
- postman,
362
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
363
- new Response('info:\n title: Missing version\n', { status: 200 })
364
- )
365
- })
366
- ).rejects.toThrow('Spec is missing "openapi" or "swagger" version field');
367
-
368
- expect(postman.uploadSpec).not.toHaveBeenCalled();
369
- expect(postman.updateSpec).not.toHaveBeenCalled();
370
- });
371
-
372
- it('reuses existing workspace, spec, and collection ids from explicit inputs', async () => {
373
- const { core, infos, outputs } = createCoreStub();
374
- const execStub = createExecStub();
375
- const ioStub = createIoStub();
376
- const postman = {
377
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
378
- createWorkspace: vi.fn(),
379
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
380
- generateCollection: vi.fn(),
381
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
382
- getTeams: vi.fn().mockResolvedValue([]),
383
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
384
- injectTests: vi.fn().mockResolvedValue(undefined),
385
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
386
- tagCollection: vi.fn().mockResolvedValue(undefined),
387
- uploadSpec: vi.fn(),
388
- updateSpec: vi.fn().mockResolvedValue(undefined),
389
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
390
- };
391
- const result = await runBootstrap(
392
- createInputs({
393
- workspaceId: 'ws-existing',
394
- specId: 'spec-existing',
395
- baselineCollectionId: 'col-baseline-existing',
396
- smokeCollectionId: 'col-smoke-existing',
397
- contractCollectionId: 'col-contract-existing',
398
- collectionSyncMode: 'reuse'
399
- }),
400
- {
401
- core,
402
- exec: execStub,
403
- io: ioStub,
404
- postman,
405
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
406
- new Response('openapi: 3.1.0', { status: 200 })
407
- )
408
- }
409
- );
410
-
411
- expect(postman.createWorkspace).not.toHaveBeenCalled();
412
- expect(postman.uploadSpec).not.toHaveBeenCalled();
413
- expect(postman.generateCollection).not.toHaveBeenCalled();
414
- expect(postman.updateSpec).toHaveBeenCalledWith('spec-existing', 'openapi: 3.1.0', 'ws-existing');
415
- expect(result).toMatchObject({
416
- 'workspace-id': 'ws-existing',
417
- 'spec-id': 'spec-existing',
418
- 'baseline-collection-id': 'col-baseline-existing',
419
- 'smoke-collection-id': 'col-smoke-existing',
420
- 'contract-collection-id': 'col-contract-existing'
421
- });
422
- expect(outputs['collections-json']).toBe(
423
- JSON.stringify({
424
- baseline: 'col-baseline-existing',
425
- contract: 'col-contract-existing',
426
- smoke: 'col-smoke-existing'
427
- })
428
- );
429
- expect(infos).toContain('Using existing workspace: ws-existing');
430
- expect(infos).toContain('Using existing baseline collection: col-baseline-existing');
431
- expect(infos).toContain('Using existing smoke collection: col-smoke-existing');
432
- expect(infos).toContain('Using existing contract collection: col-contract-existing');
433
- });
434
-
435
- it('refresh mode regenerates collections even when ids already exist', async () => {
436
- const { core } = createCoreStub();
437
- const execStub = createExecStub();
438
- const ioStub = createIoStub();
439
- const generatedIds = ['col-baseline-refresh', 'col-smoke-refresh', 'col-contract-refresh'];
440
- const postman = {
441
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
442
- createWorkspace: vi.fn(),
443
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
444
- generateCollection: vi
445
- .fn()
446
- .mockImplementation(async () => generatedIds.shift() || 'col-fallback'),
447
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
448
- getTeams: vi.fn().mockResolvedValue([]),
449
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
450
- injectTests: vi.fn().mockResolvedValue(undefined),
451
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
452
- tagCollection: vi.fn().mockResolvedValue(undefined),
453
- uploadSpec: vi.fn(),
454
- updateSpec: vi.fn().mockResolvedValue(undefined),
455
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
456
- };
457
- const result = await runBootstrap(
458
- createInputs({
459
- workspaceId: 'ws-existing',
460
- specId: 'spec-existing',
461
- baselineCollectionId: 'col-baseline-existing',
462
- smokeCollectionId: 'col-smoke-existing',
463
- contractCollectionId: 'col-contract-existing',
464
- collectionSyncMode: 'refresh',
465
- }),
466
- {
467
- core,
468
- exec: execStub,
469
- io: ioStub,
470
- postman,
471
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
472
- new Response('openapi: 3.1.0', { status: 200 })
473
- )
474
- }
475
- );
476
-
477
- expect(postman.generateCollection).toHaveBeenCalledTimes(3);
478
- expect(result).toMatchObject({
479
- 'baseline-collection-id': 'col-baseline-refresh',
480
- 'smoke-collection-id': 'col-smoke-refresh',
481
- 'contract-collection-id': 'col-contract-refresh'
482
- });
483
- });
484
-
485
- it('version mode reuses the current ref resources.yaml mappings instead of a release manifest', async () => {
486
- const { core } = createCoreStub();
487
- const execStub = createExecStub();
488
- const ioStub = createIoStub();
489
- const postman = {
490
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
491
- createWorkspace: vi.fn(),
492
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
493
- generateCollection: vi.fn(),
494
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
495
- getTeams: vi.fn().mockResolvedValue([]),
496
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
497
- injectTests: vi.fn().mockResolvedValue(undefined),
498
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
499
- tagCollection: vi.fn().mockResolvedValue(undefined),
500
- uploadSpec: vi.fn(),
501
- updateSpec: vi.fn().mockResolvedValue(undefined),
502
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
503
- };
504
- mkdirSync('.postman', { recursive: true });
505
- writeFileSync(
506
- '.postman/resources.yaml',
507
- stringifyYaml({
508
- workspace: { id: 'ws-existing' },
509
- cloudResources: {
510
- specs: { '../index.yaml': 'spec-v111' },
511
- collections: {
512
- '../postman/collections/[Baseline] core-payments release-v1.1.1': 'col-baseline-v111',
513
- '../postman/collections/[Smoke] core-payments release-v1.1.1': 'col-smoke-v111',
514
- '../postman/collections/[Contract] core-payments release-v1.1.1': 'col-contract-v111'
515
- }
516
- }
517
- })
518
- );
519
- const result = await runBootstrap(
520
- createInputs({
521
- workspaceId: 'ws-existing',
522
- collectionSyncMode: 'version',
523
- specSyncMode: 'version',
524
- releaseLabel: 'release/v1.1.1',
525
- githubRefName: 'release/v1.1.1',
526
- githubSha: 'deadbeef'
527
- }),
528
- {
529
- core,
530
- exec: execStub,
531
- io: ioStub,
532
- postman,
533
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
534
- new Response('openapi: 3.1.0', { status: 200 })
535
- )
536
- }
537
- );
538
-
539
- expect(postman.updateSpec).toHaveBeenCalledWith(
540
- 'spec-v111',
541
- 'openapi: 3.1.0',
542
- 'ws-existing'
543
- );
544
- expect(postman.uploadSpec).not.toHaveBeenCalled();
545
- expect(postman.generateCollection).not.toHaveBeenCalled();
546
- expect(result['spec-id']).toBe('spec-v111');
547
- expect(result['baseline-collection-id']).toBe('col-baseline-v111');
548
- expect(result['smoke-collection-id']).toBe('col-smoke-v111');
549
- expect(result['contract-collection-id']).toBe('col-contract-v111');
550
- });
551
-
552
- it('version mode does not persist asset identifiers to repository variables', async () => {
553
- const { core } = createCoreStub();
554
- const execStub = createExecStub();
555
- const ioStub = createIoStub();
556
- const postman = {
557
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
558
- createWorkspace: vi.fn(),
559
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
560
- generateCollection: vi
561
- .fn()
562
- .mockResolvedValueOnce('col-baseline-v111')
563
- .mockResolvedValueOnce('col-smoke-v111')
564
- .mockResolvedValueOnce('col-contract-v111'),
565
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
566
- getTeams: vi.fn().mockResolvedValue([]),
567
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
568
- injectTests: vi.fn().mockResolvedValue(undefined),
569
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
570
- tagCollection: vi.fn().mockResolvedValue(undefined),
571
- uploadSpec: vi.fn().mockResolvedValue('spec-v111'),
572
- updateSpec: vi.fn().mockResolvedValue(undefined),
573
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
574
- };
575
- await runBootstrap(
576
- createInputs({
577
- workspaceId: 'ws-existing',
578
- collectionSyncMode: 'version',
579
- specSyncMode: 'version',
580
- releaseLabel: 'v1.1.1',
581
- githubRefName: 'v1.1.1'
582
- }),
583
- {
584
- core,
585
- exec: execStub,
586
- io: ioStub,
587
- postman,
588
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
589
- new Response('openapi: 3.1.0', { status: 200 })
590
- )
591
- }
592
- );
593
- });
594
-
595
- it("version mode doesn't fall back to singleton current spec uid", async () => {
596
- const { core } = createCoreStub();
597
- const execStub = createExecStub();
598
- const ioStub = createIoStub();
599
- const postman = {
600
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
601
- createWorkspace: vi.fn(),
602
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
603
- generateCollection: vi
604
- .fn()
605
- .mockResolvedValueOnce('col-baseline-v112')
606
- .mockResolvedValueOnce('col-smoke-v112')
607
- .mockResolvedValueOnce('col-contract-v112'),
608
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
609
- getTeams: vi.fn().mockResolvedValue([]),
610
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
611
- injectTests: vi.fn().mockResolvedValue(undefined),
612
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
613
- tagCollection: vi.fn().mockResolvedValue(undefined),
614
- uploadSpec: vi.fn().mockResolvedValue('spec-v112'),
615
- updateSpec: vi.fn().mockResolvedValue(undefined),
616
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
617
- };
618
- const result = await runBootstrap(
619
- createInputs({
620
- workspaceId: 'ws-existing',
621
- collectionSyncMode: 'version',
622
- specSyncMode: 'version',
623
- releaseLabel: 'v1.1.2',
624
- githubRefName: 'v1.1.2'
625
- }),
626
- {
627
- core,
628
- exec: execStub,
629
- io: ioStub,
630
- postman,
631
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
632
- new Response('openapi: 3.1.0', { status: 200 })
633
- )
634
- }
635
- );
636
-
637
- expect(postman.updateSpec).not.toHaveBeenCalledWith(
638
- 'spec-current',
639
- 'openapi: 3.1.0',
640
- 'ws-existing'
641
- );
642
- expect(postman.uploadSpec).toHaveBeenCalledWith(
643
- 'ws-existing',
644
- 'core-payments v1.1.2',
645
- 'openapi: 3.1.0'
646
- );
647
- expect(result['spec-id']).toBe('spec-v112');
648
- });
649
-
650
- it('reuses .postman/resources.yaml for reruns before creating new assets', async () => {
651
- const { core } = createCoreStub();
652
- const execStub = createExecStub();
653
- const ioStub = createIoStub();
654
- const postman = {
655
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
656
- createWorkspace: vi.fn(),
657
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
658
- generateCollection: vi.fn(),
659
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
660
- getTeams: vi.fn().mockResolvedValue([]),
661
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
662
- injectTests: vi.fn().mockResolvedValue(undefined),
663
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
664
- tagCollection: vi.fn().mockResolvedValue(undefined),
665
- uploadSpec: vi.fn(),
666
- updateSpec: vi.fn().mockResolvedValue(undefined),
667
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
668
- };
669
- mkdirSync('.postman', { recursive: true });
670
- writeFileSync(
671
- '.postman/resources.yaml',
672
- stringifyYaml({
673
- workspace: { id: 'ws-from-file' },
674
- cloudResources: {
675
- specs: { '../index.yaml': 'spec-from-file' },
676
- collections: {
677
- '../postman/collections/[Baseline] core-payments': 'col-baseline-from-file',
678
- '../postman/collections/[Smoke] core-payments': 'col-smoke-from-file',
679
- '../postman/collections/[Contract] core-payments': 'col-contract-from-file'
680
- }
681
- }
682
- })
683
- );
684
-
685
- const result = await runBootstrap(createInputs({ collectionSyncMode: 'reuse' }), {
686
- core,
687
- exec: execStub,
688
- io: ioStub,
689
- postman,
690
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
691
- new Response('openapi: 3.1.0', { status: 200 })
692
- )
693
- });
694
-
695
- expect(postman.createWorkspace).not.toHaveBeenCalled();
696
- expect(postman.uploadSpec).not.toHaveBeenCalled();
697
- expect(postman.generateCollection).not.toHaveBeenCalled();
698
- expect(postman.updateSpec).toHaveBeenCalledWith('spec-from-file', 'openapi: 3.1.0', 'ws-from-file');
699
- expect(result).toMatchObject({
700
- 'workspace-id': 'ws-from-file',
701
- 'spec-id': 'spec-from-file',
702
- 'baseline-collection-id': 'col-baseline-from-file',
703
- 'smoke-collection-id': 'col-smoke-from-file',
704
- 'contract-collection-id': 'col-contract-from-file'
705
- });
706
- });
707
-
708
- it('ignores repository-variable asset state when .postman/resources.yaml is absent', async () => {
709
- const previousRepository = process.env.GITHUB_REPOSITORY;
710
- process.env.GITHUB_REPOSITORY = 'postman-cs/bootstrap-action-test';
711
-
712
- try {
713
- const { core } = createCoreStub();
714
- const execStub = createExecStub();
715
- const ioStub = createIoStub();
716
- const postman = {
717
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
718
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-new' }),
719
- findWorkspacesByName: vi.fn().mockResolvedValue([{ id: 'ws-from-vars', name: '[AF] core-payments' }]),
720
- generateCollection: vi
721
- .fn()
722
- .mockImplementation(async (_specId: string, _projectName: string, prefix: string) => {
723
- if (prefix === '[Baseline]') return 'col-baseline';
724
- if (prefix === '[Smoke]') return 'col-smoke';
725
- return 'col-contract';
726
- }),
727
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
728
- getTeams: vi.fn().mockResolvedValue([]),
729
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue('https://github.com/postman-cs/different-repo'),
730
- injectTests: vi.fn().mockResolvedValue(undefined),
731
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
732
- tagCollection: vi.fn().mockResolvedValue(undefined),
733
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
734
- updateSpec: vi.fn().mockResolvedValue(undefined),
735
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
736
- };
737
- const result = await runBootstrap(createInputs(), {
738
- core,
739
- exec: execStub,
740
- io: ioStub,
741
- postman,
742
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
743
- new Response('openapi: 3.1.0', { status: 200 })
744
- )
745
- });
746
-
747
- expect(postman.createWorkspace).toHaveBeenCalled();
748
- expect(result['workspace-id']).toBe('ws-new');
749
- } finally {
750
- if (previousRepository === undefined) {
751
- delete process.env.GITHUB_REPOSITORY;
752
- } else {
753
- process.env.GITHUB_REPOSITORY = previousRepository;
754
- }
755
- }
756
- });
757
-
758
- it('skips governance assignment when postman-access-token is absent', async () => {
759
- const { core, warnings } = createCoreStub();
760
- const execStub = createExecStub();
761
- const ioStub = createIoStub();
762
- const postman = {
763
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
764
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
765
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
766
- generateCollection: vi.fn().mockResolvedValue('col-id'),
767
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
768
- getTeams: vi.fn().mockResolvedValue([]),
769
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
770
- injectTests: vi.fn().mockResolvedValue(undefined),
771
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
772
- tagCollection: vi.fn().mockResolvedValue(undefined),
773
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
774
- updateSpec: vi.fn().mockResolvedValue(undefined),
775
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
776
- };
777
-
778
- const result = await runBootstrap(
779
- createInputs({ postmanAccessToken: undefined }),
780
- {
781
- core,
782
- exec: execStub,
783
- io: ioStub,
784
- postman,
785
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
786
- new Response('openapi: 3.1.0', { status: 200 })
787
- )
788
- }
789
- );
790
-
791
- expect(result['workspace-id']).toBe('ws-123');
792
- expect(postman.createWorkspace).toHaveBeenCalled();
793
- expect(
794
- warnings.some((warning) =>
795
- warning.includes('Skipping cloud spec-to-collection linking and sync because postman-access-token is not configured')
796
- )
797
- ).toBe(true);
798
- });
799
-
800
- it('passes syncExamples=false when configured', async () => {
801
- const { core } = createCoreStub();
802
- const execStub = createExecStub();
803
- const ioStub = createIoStub();
804
- const postman = {
805
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
806
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
807
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
808
- generateCollection: vi
809
- .fn()
810
- .mockResolvedValueOnce('col-baseline')
811
- .mockResolvedValueOnce('col-smoke')
812
- .mockResolvedValueOnce('col-contract'),
813
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
814
- getTeams: vi.fn().mockResolvedValue([]),
815
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
816
- injectTests: vi.fn().mockResolvedValue(undefined),
817
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
818
- tagCollection: vi.fn().mockResolvedValue(undefined),
819
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
820
- updateSpec: vi.fn().mockResolvedValue(undefined),
821
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
822
- };
823
- const internalIntegration = {
824
- assignWorkspaceToGovernanceGroup: vi.fn().mockResolvedValue(undefined),
825
- linkCollectionsToSpecification: vi.fn().mockResolvedValue(undefined),
826
- syncCollection: vi.fn().mockResolvedValue(undefined)
827
- };
828
-
829
- await runBootstrap(
830
- createInputs({ syncExamples: false }),
831
- {
832
- core,
833
- exec: execStub,
834
- io: ioStub,
835
- internalIntegration,
836
- postman,
837
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
838
- new Response('openapi: 3.1.0', { status: 200 })
839
- )
840
- }
841
- );
842
-
843
- expect(internalIntegration.linkCollectionsToSpecification).toHaveBeenCalledWith(
844
- 'spec-123',
845
- [
846
- { collectionId: 'col-baseline', syncOptions: { syncExamples: false } },
847
- { collectionId: 'col-smoke', syncOptions: { syncExamples: false } },
848
- { collectionId: 'col-contract', syncOptions: { syncExamples: false } }
849
- ]
850
- );
851
- });
852
-
853
- it('runs without any GitHub dependency', async () => {
854
- const { core } = createCoreStub();
855
- const execStub = createExecStub();
856
- const ioStub = createIoStub();
857
- const postman = {
858
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
859
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
860
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
861
- generateCollection: vi.fn().mockResolvedValue('col-id'),
862
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
863
- getTeams: vi.fn().mockResolvedValue([]),
864
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
865
- injectTests: vi.fn().mockResolvedValue(undefined),
866
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
867
- tagCollection: vi.fn().mockResolvedValue(undefined),
868
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
869
- updateSpec: vi.fn().mockResolvedValue(undefined),
870
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
871
- };
872
-
873
- const result = await runBootstrap(
874
- createInputs(),
875
- {
876
- core,
877
- exec: execStub,
878
- io: ioStub,
879
- postman,
880
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
881
- new Response('openapi: 3.1.0', { status: 200 })
882
- )
883
- }
884
- );
885
-
886
- expect(result['workspace-id']).toBe('ws-123');
887
- expect(result['spec-id']).toBe('spec-123');
888
- });
889
-
890
- it('version mode reuses current resources.yaml collections on the checked-out ref', async () => {
891
- const { core, infos } = createCoreStub();
892
- const execStub = createExecStub();
893
- const ioStub = createIoStub();
894
- const postman = {
895
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
896
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
897
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
898
- generateCollection: vi
899
- .fn()
900
- .mockImplementation(async (_specId: string, _projectName: string, prefix: string) => {
901
- if (prefix === '[Baseline]') return 'col-baseline-v2';
902
- if (prefix === '[Smoke]') return 'col-smoke-v2';
903
- return 'col-contract-v2';
904
- }),
905
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
906
- getTeams: vi.fn().mockResolvedValue([]),
907
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
908
- injectTests: vi.fn().mockResolvedValue(undefined),
909
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
910
- tagCollection: vi.fn().mockResolvedValue(undefined),
911
- uploadSpec: vi.fn().mockResolvedValue('spec-v2'),
912
- updateSpec: vi.fn().mockResolvedValue(undefined),
913
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
914
- };
915
- const resources = {
916
- workspace: { id: 'ws-current' },
917
- cloudResources: {
918
- collections: {
919
- '../postman/collections/[Baseline] core-payments': 'col-baseline-current',
920
- '../postman/collections/[Smoke] core-payments': 'col-smoke-current',
921
- '../postman/collections/[Contract] core-payments': 'col-contract-current'
922
- }
923
- }
924
- };
925
- mkdirSync('.postman', { recursive: true });
926
- writeFileSync('.postman/resources.yaml', stringifyYaml(resources));
927
-
928
- const result = await runBootstrap(
929
- createInputs({
930
- collectionSyncMode: 'version',
931
- specSyncMode: 'version',
932
- releaseLabel: 'v2.0.0'
933
- }),
934
- {
935
- core,
936
- exec: execStub,
937
- io: ioStub,
938
- postman,
939
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
940
- new Response('openapi: 3.1.0', { status: 200 })
941
- )
942
- }
943
- );
944
-
945
- expect(postman.generateCollection).not.toHaveBeenCalled();
946
- expect(result['baseline-collection-id']).toBe('col-baseline-current');
947
- expect(result['smoke-collection-id']).toBe('col-smoke-current');
948
- expect(result['contract-collection-id']).toBe('col-contract-current');
949
- expect(infos).toContain('Resolved baseline-collection-id from .postman/resources.yaml');
950
- expect(infos).toContain('Resolved smoke-collection-id from .postman/resources.yaml');
951
- expect(infos).toContain('Resolved contract-collection-id from .postman/resources.yaml');
952
- });
953
-
954
- it('versioned runs do not emit releases-json or write releases.yaml', async () => {
955
- const { core } = createCoreStub();
956
- const execStub = createExecStub();
957
- const ioStub = createIoStub();
958
- const postman = {
959
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
960
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
961
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
962
- generateCollection: vi
963
- .fn()
964
- .mockImplementation(async (_specId: string, _projectName: string, prefix: string) => {
965
- if (prefix === '[Baseline]') return 'col-baseline-v2';
966
- if (prefix === '[Smoke]') return 'col-smoke-v2';
967
- return 'col-contract-v2';
968
- }),
969
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
970
- getTeams: vi.fn().mockResolvedValue([]),
971
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
972
- injectTests: vi.fn().mockResolvedValue(undefined),
973
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
974
- tagCollection: vi.fn().mockResolvedValue(undefined),
975
- uploadSpec: vi.fn().mockResolvedValue('spec-v2'),
976
- updateSpec: vi.fn().mockResolvedValue(undefined),
977
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
978
- };
979
-
980
- const result = await runBootstrap(
981
- createInputs({
982
- collectionSyncMode: 'version',
983
- specSyncMode: 'version',
984
- releaseLabel: 'v2.0.0'
985
- }),
986
- {
987
- core,
988
- exec: execStub,
989
- io: ioStub,
990
- postman,
991
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
992
- new Response('openapi: 3.1.0', { status: 200 })
993
- )
994
- }
995
- );
996
-
997
- expect('releases-json' in result).toBe(false);
998
- expect(existsSync('.postman/releases.yaml')).toBe(false);
999
- });
1000
-
1001
- it('emits warnings for lint violations but does not fail', async () => {
1002
- const { core, warnings } = createCoreStub();
1003
- const execStub = createExecStub(
1004
- JSON.stringify({
1005
- violations: [
1006
- { issue: 'Missing description', path: '$.paths./payments.get', severity: 'WARNING' }
1007
- ]
1008
- })
1009
- );
1010
- const ioStub = createIoStub();
1011
- const postman = {
1012
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
1013
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
1014
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
1015
- generateCollection: vi.fn().mockResolvedValue('col-id'),
1016
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
1017
- getTeams: vi.fn().mockResolvedValue([]),
1018
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
1019
- injectTests: vi.fn().mockResolvedValue(undefined),
1020
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
1021
- tagCollection: vi.fn().mockResolvedValue(undefined),
1022
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
1023
- updateSpec: vi.fn().mockResolvedValue(undefined),
1024
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
1025
- };
1026
-
1027
- const result = await runBootstrap(createInputs(), {
1028
- core,
1029
- exec: execStub,
1030
- io: ioStub,
1031
- postman,
1032
- specFetcher: vi.fn<typeof fetch>().mockResolvedValue(
1033
- new Response('openapi: 3.1.0', { status: 200 })
1034
- )
1035
- });
1036
-
1037
- expect(result['workspace-id']).toBe('ws-123');
1038
- expect(warnings.some((w) => w.includes('Missing description'))).toBe(true);
1039
- const lintSummary = JSON.parse(result['lint-summary-json']);
1040
- expect(lintSummary.warnings).toBe(1);
1041
- expect(lintSummary.errors).toBe(0);
1042
- });
1043
-
1044
- it('normalizeSpecDocument adds summary from operationId or METHOD+path', () => {
1045
- const warn = vi.fn();
1046
- const withId = normalizeSpecDocument(
1047
- JSON.stringify({
1048
- openapi: '3.0.0',
1049
- info: { title: 'T', version: '1' },
1050
- paths: { '/pets': { get: { operationId: 'listPets' } } }
1051
- }),
1052
- warn
1053
- );
1054
- expect(JSON.parse(withId).paths['/pets'].get.summary).toBe('listPets');
1055
- expect(warn).toHaveBeenCalledWith(expect.stringContaining('operationId'));
1056
-
1057
- const warn2 = vi.fn();
1058
- const bare = normalizeSpecDocument(
1059
- JSON.stringify({
1060
- openapi: '3.0.0',
1061
- info: { title: 'T', version: '1' },
1062
- paths: { '/x': { post: {} } }
1063
- }),
1064
- warn2
1065
- );
1066
- expect(JSON.parse(bare).paths['/x'].post.summary).toBe('POST /x');
1067
- expect(warn2).toHaveBeenCalledWith(expect.stringContaining('method + path'));
1068
- });
1069
-
1070
- it('normalizeSpecDocument truncates long summaries', () => {
1071
- const long = 'x'.repeat(250);
1072
- const warn = vi.fn();
1073
- const out = normalizeSpecDocument(
1074
- JSON.stringify({
1075
- openapi: '3.0.0',
1076
- info: { title: 'T', version: '1' },
1077
- paths: { '/a': { get: { summary: long } } }
1078
- }),
1079
- warn
1080
- );
1081
- const s = JSON.parse(out).paths['/a'].get.summary as string;
1082
- expect(s.length).toBe(200);
1083
- expect(s.endsWith('…')).toBe(true);
1084
- expect(warn).toHaveBeenCalledWith(expect.stringContaining('truncated'));
1085
- });
1086
- });
1087
-
1088
- describe('lintSpecViaCli', () => {
1089
- it('parses warning and error counts from postman cli json output', async () => {
1090
- const summary = await lintSpecViaCli(
1091
- {
1092
- exec: createExecStub(
1093
- JSON.stringify({
1094
- violations: [
1095
- { severity: 'ERROR', issue: 'broken' },
1096
- { severity: 'WARNING', issue: 'warn' }
1097
- ]
1098
- })
1099
- )
1100
- },
1101
- 'ws-123',
1102
- 'spec-123'
1103
- );
1104
-
1105
- expect(summary).toEqual({
1106
- errors: 1,
1107
- violations: [
1108
- { severity: 'ERROR', issue: 'broken' },
1109
- { severity: 'WARNING', issue: 'warn' }
1110
- ],
1111
- warnings: 1
1112
- });
1113
- });
1114
- });
1115
-
1116
- it('fails with team list when org-mode detected and no workspace-team-id', async () => {
1117
- const { core } = createCoreStub();
1118
- const execStub = createExecStub();
1119
- const ioStub = createIoStub();
1120
- const postman = {
1121
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
1122
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
1123
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
1124
- generateCollection: vi.fn().mockResolvedValue('col-1'),
1125
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('13347347'),
1126
- getTeams: vi.fn().mockResolvedValue([
1127
- { id: 132109, name: 'Field Services', handle: 'fs', organizationId: 13347347 },
1128
- { id: 132118, name: 'Customer Ed', handle: 'ce', organizationId: 13347347 }
1129
- ]),
1130
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
1131
- injectTests: vi.fn().mockResolvedValue(undefined),
1132
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
1133
- tagCollection: vi.fn().mockResolvedValue(undefined),
1134
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
1135
- updateSpec: vi.fn().mockResolvedValue(undefined),
1136
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
1137
- };
1138
- const specFetcher = vi.fn<typeof fetch>().mockResolvedValue(
1139
- new Response('openapi: 3.1.0', { status: 200 })
1140
- );
1141
-
1142
- await expect(
1143
- runBootstrap(createInputs(), {
1144
- core,
1145
- exec: execStub,
1146
- io: ioStub,
1147
- postman,
1148
- specFetcher
1149
- })
1150
- ).rejects.toThrow('Org-mode account detected');
1151
-
1152
- expect(postman.createWorkspace).not.toHaveBeenCalled();
1153
- });
1154
-
1155
- it('passes workspace-team-id to createWorkspace for org-mode accounts', async () => {
1156
- const { core, outputs } = createCoreStub();
1157
- const execStub = createExecStub();
1158
- const ioStub = createIoStub();
1159
- const postman = {
1160
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
1161
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-org' }),
1162
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
1163
- generateCollection: vi.fn().mockResolvedValue('col-1'),
1164
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('13347347'),
1165
- getTeams: vi.fn().mockResolvedValue([]),
1166
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
1167
- injectTests: vi.fn().mockResolvedValue(undefined),
1168
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
1169
- tagCollection: vi.fn().mockResolvedValue(undefined),
1170
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
1171
- updateSpec: vi.fn().mockResolvedValue(undefined),
1172
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
1173
- };
1174
- const specFetcher = vi.fn<typeof fetch>().mockResolvedValue(
1175
- new Response('openapi: 3.1.0', { status: 200 })
1176
- );
1177
-
1178
- await runBootstrap(createInputs({ workspaceTeamId: '132319' }), {
1179
- core,
1180
- exec: execStub,
1181
- io: ioStub,
1182
- postman,
1183
- specFetcher
1184
- });
1185
-
1186
- expect(postman.createWorkspace).toHaveBeenCalledWith(
1187
- expect.any(String),
1188
- expect.any(String),
1189
- 132319
1190
- );
1191
- expect(outputs['workspace-id']).toBe('ws-org');
1192
- });
1193
-
1194
- it('skips org-mode detection when workspace-id is already provided', async () => {
1195
- const { core, outputs } = createCoreStub();
1196
- const execStub = createExecStub();
1197
- const ioStub = createIoStub();
1198
- const postman = {
1199
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
1200
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
1201
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
1202
- generateCollection: vi.fn().mockResolvedValue('col-1'),
1203
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('13347347'),
1204
- getTeams: vi.fn().mockResolvedValue([
1205
- { id: 132109, name: 'FS', handle: 'fs', organizationId: 13347347 },
1206
- { id: 132118, name: 'CE', handle: 'ce', organizationId: 13347347 }
1207
- ]),
1208
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
1209
- injectTests: vi.fn().mockResolvedValue(undefined),
1210
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
1211
- tagCollection: vi.fn().mockResolvedValue(undefined),
1212
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
1213
- updateSpec: vi.fn().mockResolvedValue(undefined),
1214
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
1215
- };
1216
- const specFetcher = vi.fn<typeof fetch>().mockResolvedValue(
1217
- new Response('openapi: 3.1.0', { status: 200 })
1218
- );
1219
-
1220
- await runBootstrap(createInputs({ workspaceId: 'ws-existing' }), {
1221
- core,
1222
- exec: execStub,
1223
- io: ioStub,
1224
- postman,
1225
- specFetcher
1226
- });
1227
-
1228
- expect(postman.createWorkspace).not.toHaveBeenCalled();
1229
- expect(outputs['workspace-id']).toBe('ws-existing');
1230
- });
1231
-
1232
- it('warns and proceeds when getTeams fails', async () => {
1233
- const { core } = createCoreStub();
1234
- const warnings: string[] = [];
1235
- core.warning = vi.fn((msg: string) => warnings.push(msg));
1236
- const execStub = createExecStub();
1237
- const ioStub = createIoStub();
1238
- const postman = {
1239
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
1240
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
1241
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
1242
- generateCollection: vi.fn().mockResolvedValue('col-1'),
1243
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
1244
- getTeams: vi.fn().mockRejectedValue(new Error('Network error')),
1245
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
1246
- injectTests: vi.fn().mockResolvedValue(undefined),
1247
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
1248
- tagCollection: vi.fn().mockResolvedValue(undefined),
1249
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
1250
- updateSpec: vi.fn().mockResolvedValue(undefined),
1251
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
1252
- };
1253
- const specFetcher = vi.fn<typeof fetch>().mockResolvedValue(
1254
- new Response('openapi: 3.1.0', { status: 200 })
1255
- );
1256
-
1257
- await runBootstrap(createInputs(), {
1258
- core,
1259
- exec: execStub,
1260
- io: ioStub,
1261
- postman,
1262
- specFetcher
1263
- });
1264
-
1265
- expect(warnings.some(w => w.includes('Could not check for org-mode'))).toBe(true);
1266
- expect(postman.createWorkspace).toHaveBeenCalled();
1267
- });
1268
-
1269
- it('throws when workspace-team-id is non-numeric', async () => {
1270
- const { core } = createCoreStub();
1271
- const execStub = createExecStub();
1272
- const ioStub = createIoStub();
1273
- const postman = {
1274
- addAdminsToWorkspace: vi.fn().mockResolvedValue(undefined),
1275
- createWorkspace: vi.fn().mockResolvedValue({ id: 'ws-123' }),
1276
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
1277
- generateCollection: vi.fn().mockResolvedValue('col-1'),
1278
- getAutoDerivedTeamId: vi.fn().mockResolvedValue('12345'),
1279
- getTeams: vi.fn().mockResolvedValue([]),
1280
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(null),
1281
- injectTests: vi.fn().mockResolvedValue(undefined),
1282
- inviteRequesterToWorkspace: vi.fn().mockResolvedValue(undefined),
1283
- tagCollection: vi.fn().mockResolvedValue(undefined),
1284
- uploadSpec: vi.fn().mockResolvedValue('spec-123'),
1285
- updateSpec: vi.fn().mockResolvedValue(undefined),
1286
- getSpecContent: vi.fn().mockResolvedValue('openapi: 3.1.0')
1287
- };
1288
- const specFetcher = vi.fn<typeof fetch>().mockResolvedValue(
1289
- new Response('openapi: 3.1.0', { status: 200 })
1290
- );
1291
-
1292
- await expect(
1293
- runBootstrap(createInputs({ workspaceTeamId: 'not-a-number' }), {
1294
- core,
1295
- exec: execStub,
1296
- io: ioStub,
1297
- postman,
1298
- specFetcher
1299
- })
1300
- ).rejects.toThrow('workspace-team-id must be a numeric sub-team ID');
1301
- });