@postman-cse/onboarding-bootstrap 0.11.0 → 0.13.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,268 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
-
3
- import { PostmanAssetsClient } from '../src/lib/postman/postman-assets-client.js';
4
-
5
- function jsonResponse(body: unknown, init: ResponseInit = {}): Response {
6
- return new Response(JSON.stringify(body), {
7
- headers: {
8
- 'Content-Type': 'application/json'
9
- },
10
- ...init
11
- });
12
- }
13
-
14
- describe('PostmanAssetsClient', () => {
15
- it('uses the public Postman API base URL by default', () => {
16
- const client = new PostmanAssetsClient({
17
- apiKey: 'pmak-test'
18
- });
19
-
20
- expect(client.getBaseUrl()).toBe('https://api.getpostman.com');
21
- });
22
-
23
- it('creates a workspace and enforces team visibility', async () => {
24
- const fetchImpl = vi
25
- .fn<typeof fetch>()
26
- .mockResolvedValueOnce(
27
- jsonResponse({
28
- workspace: {
29
- id: 'ws-123'
30
- }
31
- })
32
- )
33
- .mockResolvedValueOnce(
34
- jsonResponse({
35
- workspace: {
36
- id: 'ws-123',
37
- visibility: 'private'
38
- }
39
- })
40
- )
41
- .mockResolvedValueOnce(
42
- jsonResponse({
43
- workspace: {
44
- id: 'ws-123',
45
- visibility: 'team'
46
- }
47
- })
48
- );
49
-
50
- const client = new PostmanAssetsClient({
51
- apiKey: 'pmak-test',
52
- fetchImpl
53
- });
54
-
55
- await expect(client.createWorkspace('Core Banking', 'desc')).resolves.toEqual({
56
- id: 'ws-123'
57
- });
58
- expect(fetchImpl).toHaveBeenCalledTimes(3);
59
- expect(fetchImpl).toHaveBeenNthCalledWith(
60
- 3,
61
- 'https://api.getpostman.com/workspaces/ws-123',
62
- expect.objectContaining({
63
- method: 'PUT'
64
- })
65
- );
66
- });
67
-
68
- it('normalizes collection tags to valid Postman slugs', async () => {
69
- const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(
70
- new Response(null, {
71
- status: 204
72
- })
73
- );
74
- const client = new PostmanAssetsClient({
75
- apiKey: 'pmak-test',
76
- fetchImpl
77
- });
78
-
79
- await client.tagCollection('col-123', ['Generated Smoke', 'core banking']);
80
-
81
- expect(fetchImpl).toHaveBeenCalledWith(
82
- 'https://api.getpostman.com/collections/col-123/tags',
83
- expect.objectContaining({
84
- method: 'PUT',
85
- body: JSON.stringify({
86
- tags: [{ slug: 'generated-smoke' }, { slug: 'core-banking' }]
87
- })
88
- })
89
- );
90
- });
91
-
92
- it('returns existing spec content when available', async () => {
93
- const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(
94
- jsonResponse({ content: 'openapi: 3.1.0' })
95
- );
96
- const client = new PostmanAssetsClient({
97
- apiKey: 'pmak-test',
98
- fetchImpl
99
- });
100
-
101
- await expect(client.getSpecContent('spec-123')).resolves.toBe('openapi: 3.1.0');
102
- });
103
-
104
- it('returns undefined when fetching existing spec content fails', async () => {
105
- const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(
106
- new Response('not found', { status: 404 })
107
- );
108
- const client = new PostmanAssetsClient({
109
- apiKey: 'pmak-test',
110
- fetchImpl
111
- });
112
-
113
- await expect(client.getSpecContent('spec-123')).resolves.toBeUndefined();
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
- });
268
- });
@@ -1,54 +0,0 @@
1
- import { describe, expect, it, vi, afterEach } from 'vitest';
2
-
3
- import { retry } from '../src/lib/retry.js';
4
-
5
- describe('retry', () => {
6
- afterEach(() => {
7
- vi.useRealTimers();
8
- });
9
-
10
- it('retries failed operations and resolves on a later success', async () => {
11
- vi.useFakeTimers();
12
-
13
- const operation = vi
14
- .fn<() => Promise<string>>()
15
- .mockRejectedValueOnce(new Error('first failure'))
16
- .mockResolvedValueOnce('ok');
17
-
18
- const onRetry = vi.fn();
19
- const promise = retry(operation, {
20
- maxAttempts: 3,
21
- delayMs: 250,
22
- onRetry
23
- });
24
-
25
- await vi.advanceTimersByTimeAsync(250);
26
-
27
- await expect(promise).resolves.toBe('ok');
28
- expect(operation).toHaveBeenCalledTimes(2);
29
- expect(onRetry).toHaveBeenCalledTimes(1);
30
- expect(onRetry).toHaveBeenCalledWith(
31
- expect.objectContaining({
32
- attempt: 1,
33
- delayMs: 250
34
- })
35
- );
36
- });
37
-
38
- it('stops retrying when shouldRetry rejects the error', async () => {
39
- vi.useFakeTimers();
40
-
41
- const operation = vi
42
- .fn<() => Promise<string>>()
43
- .mockRejectedValue(new Error('fatal'));
44
-
45
- const promise = retry(operation, {
46
- maxAttempts: 4,
47
- delayMs: 500,
48
- shouldRetry: () => false
49
- });
50
-
51
- await expect(promise).rejects.toThrow('fatal');
52
- expect(operation).toHaveBeenCalledTimes(1);
53
- });
54
- });
@@ -1,66 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { HttpError } from '../src/lib/http-error.js';
4
- import {
5
- REDACTED,
6
- redactSecrets,
7
- sanitizeHeaders
8
- } from '../src/lib/secrets.js';
9
-
10
- describe('secret safety rails', () => {
11
- it('redacts configured secret values from freeform text', () => {
12
- const sanitized = redactSecrets(
13
- 'Authorization: Bearer token-123 and key pmak-secret',
14
- ['token-123', 'pmak-secret']
15
- );
16
-
17
- expect(sanitized).toBe(`Authorization: Bearer ${REDACTED} and key ${REDACTED}`);
18
- });
19
-
20
- it('sanitizes headers before surfacing them', () => {
21
- const headers = sanitizeHeaders(
22
- {
23
- Authorization: 'Bearer token-123',
24
- 'x-api-key': 'pmak-secret',
25
- 'x-trace-id': 'trace-token-123'
26
- },
27
- ['token-123', 'pmak-secret']
28
- );
29
-
30
- expect(headers).toEqual({
31
- authorization: REDACTED,
32
- 'x-api-key': REDACTED,
33
- 'x-trace-id': `trace-${REDACTED}`
34
- });
35
- });
36
-
37
- it('builds sanitized HTTP diagnostics without leaking token material', () => {
38
- const error = new HttpError({
39
- method: 'POST',
40
- url: 'https://example.test/resource?token=token-123',
41
- status: 401,
42
- statusText: 'Unauthorized',
43
- requestHeaders: {
44
- Authorization: 'Bearer token-123',
45
- 'x-api-key': 'pmak-secret'
46
- },
47
- responseBody: 'token-123 rejected with api key pmak-secret',
48
- secretValues: ['token-123', 'pmak-secret']
49
- });
50
-
51
- expect(error.message).not.toContain('token-123');
52
- expect(error.message).not.toContain('pmak-secret');
53
- expect(error.toJSON()).toEqual({
54
- method: 'POST',
55
- name: 'HttpError',
56
- requestHeaders: {
57
- authorization: REDACTED,
58
- 'x-api-key': REDACTED
59
- },
60
- responseBody: `${REDACTED} rejected with api key ${REDACTED}`,
61
- status: 401,
62
- statusText: 'Unauthorized',
63
- url: `https://example.test/resource?token=${REDACTED}`
64
- });
65
- });
66
- });
@@ -1,301 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
-
3
- import {
4
- chooseCanonicalWorkspace,
5
- resolveCanonicalWorkspaceSelection
6
- } from '../src/lib/postman/workspace-selection.js';
7
- import { normalizeGitRepoUrl } from '../src/lib/postman/postman-assets-client.js';
8
-
9
- describe('normalizeGitRepoUrl', () => {
10
- it('returns empty string for empty input', () => {
11
- expect(normalizeGitRepoUrl('')).toBe('');
12
- expect(normalizeGitRepoUrl(null)).toBe('');
13
- expect(normalizeGitRepoUrl(undefined)).toBe('');
14
- });
15
-
16
- it('normalizes GitHub HTTPS URLs', () => {
17
- expect(normalizeGitRepoUrl('https://github.com/Postman-CS/my-repo')).toBe(
18
- 'https://github.com/postman-cs/my-repo'
19
- );
20
- });
21
-
22
- it('normalizes GitLab HTTPS URLs', () => {
23
- expect(normalizeGitRepoUrl('https://gitlab.com/Org-Name/My-Repo')).toBe(
24
- 'https://gitlab.com/org-name/my-repo'
25
- );
26
- });
27
-
28
- it('strips .git from GitLab URLs', () => {
29
- expect(normalizeGitRepoUrl('https://gitlab.com/org/repo.git')).toBe(
30
- 'https://gitlab.com/org/repo'
31
- );
32
- });
33
-
34
- it('converts GitLab SSH URLs to HTTPS', () => {
35
- expect(normalizeGitRepoUrl('git@gitlab.com:org/repo.git')).toBe(
36
- 'https://gitlab.com/org/repo'
37
- );
38
- });
39
-
40
- it('handles self-hosted GitLab instances', () => {
41
- expect(normalizeGitRepoUrl('https://git.example.com/team/project.git')).toBe(
42
- 'https://git.example.com/team/project'
43
- );
44
- expect(normalizeGitRepoUrl('git@git.example.com:team/project.git')).toBe(
45
- 'https://git.example.com/team/project'
46
- );
47
- });
48
-
49
- it('strips trailing .git from GitHub URLs', () => {
50
- expect(normalizeGitRepoUrl('https://github.com/postman-cs/my-repo.git')).toBe(
51
- 'https://github.com/postman-cs/my-repo'
52
- );
53
- });
54
-
55
- it('converts GitHub SSH URLs to HTTPS', () => {
56
- expect(normalizeGitRepoUrl('git@github.com:postman-cs/my-repo.git')).toBe(
57
- 'https://github.com/postman-cs/my-repo'
58
- );
59
- });
60
- });
61
-
62
- describe('chooseCanonicalWorkspace', () => {
63
- const repoUrl = 'https://github.com/postman-cs/core-payments';
64
-
65
- it('returns create when no matching workspaces exist', () => {
66
- const result = chooseCanonicalWorkspace({
67
- repoUrl,
68
- matchingWorkspaces: []
69
- });
70
- expect(result).toEqual({ type: 'create' });
71
- });
72
-
73
- it('returns existing with linked_match when exactly one workspace is linked to the repo', () => {
74
- const result = chooseCanonicalWorkspace({
75
- repoUrl,
76
- matchingWorkspaces: [
77
- { id: 'ws-abc', linkedRepoUrl: repoUrl }
78
- ]
79
- });
80
- expect(result).toEqual({
81
- type: 'existing',
82
- workspaceId: 'ws-abc',
83
- source: 'linked_match',
84
- warning: undefined
85
- });
86
- });
87
-
88
- it('emits a warning when linked workspace differs from repo var workspace', () => {
89
- const result = chooseCanonicalWorkspace({
90
- repoUrl,
91
- repoWorkspaceId: 'ws-old',
92
- matchingWorkspaces: [
93
- { id: 'ws-new', linkedRepoUrl: repoUrl }
94
- ]
95
- });
96
- expect(result.type).toBe('existing');
97
- if (result.type === 'existing') {
98
- expect(result.workspaceId).toBe('ws-new');
99
- expect(result.source).toBe('linked_match');
100
- expect(result.warning).toContain('ws-old');
101
- expect(result.warning).toContain('ws-new');
102
- }
103
- });
104
-
105
- it('returns manual_review when multiple workspaces are linked to the same repo', () => {
106
- const result = chooseCanonicalWorkspace({
107
- repoUrl,
108
- matchingWorkspaces: [
109
- { id: 'ws-1', linkedRepoUrl: repoUrl },
110
- { id: 'ws-2', linkedRepoUrl: repoUrl }
111
- ]
112
- });
113
- expect(result.type).toBe('manual_review');
114
- if (result.type === 'manual_review') {
115
- expect(result.reason).toContain('ws-1');
116
- expect(result.reason).toContain('ws-2');
117
- }
118
- });
119
-
120
- it('keeps repo var workspace when multiple linked workspaces exist and one matches', () => {
121
- const result = chooseCanonicalWorkspace({
122
- repoUrl,
123
- repoWorkspaceId: 'ws-1',
124
- matchingWorkspaces: [
125
- { id: 'ws-1', linkedRepoUrl: repoUrl },
126
- { id: 'ws-2', linkedRepoUrl: repoUrl }
127
- ]
128
- });
129
- expect(result.type).toBe('existing');
130
- if (result.type === 'existing') {
131
- expect(result.workspaceId).toBe('ws-1');
132
- expect(result.source).toBe('linked_match');
133
- }
134
- });
135
-
136
- it('returns existing with repo_var when no linked match but repo var is set', () => {
137
- const result = chooseCanonicalWorkspace({
138
- repoWorkspaceId: 'ws-3',
139
- repoUrl,
140
- matchingWorkspaces: []
141
- });
142
-
143
- expect(result).toEqual({
144
- type: 'existing',
145
- workspaceId: 'ws-3',
146
- source: 'repo_var'
147
- });
148
- });
149
-
150
- it('returns create when repo var workspace matches by name but is linked to a different repository', () => {
151
- const result = chooseCanonicalWorkspace({
152
- repoWorkspaceId: 'ws-stale',
153
- repoUrl,
154
- matchingWorkspaces: [
155
- {
156
- id: 'ws-stale',
157
- linkedRepoUrl: 'https://github.com/postman-cs/different-repo'
158
- }
159
- ]
160
- });
161
-
162
- expect(result).toEqual({ type: 'create' });
163
- });
164
-
165
- it('returns existing with name_match when no linked match and no repo var', () => {
166
- const result = chooseCanonicalWorkspace({
167
- repoUrl,
168
- matchingWorkspaces: [{ id: 'ws-7' }]
169
- });
170
- expect(result).toEqual({
171
- type: 'existing',
172
- workspaceId: 'ws-7',
173
- source: 'name_match'
174
- });
175
- });
176
-
177
- it('picks the lexicographically first workspace on name_match when multiple exist', () => {
178
- const result = chooseCanonicalWorkspace({
179
- repoUrl,
180
- matchingWorkspaces: [
181
- { id: 'ws-zzz', linkedRepoUrl: null },
182
- { id: 'ws-aaa', linkedRepoUrl: null }
183
- ]
184
- });
185
- expect(result.type).toBe('existing');
186
- if (result.type === 'existing') {
187
- expect(result.workspaceId).toBe('ws-aaa');
188
- }
189
- });
190
- });
191
-
192
- describe('resolveCanonicalWorkspaceSelection', () => {
193
- const repoUrl = 'https://github.com/postman-cs/core-payments';
194
- const workspaceName = '[AF] core-payments';
195
-
196
- it('returns create when no workspaces match by name', async () => {
197
- const postman = {
198
- findWorkspacesByName: vi.fn().mockResolvedValue([]),
199
- getWorkspaceGitRepoUrl: vi.fn()
200
- };
201
-
202
- const result = await resolveCanonicalWorkspaceSelection({
203
- postman,
204
- workspaceName,
205
- repoUrl,
206
- teamId: 'team-123',
207
- accessToken: 'token-abc'
208
- });
209
-
210
- expect(result).toEqual({ type: 'create' });
211
- expect(postman.getWorkspaceGitRepoUrl).not.toHaveBeenCalled();
212
- });
213
-
214
- it('fetches git repo URL even when only one workspace matches by name', async () => {
215
- const postman = {
216
- findWorkspacesByName: vi.fn().mockResolvedValue([{ id: 'ws-single', name: workspaceName }]),
217
- getWorkspaceGitRepoUrl: vi.fn().mockResolvedValue(repoUrl)
218
- };
219
-
220
- const result = await resolveCanonicalWorkspaceSelection({
221
- postman,
222
- workspaceName,
223
- repoUrl,
224
- teamId: 'team-123',
225
- accessToken: 'token-abc'
226
- });
227
-
228
- expect(result.type).toBe('existing');
229
- expect(postman.getWorkspaceGitRepoUrl).toHaveBeenCalled();
230
- });
231
-
232
- it('fetches git repo URLs when multiple workspaces match by name', async () => {
233
- const postman = {
234
- findWorkspacesByName: vi.fn().mockResolvedValue([
235
- { id: 'ws-1', name: workspaceName },
236
- { id: 'ws-2', name: workspaceName }
237
- ]),
238
- getWorkspaceGitRepoUrl: vi.fn().mockImplementation(async (id: string) => {
239
- if (id === 'ws-1') return repoUrl;
240
- return null;
241
- })
242
- };
243
-
244
- const result = await resolveCanonicalWorkspaceSelection({
245
- postman,
246
- workspaceName,
247
- repoUrl,
248
- teamId: 'team-123',
249
- accessToken: 'token-abc'
250
- });
251
-
252
- expect(result.type).toBe('existing');
253
- if (result.type === 'existing') {
254
- expect(result.workspaceId).toBe('ws-1');
255
- expect(result.source).toBe('linked_match');
256
- }
257
- expect(postman.getWorkspaceGitRepoUrl).toHaveBeenCalledTimes(2);
258
- });
259
-
260
- it('falls back to repoWorkspaceId when workspace lookup fails', async () => {
261
- const warn = vi.fn();
262
- const postman = {
263
- findWorkspacesByName: vi.fn().mockRejectedValue(new Error('network error')),
264
- getWorkspaceGitRepoUrl: vi.fn()
265
- };
266
-
267
- const result = await resolveCanonicalWorkspaceSelection({
268
- postman,
269
- workspaceName,
270
- repoWorkspaceId: 'ws-fallback',
271
- repoUrl,
272
- teamId: 'team-123',
273
- accessToken: 'token-abc',
274
- warn
275
- });
276
-
277
- expect(result.type).toBe('existing');
278
- if (result.type === 'existing') {
279
- expect(result.workspaceId).toBe('ws-fallback');
280
- expect(result.source).toBe('repo_var');
281
- }
282
- expect(warn).toHaveBeenCalledWith(expect.stringContaining('ws-fallback'));
283
- });
284
-
285
- it('rethrows workspace lookup error when no repoWorkspaceId fallback exists', async () => {
286
- const postman = {
287
- findWorkspacesByName: vi.fn().mockRejectedValue(new Error('network error')),
288
- getWorkspaceGitRepoUrl: vi.fn()
289
- };
290
-
291
- await expect(
292
- resolveCanonicalWorkspaceSelection({
293
- postman,
294
- workspaceName,
295
- repoUrl,
296
- teamId: 'team-123',
297
- accessToken: 'token-abc'
298
- })
299
- ).rejects.toThrow('network error');
300
- });
301
- });
@@ -1,11 +0,0 @@
1
- {
2
- "workspace-id": "ws-123",
3
- "workspace-url": "https://go.postman.co/workspace/ws-123",
4
- "workspace-name": "[AF] core-payments",
5
- "spec-id": "spec-123",
6
- "baseline-collection-id": "col-baseline",
7
- "smoke-collection-id": "col-smoke",
8
- "contract-collection-id": "col-contract",
9
- "collections-json": "{\"baseline\":\"col-baseline\",\"smoke\":\"col-smoke\",\"contract\":\"col-contract\"}",
10
- "lint-summary-json": "{\"errors\":0,\"total\":0,\"violations\":[],\"warnings\":0}"
11
- }