@postman-cse/onboarding-bootstrap 0.9.1 → 0.11.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.
package/tests/cli.test.ts CHANGED
@@ -14,6 +14,14 @@ describe('parseCliArgs', () => {
14
14
  'pmak-test',
15
15
  '--workspace-admin-user-ids',
16
16
  '101,102',
17
+ '--sync-examples',
18
+ 'false',
19
+ '--collection-sync-mode',
20
+ 'version',
21
+ '--spec-sync-mode',
22
+ 'version',
23
+ '--release-label',
24
+ 'v1.2.3',
17
25
  '--team-id',
18
26
  '12345',
19
27
  '--repo-url',
@@ -30,6 +38,10 @@ describe('parseCliArgs', () => {
30
38
  expect(config.inputEnv.INPUT_SPEC_URL).toBe('https://example.test/openapi.yaml');
31
39
  expect(config.inputEnv.INPUT_POSTMAN_API_KEY).toBe('pmak-test');
32
40
  expect(config.inputEnv.INPUT_WORKSPACE_ADMIN_USER_IDS).toBe('101,102');
41
+ expect(config.inputEnv.INPUT_SYNC_EXAMPLES).toBe('false');
42
+ expect(config.inputEnv.INPUT_COLLECTION_SYNC_MODE).toBe('version');
43
+ expect(config.inputEnv.INPUT_SPEC_SYNC_MODE).toBe('version');
44
+ expect(config.inputEnv.INPUT_RELEASE_LABEL).toBe('v1.2.3');
33
45
  expect(config.inputEnv.INPUT_TEAM_ID).toBe('12345');
34
46
  expect(config.inputEnv.INPUT_REPO_URL).toBe('https://github.com/postman-cs/postman-bootstrap-action');
35
47
  expect(config.resultJsonPath).toBe('tmp/result.json');
@@ -49,7 +61,7 @@ describe('toDotenv', () => {
49
61
  'contract-collection-id': 'col-contract',
50
62
  'collections-json': '{"baseline":"col-baseline"}',
51
63
  'lint-summary-json': '{"errors":0}'
52
- });
64
+ } as any);
53
65
 
54
66
  expect(dotenv).toContain('POSTMAN_BOOTSTRAP_WORKSPACE_ID="ws-123"');
55
67
  expect(dotenv).toContain('POSTMAN_BOOTSTRAP_SPEC_ID="spec-123"');
@@ -104,7 +116,7 @@ describe('runCli', () => {
104
116
  'contract-collection-id': 'col-contract',
105
117
  'collections-json': '{"baseline":"col-baseline","smoke":"col-smoke","contract":"col-contract"}',
106
118
  'lint-summary-json': '{"errors":0,"total":0,"violations":[],"warnings":0}'
107
- };
119
+ } as any;
108
120
  },
109
121
  writeStdout: (chunk) => {
110
122
  stdoutChunks.push(chunk);
@@ -124,21 +136,18 @@ describe('runCli', () => {
124
136
  });
125
137
 
126
138
  describe('createCliDependencies', () => {
127
- it('creates github and internal integration dependencies under token conditions', () => {
139
+ it('creates internal integration dependencies under token conditions', () => {
128
140
  const inputs = resolveInputs({
129
141
  INPUT_PROJECT_NAME: 'core-payments',
130
142
  INPUT_SPEC_URL: 'https://example.test/openapi.yaml',
131
143
  INPUT_POSTMAN_API_KEY: 'pmak-test',
132
144
  INPUT_POSTMAN_ACCESS_TOKEN: 'pat-test',
133
- INPUT_GITHUB_TOKEN: 'gh-token',
134
- INPUT_GH_FALLBACK_TOKEN: 'gh-fallback',
135
145
  INPUT_REPO_URL: 'https://github.com/postman-cs/postman-bootstrap-action',
136
146
  INPUT_TEAM_ID: '12345'
137
147
  });
138
148
 
139
149
  const dependencies = createCliDependencies(inputs);
140
150
 
141
- expect(dependencies.github).toBeDefined();
142
151
  expect(dependencies.internalIntegration).toBeDefined();
143
152
  });
144
153
  });
@@ -41,6 +41,34 @@ describe('open-alpha action contract', () => {
41
41
  expect(resolveInputs({}).integrationBackend).toBe('bifrost');
42
42
  });
43
43
 
44
+ it('defaults lifecycle controls in contract, manifest, and runtime', () => {
45
+ expect(openAlphaActionContract.inputs['sync-examples'].default).toBe('true');
46
+ expect(openAlphaActionContract.inputs['sync-examples'].allowedValues).toEqual([
47
+ 'true',
48
+ 'false'
49
+ ]);
50
+ expect(actionManifest.inputs['sync-examples'].default).toBe('true');
51
+ expect(resolveInputs({}).syncExamples).toBe(true);
52
+
53
+ expect(openAlphaActionContract.inputs['collection-sync-mode'].default).toBe('refresh');
54
+ expect(openAlphaActionContract.inputs['collection-sync-mode'].allowedValues).toEqual([
55
+ 'reuse',
56
+ 'refresh',
57
+ 'version'
58
+ ]);
59
+ expect(actionManifest.inputs['collection-sync-mode'].default).toBe('refresh');
60
+ expect(resolveInputs({}).collectionSyncMode).toBe('refresh');
61
+
62
+ expect(openAlphaActionContract.inputs['spec-sync-mode'].default).toBe('update');
63
+ expect(openAlphaActionContract.inputs['spec-sync-mode'].allowedValues).toEqual([
64
+ 'update',
65
+ 'version'
66
+ ]);
67
+ expect(actionManifest.inputs['spec-sync-mode'].default).toBe('update');
68
+ expect(resolveInputs({}).specSyncMode).toBe('update');
69
+
70
+ });
71
+
44
72
  it('rejects unsupported integration backends during input resolution', () => {
45
73
  expect(() =>
46
74
  resolveInputs({
@@ -63,9 +91,6 @@ describe('open-alpha action contract', () => {
63
91
  expect(openAlphaActionContract.retainedBehavior).toContain(
64
92
  'governance group assignment'
65
93
  );
66
- expect(openAlphaActionContract.retainedBehavior).toContain(
67
- 'GitHub repository variable persistence for downstream sync steps'
68
- );
69
94
  expect(openAlphaActionContract.removedBehavior).toContain('step mode');
70
95
  expect(openAlphaActionContract.removedBehavior).toContain(
71
96
  'aws, docker, and infra workflow concerns'
@@ -184,6 +184,75 @@ describe('internal integration adapter', () => {
184
184
  );
185
185
  });
186
186
 
187
+ it('routes specification collection linking through the Bifrost proxy with sync options', async () => {
188
+ const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(
189
+ jsonResponse({ data: { updated: 1 } })
190
+ );
191
+
192
+ const adapter = createInternalIntegrationAdapter({
193
+ backend: 'bifrost',
194
+ accessToken: 'token-123',
195
+ teamId: '11430732',
196
+ fetchImpl
197
+ });
198
+
199
+ await adapter.linkCollectionsToSpecification('spec-123', [
200
+ {
201
+ collectionId: 'col-1',
202
+ syncOptions: { syncExamples: true }
203
+ }
204
+ ]);
205
+
206
+ expect(fetchImpl).toHaveBeenCalledWith(
207
+ 'https://bifrost-premium-https-v4.gw.postman.com/ws/proxy',
208
+ expect.objectContaining({
209
+ method: 'POST',
210
+ headers: expect.objectContaining({
211
+ 'x-access-token': 'token-123',
212
+ 'x-entity-team-id': '11430732'
213
+ }),
214
+ body: JSON.stringify({
215
+ service: 'specification',
216
+ method: 'put',
217
+ path: '/specifications/spec-123/collections',
218
+ body: [
219
+ {
220
+ collectionId: 'col-1',
221
+ syncOptions: { syncExamples: true }
222
+ }
223
+ ]
224
+ })
225
+ })
226
+ );
227
+ });
228
+
229
+ it('routes specification collection sync through the Bifrost proxy', async () => {
230
+ const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(
231
+ jsonResponse({ data: { taskId: 'task-1' } })
232
+ );
233
+
234
+ const adapter = createInternalIntegrationAdapter({
235
+ backend: 'bifrost',
236
+ accessToken: 'token-123',
237
+ teamId: '11430732',
238
+ fetchImpl
239
+ });
240
+
241
+ await adapter.syncCollection('spec-123', 'col-1');
242
+
243
+ expect(fetchImpl).toHaveBeenCalledWith(
244
+ 'https://bifrost-premium-https-v4.gw.postman.com/ws/proxy',
245
+ expect.objectContaining({
246
+ method: 'POST',
247
+ body: JSON.stringify({
248
+ service: 'specification',
249
+ method: 'post',
250
+ path: '/specifications/spec-123/collections/col-1/sync'
251
+ })
252
+ })
253
+ );
254
+ });
255
+
187
256
  it('treats projectAlreadyConnected as idempotent when the same repo is linked', async () => {
188
257
  const fetchImpl = vi.fn<typeof fetch>()
189
258
  // First call: connectWorkspaceToRepository POST returns 400 projectAlreadyConnected
@@ -112,4 +112,157 @@ describe('PostmanAssetsClient', () => {
112
112
 
113
113
  await expect(client.getSpecContent('spec-123')).resolves.toBeUndefined();
114
114
  });
115
+
116
+ it('creates a workspace with targetTeamId in the payload for org-mode', async () => {
117
+ const fetchImpl = vi
118
+ .fn<typeof fetch>()
119
+ .mockResolvedValueOnce(
120
+ jsonResponse({
121
+ workspace: {
122
+ id: 'ws-org-123'
123
+ }
124
+ })
125
+ )
126
+ .mockResolvedValueOnce(
127
+ jsonResponse({
128
+ workspace: {
129
+ id: 'ws-org-123',
130
+ visibility: 'team'
131
+ }
132
+ })
133
+ );
134
+
135
+ const client = new PostmanAssetsClient({
136
+ apiKey: 'pmak-test',
137
+ fetchImpl
138
+ });
139
+
140
+ await expect(client.createWorkspace('Org WS', 'desc', 132319)).resolves.toEqual({
141
+ id: 'ws-org-123'
142
+ });
143
+ expect(fetchImpl).toHaveBeenCalledTimes(2);
144
+ expect(fetchImpl).toHaveBeenNthCalledWith(
145
+ 1,
146
+ 'https://api.getpostman.com/workspaces',
147
+ expect.objectContaining({
148
+ method: 'POST',
149
+ body: JSON.stringify({
150
+ workspace: {
151
+ about: 'desc',
152
+ name: 'Org WS',
153
+ type: 'team',
154
+ teamId: 132319
155
+ }
156
+ })
157
+ })
158
+ );
159
+ });
160
+
161
+ it('creates a workspace without teamId when targetTeamId is not provided', async () => {
162
+ const fetchImpl = vi
163
+ .fn<typeof fetch>()
164
+ .mockResolvedValueOnce(
165
+ jsonResponse({
166
+ workspace: { id: 'ws-no-team' }
167
+ })
168
+ )
169
+ .mockResolvedValueOnce(
170
+ jsonResponse({
171
+ workspace: { id: 'ws-no-team', visibility: 'team' }
172
+ })
173
+ );
174
+
175
+ const client = new PostmanAssetsClient({
176
+ apiKey: 'pmak-test',
177
+ fetchImpl
178
+ });
179
+
180
+ await expect(client.createWorkspace('Regular WS', 'desc')).resolves.toEqual({
181
+ id: 'ws-no-team'
182
+ });
183
+ expect(fetchImpl).toHaveBeenNthCalledWith(
184
+ 1,
185
+ 'https://api.getpostman.com/workspaces',
186
+ expect.objectContaining({
187
+ body: JSON.stringify({
188
+ workspace: {
189
+ about: 'desc',
190
+ name: 'Regular WS',
191
+ type: 'team'
192
+ }
193
+ })
194
+ })
195
+ );
196
+ });
197
+
198
+ it('throws actionable error for org-mode workspace creation failure', async () => {
199
+ const errorBody = JSON.stringify({
200
+ error: {
201
+ name: 'invalidParamError',
202
+ message: 'Only personal workspaces (internal) can be created outside team'
203
+ }
204
+ });
205
+ const fetchImpl = vi.fn<typeof fetch>().mockImplementation(
206
+ async () => new Response(errorBody, { status: 400 })
207
+ );
208
+
209
+ const client = new PostmanAssetsClient({
210
+ apiKey: 'pmak-test',
211
+ fetchImpl
212
+ });
213
+
214
+ await expect(client.createWorkspace('Org WS', 'desc')).rejects.toThrow(
215
+ 'workspace-team-id'
216
+ );
217
+ });
218
+
219
+ it('returns parsed sub-teams from getTeams', async () => {
220
+ const fetchImpl = vi.fn<typeof fetch>().mockResolvedValueOnce(
221
+ jsonResponse({
222
+ data: [
223
+ { id: 132109, name: 'Field Services', handle: 'fs', organizationId: 13347347 },
224
+ { id: 132118, name: 'Customer Education', handle: 'ce', organizationId: 13347347 },
225
+ { id: 132272, name: 'RonCorp', handle: 'rc', organizationId: 13347347 }
226
+ ]
227
+ })
228
+ );
229
+
230
+ const client = new PostmanAssetsClient({
231
+ apiKey: 'pmak-test',
232
+ fetchImpl
233
+ });
234
+
235
+ const teams = await client.getTeams();
236
+ expect(teams).toEqual([
237
+ { id: 132109, name: 'Field Services', handle: 'fs', organizationId: 13347347 },
238
+ { id: 132118, name: 'Customer Education', handle: 'ce', organizationId: 13347347 },
239
+ { id: 132272, name: 'RonCorp', handle: 'rc', organizationId: 13347347 }
240
+ ]);
241
+ });
242
+
243
+ it('returns empty array from getTeams when no teams exist', async () => {
244
+ const fetchImpl = vi.fn<typeof fetch>().mockResolvedValueOnce(
245
+ jsonResponse({ data: [] })
246
+ );
247
+
248
+ const client = new PostmanAssetsClient({
249
+ apiKey: 'pmak-test',
250
+ fetchImpl
251
+ });
252
+
253
+ await expect(client.getTeams()).resolves.toEqual([]);
254
+ });
255
+
256
+ it('propagates errors from getTeams without swallowing', async () => {
257
+ const fetchImpl = vi.fn<typeof fetch>().mockResolvedValueOnce(
258
+ new Response('Forbidden', { status: 403 })
259
+ );
260
+
261
+ const client = new PostmanAssetsClient({
262
+ apiKey: 'pmak-test',
263
+ fetchImpl
264
+ });
265
+
266
+ await expect(client.getTeams()).rejects.toThrow();
267
+ });
115
268
  });