@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/.github/workflows/contract-smoke.yml +155 -0
- package/.omc/sessions/b36ac16d-ce26-4e94-ae7e-dd817f2430a3.json +8 -0
- package/CLAUDE.md +57 -0
- package/README.md +133 -12
- package/action.yml +21 -18
- package/dist/cli.cjs +370 -518
- package/dist/index.cjs +364 -509
- package/package.json +3 -2
- package/src/cli.ts +6 -11
- package/src/contracts.ts +30 -24
- package/src/index.ts +302 -322
- package/src/lib/postman/internal-integration-adapter.ts +116 -30
- package/src/lib/postman/postman-assets-client.ts +27 -4
- package/tests/bootstrap-action.test.ts +647 -77
- package/tests/cli.test.ts +15 -6
- package/tests/contract.test.ts +28 -3
- package/tests/internal-integration-adapter.test.ts +69 -0
- package/tests/postman-assets-client.test.ts +153 -0
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
|
|
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
|
});
|
package/tests/contract.test.ts
CHANGED
|
@@ -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
|
});
|