-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathverify-postman-publish.ts
More file actions
187 lines (162 loc) · 6.19 KB
/
Copy pathverify-postman-publish.ts
File metadata and controls
187 lines (162 loc) · 6.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env ts-node
import assert from 'node:assert/strict';
import test from 'node:test';
import {
WORKSPACE_NAME,
collectBaseUrls,
countRequests,
fingerprintCollection,
hasRequestMatching,
listCollectionFiles,
loadLocalCollection,
postmanFetch,
readJson,
readMapping,
requireApiKey,
} from './postman-publish-lib';
interface WorkspaceResponse {
workspace: {
id: string;
name: string;
type: string;
collections?: Array<{ id: string; uid?: string }>;
};
}
interface CollectionResponse {
collection: {
info: {
name: string;
description?: string | { content?: string };
};
item?: unknown[];
auth?: unknown;
variable?: Array<{ key: string; value: string }>;
};
}
async function fetchPublic(url: string): Promise<Response> {
return fetch(url, {
headers: {
Accept: 'text/html,application/json,*/*',
'User-Agent': 'extole-specification-verify/1.0',
},
redirect: 'follow',
});
}
const apiKey = requireApiKey();
const mapping = readMapping();
assert.ok(
mapping.workspace?.id,
'postman/.postman-publish.json is missing workspace.id; run npm run publish:postman first.',
);
const workspaceId = mapping.workspace!.id;
const files = listCollectionFiles();
assert.ok(files.length > 0, 'No local Postman collections found in postman/.');
test('group 1: authenticated workspace and collection state', async () => {
const workspaceResponse = await postmanFetch(apiKey, `/workspaces/${workspaceId}`);
const workspaceBody = (await readJson(workspaceResponse)) as WorkspaceResponse;
assert.equal(workspaceResponse.status, 200, JSON.stringify(workspaceBody));
assert.equal(workspaceBody.workspace.name, WORKSPACE_NAME);
if (mapping.workspace?.type === 'public') {
assert.equal(workspaceBody.workspace.type, 'public');
} else {
console.warn(
`Workspace type is "${workspaceBody.workspace.type}" (expected public). Flip visibility in Postman UI if external discovery is required.`,
);
}
const workspaceCollectionIds = new Set(
(workspaceBody.workspace.collections ?? []).map((collection) => collection.uid ?? collection.id),
);
for (const filename of files) {
const key = filename.replace(/\.json$/, '');
const mapped = mapping.collections[key];
assert.ok(mapped?.uid, `Missing mapped UID for ${key}`);
assert.ok(
workspaceCollectionIds.has(mapped.uid),
`Workspace does not include collection UID ${mapped.uid} (${key})`,
);
const collectionResponse = await postmanFetch(apiKey, `/collections/${mapped.uid}`);
const collectionBody = (await readJson(collectionResponse)) as CollectionResponse;
assert.equal(collectionResponse.status, 200, JSON.stringify(collectionBody));
const local = loadLocalCollection(filename);
assert.equal(collectionBody.collection.info.name, local.info.name);
const remoteCount = countRequests(collectionBody.collection.item as never);
const localCount = countRequests(local.item);
assert.equal(
remoteCount,
localCount,
`Request count mismatch for ${key}: remote=${remoteCount}, local=${localCount}`,
);
}
});
test('group 2: public visibility without auth', async () => {
const overviewUrl = mapping.workspace!.overviewUrl;
const overviewResponse = await fetchPublic(overviewUrl);
assert.equal(overviewResponse.status, 200, `Expected workspace page at ${overviewUrl}`);
const consumer = mapping.collections['integration-consumer-to-extole'];
assert.ok(consumer?.uid, 'integration-consumer-to-extole mapping missing');
const collectionPage = await fetchPublic(
`https://www.postman.com/_api/collection/${consumer.uid}`,
);
if (mapping.workspace?.type === 'public') {
assert.equal(
collectionPage.status,
200,
`Expected public collection API for ${consumer.uid}`,
);
return;
}
console.warn(
`Workspace type is "${mapping.workspace?.type ?? 'unknown'}". Public collection API returned ${collectionPage.status}; flip workspace visibility to public in Postman UI for external discoverability.`,
);
assert.notEqual(
collectionPage.status,
401,
`Collection ${consumer.uid} requires authentication unexpectedly`,
);
});
test('group 3: content sanity', async () => {
for (const filename of files) {
const local = loadLocalCollection(filename);
const baseUrls = collectBaseUrls(local);
assert.ok(baseUrls.length > 0, `${filename} is missing baseUrl variable`);
for (const baseUrl of baseUrls) {
assert.doesNotMatch(baseUrl, /localhost|127\.0\.0\.1|\.internal/i, `${filename} leaked internal baseUrl ${baseUrl}`);
assert.match(
baseUrl,
/api\.extole\.(com|io)|\{\{brand\}\}\.extole\.io/i,
`${filename} baseUrl ${baseUrl} is unexpected`,
);
}
}
const consumer = loadLocalCollection('integration-consumer-to-extole.json');
assert.ok(
hasRequestMatching(consumer.item, (method, requestPath) =>
method === 'POST' && requestPath.includes('/zones'),
),
'integration-consumer-to-extole is missing POST /zones request',
);
const server = loadLocalCollection('integration-server-to-extole.json');
assert.ok(server.auth, 'integration-server-to-extole is missing collection-level auth');
assert.equal((server.auth as { type?: string }).type, 'apikey');
});
test('group 4: round-trip drift detection', async () => {
for (const filename of files) {
const key = filename.replace(/\.json$/, '');
const mapped = mapping.collections[key];
assert.ok(mapped?.uid, `Missing mapped UID for ${key}`);
const local = loadLocalCollection(filename);
const localFingerprint = fingerprintCollection(local);
const collectionResponse = await postmanFetch(apiKey, `/collections/${mapped.uid}`);
const collectionBody = (await readJson(collectionResponse)) as CollectionResponse;
assert.equal(collectionResponse.status, 200, JSON.stringify(collectionBody));
const remoteFingerprint = fingerprintCollection({
info: collectionBody.collection.info,
item: collectionBody.collection.item as never,
});
assert.equal(
remoteFingerprint,
localFingerprint,
`Fingerprint mismatch for ${key}; published collection drifted from local postman/${filename}`,
);
}
});