job-application-agent 3.1.0 → 3.1.2
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/README.md +88 -128
- package/job-application-agent/SKILL.md +28 -19
- package/job-application-agent/references/ANALYTICS.md +4 -0
- package/job-application-agent/references/RUNS.md +2 -1
- package/job-application-agent/references/SCHEMAS.md +8 -3
- package/job-application-agent/references/SOURCES.json +156 -0
- package/job-application-agent/references/SOURCES.md +66 -0
- package/job-application-agent/scripts/job-application.mjs +208 -23
- package/job-application-agent/scripts/source-community-client.mjs +254 -0
- package/job-application-agent/scripts/source-community-schema.mjs +180 -0
- package/job-application-agent/tests/job-application.test.mjs +11 -9
- package/job-application-agent/tests/privacy-audit.test.mjs +16 -0
- package/job-application-agent/tests/source-community-client.test.mjs +245 -0
- package/job-application-agent/tests/source-community-schema.test.mjs +166 -0
- package/job-application-agent/tests/telemetry-client.test.mjs +1 -1
- package/job-application-agent/tests/workflow-state.test.mjs +394 -7
- package/package.json +7 -3
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { SourceCommunityClient } from '../scripts/source-community-client.mjs';
|
|
8
|
+
|
|
9
|
+
const source = {
|
|
10
|
+
name: 'Example Engineering Board',
|
|
11
|
+
baseUrl: 'https://jobs.example.org/openings/engineering?ref=candidate@example.com#openings',
|
|
12
|
+
kind: 'job-board',
|
|
13
|
+
regions: ['global', 'remote'],
|
|
14
|
+
roleFamilies: ['engineering'],
|
|
15
|
+
requiresSession: false,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function relay() {
|
|
19
|
+
const requests = [];
|
|
20
|
+
const community = [{
|
|
21
|
+
sourceId: 'community-abcdef1234567890',
|
|
22
|
+
name: 'Example Engineering Board',
|
|
23
|
+
baseUrl: 'https://jobs.example.org/openings/engineering',
|
|
24
|
+
kind: 'job-board',
|
|
25
|
+
regions: ['global', 'remote'],
|
|
26
|
+
roleFamilies: ['engineering'],
|
|
27
|
+
requiresSession: false,
|
|
28
|
+
registryStatus: 'community-reviewed',
|
|
29
|
+
contributionCount: 2,
|
|
30
|
+
}];
|
|
31
|
+
const fetch = async (url, options = {}) => {
|
|
32
|
+
requests.push({ url, options, body: options.body ? JSON.parse(options.body) : null });
|
|
33
|
+
if (url.endsWith('/v1/install')) return Response.json({ installationId: '11111111-1111-4111-8111-111111111111', token: 'source-token', expiresAt: '2099-01-01T00:00:00.000Z' }, { status: 201 });
|
|
34
|
+
if (url.endsWith('/v1/sources') && options.method === 'POST') return Response.json({ accepted: true, sourceId: 'community-abcdef1234567890', publicationStatus: 'pending', uniqueContributors: 1 }, { status: 202 });
|
|
35
|
+
return Response.json({ version: 1, sources: community });
|
|
36
|
+
};
|
|
37
|
+
return { fetch, requests };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test('source sharing is enabled by default, disclosed, sanitized, and sent immediately', async (t) => {
|
|
41
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-default-'));
|
|
42
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
43
|
+
const network = relay();
|
|
44
|
+
let notice = '';
|
|
45
|
+
const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: (value) => { notice += value; } });
|
|
46
|
+
|
|
47
|
+
const result = await client.contribute(source);
|
|
48
|
+
|
|
49
|
+
assert.deepEqual(result, { shared: true, sourceId: 'community-abcdef1234567890', publicationStatus: 'pending', uniqueContributors: 1 });
|
|
50
|
+
assert.match(notice, /community source sharing is enabled by default/i);
|
|
51
|
+
assert.equal(network.requests.length, 2);
|
|
52
|
+
assert.equal(network.requests[1].url, 'https://relay.example.com/v1/sources');
|
|
53
|
+
assert.equal(network.requests[1].body.source.baseUrl, 'https://jobs.example.org/openings/engineering');
|
|
54
|
+
assert.equal(JSON.stringify(network.requests[1].body).includes('candidate@example.com'), false);
|
|
55
|
+
const stored = JSON.parse(await readFile(join(directory, 'source-sharing.json'), 'utf8'));
|
|
56
|
+
assert.equal(stored.enabled, true);
|
|
57
|
+
assert.equal(stored.disclosed, true);
|
|
58
|
+
if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'source-sharing.json'))).mode & 0o777, 0o600);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('community source listing validates the public response before reuse', async (t) => {
|
|
62
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-list-'));
|
|
63
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
64
|
+
const network = relay();
|
|
65
|
+
const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: () => {} });
|
|
66
|
+
const [listed] = await client.list();
|
|
67
|
+
assert.equal(listed.sourceId, 'community-abcdef1234567890');
|
|
68
|
+
assert.equal(listed.baseUrl, 'https://jobs.example.org/openings/engineering');
|
|
69
|
+
assert.equal(listed.contributionCount, 2);
|
|
70
|
+
assert.equal(listed.registryStatus, 'community-reviewed');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('source sharing can be disabled independently and never blocks local collection', async (t) => {
|
|
74
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-disabled-'));
|
|
75
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
76
|
+
const network = relay();
|
|
77
|
+
const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: () => {} });
|
|
78
|
+
|
|
79
|
+
assert.equal((await client.configure('disable')).enabled, false);
|
|
80
|
+
assert.deepEqual(await client.contribute(source), { shared: false, reason: 'disabled' });
|
|
81
|
+
assert.equal(network.requests.length, 0);
|
|
82
|
+
assert.equal((await client.configure('enable')).enabled, true);
|
|
83
|
+
assert.equal((await client.contribute(source)).shared, true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('source sharing rejects personal and one-off job URLs before network transmission', async (t) => {
|
|
87
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-reject-'));
|
|
88
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
89
|
+
const network = relay();
|
|
90
|
+
const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: network.fetch, stderr: () => {} });
|
|
91
|
+
|
|
92
|
+
await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://linkedin.com/in/some-person' }), /profile or personal/i);
|
|
93
|
+
await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://jobs.example.org/jobs/123456' }), /repeatable discovery surface/i);
|
|
94
|
+
await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://jobs.example.org/candidate@example.com/openings' }), /identity-like content/i);
|
|
95
|
+
await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://127.0.0.1/jobs' }), /public internet hostname/i);
|
|
96
|
+
await assert.rejects(() => client.preview({ ...source, baseUrl: 'https://careers.internal.local/jobs' }), /public internet hostname/i);
|
|
97
|
+
assert.equal(network.requests.length, 0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('network failures are best effort and return an unavailable result', async (t) => {
|
|
101
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-offline-'));
|
|
102
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
103
|
+
const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: async () => { throw new Error('offline'); }, stderr: () => {} });
|
|
104
|
+
assert.deepEqual(await client.contribute(source), { shared: false, reason: 'unavailable' });
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('a failed contribution does not suppress a healthy community registry read', async (t) => {
|
|
108
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-independent-read-'));
|
|
109
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
110
|
+
const network = relay();
|
|
111
|
+
const fetch = async (url, options = {}) => {
|
|
112
|
+
if (url.endsWith('/v1/sources') && options.method === 'POST') return Response.json({ error: 'rate_limited' }, { status: 429 });
|
|
113
|
+
return network.fetch(url, options);
|
|
114
|
+
};
|
|
115
|
+
const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
|
|
116
|
+
|
|
117
|
+
assert.deepEqual(await client.contribute(source), { shared: false, reason: 'unavailable' });
|
|
118
|
+
const [listed] = await client.list();
|
|
119
|
+
assert.equal(listed.sourceId, 'community-abcdef1234567890');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('concurrent opt-out is preserved and rechecked before source transmission', async (t) => {
|
|
123
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-opt-out-race-'));
|
|
124
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
125
|
+
await writeFile(join(directory, 'source-sharing.json'), JSON.stringify({
|
|
126
|
+
version: 1,
|
|
127
|
+
enabled: true,
|
|
128
|
+
disclosed: true,
|
|
129
|
+
installationId: null,
|
|
130
|
+
token: null,
|
|
131
|
+
tokenExpiresAt: null,
|
|
132
|
+
}));
|
|
133
|
+
let releaseInstall;
|
|
134
|
+
let installStarted;
|
|
135
|
+
const installGate = new Promise((resolve) => { releaseInstall = resolve; });
|
|
136
|
+
const installObserved = new Promise((resolve) => { installStarted = resolve; });
|
|
137
|
+
const sourcePosts = [];
|
|
138
|
+
const fetch = async (url, options = {}) => {
|
|
139
|
+
if (url.endsWith('/v1/install')) {
|
|
140
|
+
installStarted();
|
|
141
|
+
await installGate;
|
|
142
|
+
return Response.json({ installationId: '11111111-1111-4111-8111-111111111111', token: 'source-token', expiresAt: '2099-01-01T00:00:00.000Z' }, { status: 201 });
|
|
143
|
+
}
|
|
144
|
+
sourcePosts.push({ url, options });
|
|
145
|
+
return Response.json({ accepted: true, sourceId: 'community-abcdef1234567890', publicationStatus: 'pending', uniqueContributors: 1 }, { status: 202 });
|
|
146
|
+
};
|
|
147
|
+
const contributor = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
|
|
148
|
+
const settings = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
|
|
149
|
+
|
|
150
|
+
const contribution = contributor.contribute(source);
|
|
151
|
+
await installObserved;
|
|
152
|
+
assert.equal((await settings.configure('disable')).enabled, false);
|
|
153
|
+
releaseInstall();
|
|
154
|
+
|
|
155
|
+
assert.deepEqual(await contribution, { shared: false, reason: 'disabled' });
|
|
156
|
+
assert.equal(sourcePosts.length, 0);
|
|
157
|
+
assert.equal(JSON.parse(await readFile(join(directory, 'source-sharing.json'), 'utf8')).enabled, false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('concurrent reset is not undone by an in-flight credential refresh', async (t) => {
|
|
161
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-reset-race-'));
|
|
162
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
163
|
+
await writeFile(join(directory, 'source-sharing.json'), JSON.stringify({
|
|
164
|
+
version: 1,
|
|
165
|
+
enabled: true,
|
|
166
|
+
disclosed: true,
|
|
167
|
+
installationId: null,
|
|
168
|
+
token: null,
|
|
169
|
+
tokenExpiresAt: null,
|
|
170
|
+
}));
|
|
171
|
+
let releaseInstall;
|
|
172
|
+
let installStarted;
|
|
173
|
+
const installGate = new Promise((resolve) => { releaseInstall = resolve; });
|
|
174
|
+
const installObserved = new Promise((resolve) => { installStarted = resolve; });
|
|
175
|
+
const fetch = async (url) => {
|
|
176
|
+
if (!url.endsWith('/v1/install')) throw new Error('source contribution must remain disabled');
|
|
177
|
+
installStarted();
|
|
178
|
+
await installGate;
|
|
179
|
+
return Response.json({ installationId: '11111111-1111-4111-8111-111111111111', token: 'source-token', expiresAt: '2099-01-01T00:00:00.000Z' }, { status: 201 });
|
|
180
|
+
};
|
|
181
|
+
const contributor = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
|
|
182
|
+
const settings = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
|
|
183
|
+
|
|
184
|
+
const contribution = contributor.contribute(source);
|
|
185
|
+
await installObserved;
|
|
186
|
+
assert.deepEqual(await settings.configure('reset'), {
|
|
187
|
+
enabled: false,
|
|
188
|
+
disclosed: true,
|
|
189
|
+
hasInstallationId: false,
|
|
190
|
+
endpoint: 'https://relay.example.com',
|
|
191
|
+
schemaVersion: 1,
|
|
192
|
+
});
|
|
193
|
+
releaseInstall();
|
|
194
|
+
|
|
195
|
+
assert.deepEqual(await contribution, { shared: false, reason: 'disabled' });
|
|
196
|
+
const stored = JSON.parse(await readFile(join(directory, 'source-sharing.json'), 'utf8'));
|
|
197
|
+
assert.equal(stored.enabled, false);
|
|
198
|
+
assert.equal(stored.installationId, null);
|
|
199
|
+
assert.equal(stored.token, null);
|
|
200
|
+
assert.equal(stored.tokenExpiresAt, null);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('recovers a source-sharing lock whose owner process no longer exists', async (t) => {
|
|
204
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-stale-lock-'));
|
|
205
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
206
|
+
await writeFile(join(directory, '.source-sharing.lock'), '99999999\n');
|
|
207
|
+
const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch: async () => { throw new Error('network must not be used'); }, stderr: () => {} });
|
|
208
|
+
|
|
209
|
+
assert.equal((await client.configure('disable')).enabled, false);
|
|
210
|
+
await assert.rejects(() => stat(join(directory, '.source-sharing.lock')), { code: 'ENOENT' });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('invalid stored relay credentials are replaced once and persisted', async (t) => {
|
|
214
|
+
const directory = await mkdtemp(join(tmpdir(), 'source-community-credential-recovery-'));
|
|
215
|
+
t.after(() => rm(directory, { recursive: true, force: true }));
|
|
216
|
+
const oldInstallationId = '11111111-1111-4111-8111-111111111111';
|
|
217
|
+
const newInstallationId = '22222222-2222-4222-8222-222222222222';
|
|
218
|
+
await writeFile(join(directory, 'source-sharing.json'), JSON.stringify({
|
|
219
|
+
version: 1,
|
|
220
|
+
enabled: true,
|
|
221
|
+
disclosed: true,
|
|
222
|
+
installationId: oldInstallationId,
|
|
223
|
+
token: 'invalid-old-token',
|
|
224
|
+
tokenExpiresAt: '2099-01-01T00:00:00.000Z',
|
|
225
|
+
}));
|
|
226
|
+
const requests = [];
|
|
227
|
+
const fetch = async (url, options = {}) => {
|
|
228
|
+
const body = options.body ? JSON.parse(options.body) : null;
|
|
229
|
+
requests.push({ url, body });
|
|
230
|
+
if (url.endsWith('/v1/sources') && body.installationId === oldInstallationId) return Response.json({ error: 'invalid_token' }, { status: 401 });
|
|
231
|
+
if (url.endsWith('/v1/install') && body.installationId === oldInstallationId) return Response.json({ error: 'invalid_token' }, { status: 401 });
|
|
232
|
+
if (url.endsWith('/v1/install')) return Response.json({ installationId: newInstallationId, token: 'new-token', expiresAt: '2099-01-01T00:00:00.000Z' }, { status: 201 });
|
|
233
|
+
return Response.json({ accepted: true, sourceId: 'community-abcdef1234567890', publicationStatus: 'pending', uniqueContributors: 1 }, { status: 202 });
|
|
234
|
+
};
|
|
235
|
+
const client = new SourceCommunityClient({ stateDir: directory, endpoint: 'https://relay.example.com', fetch, stderr: () => {} });
|
|
236
|
+
|
|
237
|
+
const result = await client.contribute(source);
|
|
238
|
+
|
|
239
|
+
assert.equal(result.shared, true);
|
|
240
|
+
assert.ok(requests.some((request) => request.url.endsWith('/v1/install') && request.body.installationId === oldInstallationId));
|
|
241
|
+
assert.ok(requests.some((request) => request.url.endsWith('/v1/install') && Object.keys(request.body).length === 0));
|
|
242
|
+
const stored = JSON.parse(await readFile(join(directory, 'source-sharing.json'), 'utf8'));
|
|
243
|
+
assert.equal(stored.installationId, newInstallationId);
|
|
244
|
+
assert.equal(stored.token, 'new-token');
|
|
245
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { communitySourceId, isRepeatableCommunitySourceRoute, normalizeCommunitySource } from '../scripts/source-community-schema.mjs';
|
|
5
|
+
|
|
6
|
+
const source = {
|
|
7
|
+
name: 'Example Jobs',
|
|
8
|
+
baseUrl: 'https://example.com/',
|
|
9
|
+
kind: 'job-board',
|
|
10
|
+
regions: ['global'],
|
|
11
|
+
roleFamilies: ['engineering'],
|
|
12
|
+
requiresSession: false,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test('rejects known ATS and network job-detail routes', () => {
|
|
16
|
+
const detailUrls = [
|
|
17
|
+
'https://example.wd5.myworkdayjobs.com/en-US/jobs/job/Bengaluru/Senior-Engineer_R-12345',
|
|
18
|
+
'https://www.linkedin.com/jobs/view/1234567890',
|
|
19
|
+
'https://job-boards.greenhouse.io/example/jobs/1234567',
|
|
20
|
+
'https://jobs.lever.co/example/12345678-1234-4123-8123-123456789abc',
|
|
21
|
+
'https://jobs.ashbyhq.com/example/12345678-1234-4123-8123-123456789abc',
|
|
22
|
+
'https://apply.workable.com/example/j/ABC123DEF4/',
|
|
23
|
+
'https://jobs.smartrecruiters.com/Example/123456789-senior-engineer',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
for (const baseUrl of detailUrls) {
|
|
27
|
+
assert.equal(isRepeatableCommunitySourceRoute(new URL(baseUrl)), false, baseUrl);
|
|
28
|
+
assert.throws(
|
|
29
|
+
() => normalizeCommunitySource({ ...source, baseUrl }),
|
|
30
|
+
/repeatable discovery surface|identity-like content/i,
|
|
31
|
+
baseUrl,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('accepts roots and recognizable collection, directory, feed, careers, openings, and job-index routes', () => {
|
|
37
|
+
const collectionUrls = [
|
|
38
|
+
'https://example.com/',
|
|
39
|
+
'https://example.com/careers',
|
|
40
|
+
'https://example.com/openings/engineering',
|
|
41
|
+
'https://example.com/jobs/search',
|
|
42
|
+
'https://example.com/job-index',
|
|
43
|
+
'https://example.com/community/directory',
|
|
44
|
+
'https://example.com/hiring/feed.xml',
|
|
45
|
+
'https://example.wd5.myworkdayjobs.com/en-US/jobs',
|
|
46
|
+
'https://www.linkedin.com/jobs/search',
|
|
47
|
+
'https://job-boards.greenhouse.io/example',
|
|
48
|
+
'https://jobs.lever.co/example',
|
|
49
|
+
'https://jobs.ashbyhq.com/example',
|
|
50
|
+
'https://apply.workable.com/example',
|
|
51
|
+
'https://jobs.smartrecruiters.com/Example',
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
for (const baseUrl of collectionUrls) {
|
|
55
|
+
assert.equal(isRepeatableCommunitySourceRoute(new URL(baseUrl)), true, baseUrl);
|
|
56
|
+
assert.equal(normalizeCommunitySource({ ...source, baseUrl }).baseUrl, baseUrl.replace(/\/$/, ''), baseUrl);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('fails closed for unknown non-collection paths and strips collection queries and fragments', () => {
|
|
61
|
+
for (const baseUrl of ['https://example.com/software-engineer', 'https://example.com/jobs/senior-software-engineer']) {
|
|
62
|
+
assert.equal(isRepeatableCommunitySourceRoute(new URL(baseUrl)), false, baseUrl);
|
|
63
|
+
assert.throws(() => normalizeCommunitySource({ ...source, baseUrl }), /repeatable discovery surface/i);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const normalized = normalizeCommunitySource({
|
|
67
|
+
...source,
|
|
68
|
+
baseUrl: 'https://example.com/openings/engineering?email=candidate@example.com&token=secret#jobs',
|
|
69
|
+
});
|
|
70
|
+
assert.equal(normalized.baseUrl, 'https://example.com/openings/engineering');
|
|
71
|
+
assert.equal(JSON.stringify(normalized).includes('candidate@example.com'), false);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('rejects identity-like path namespaces even when they contain a collection cue', () => {
|
|
75
|
+
for (const baseUrl of ['https://example.com/users/jane/openings', 'https://example.com/profile/jane/careers', 'https://x.com/jane/jobs']) {
|
|
76
|
+
assert.throws(() => normalizeCommunitySource({ ...source, baseUrl }), /profile or personal|identity-like/i, baseUrl);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('rejects repeatedly encoded identity paths and identity-bearing taxonomy fields', () => {
|
|
81
|
+
assert.throws(
|
|
82
|
+
() => normalizeCommunitySource({ ...source, baseUrl: 'https://example.com/candidate%2540example.com/openings' }),
|
|
83
|
+
/identity-like/i,
|
|
84
|
+
);
|
|
85
|
+
assert.throws(() => normalizeCommunitySource({ ...source, regions: ['candidate@example.com'] }), /identity-like/i);
|
|
86
|
+
assert.throws(() => normalizeCommunitySource({ ...source, roleFamilies: ['+1 415 555 0100'] }), /identity-like/i);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('rejects Unicode and compatibility-form email identities before sharing', () => {
|
|
90
|
+
const privateValues = [
|
|
91
|
+
{ name: 'Jobs curated by josé@example.com' },
|
|
92
|
+
{ regions: ['用户@example.com'] },
|
|
93
|
+
{ regions: ['उपयोगकर्ता@example.com'] },
|
|
94
|
+
{ roleFamilies: ['jose@example.com'] },
|
|
95
|
+
{ baseUrl: 'https://example.com/%E7%94%A8%E6%88%B7%40example.com/openings' },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const override of privateValues) {
|
|
99
|
+
assert.throws(() => normalizeCommunitySource({ ...source, ...override }), /identity-like/i);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('rejects credential-like opaque path segments before source sharing', () => {
|
|
104
|
+
assert.throws(
|
|
105
|
+
() => normalizeCommunitySource({ ...source, baseUrl: 'https://example.com/feed/AbCdEfGhIjKlMnOpQrStUvWxYz/jobs' }),
|
|
106
|
+
/credential-like/i,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('rejects JWT, key-value, and prefixed credentials in source paths', () => {
|
|
111
|
+
const credentialUrls = [
|
|
112
|
+
'https://example.com/feed/eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c/jobs',
|
|
113
|
+
'https://example.com/feed/token=AbCdEfGhIjKlMnOpQrStUvWxYz/jobs',
|
|
114
|
+
'https://example.com/feed/api_key.AbCdEfGhIjKlMnOpQrStUvWxYz/jobs',
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
for (const baseUrl of credentialUrls) {
|
|
118
|
+
assert.throws(() => normalizeCommunitySource({ ...source, baseUrl }), /credential-like/i, baseUrl);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('rejects phone identities written with Unicode decimal digits', () => {
|
|
123
|
+
const privateValues = [
|
|
124
|
+
{ name: 'Jobs curated by +٩١ ٩٨٧٦٥ ٤٣٢١٠' },
|
|
125
|
+
{ regions: ['+९१ ९८७६५ ४३२१०'] },
|
|
126
|
+
{ baseUrl: 'https://example.com/%2B%D9%A9%D9%A1%20%D9%A9%D9%A8%D9%A7%D9%A6%D9%A5%20%D9%A4%D9%A3%D9%A2%D9%A1%D9%A0/jobs' },
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
for (const override of privateValues) {
|
|
130
|
+
assert.throws(() => normalizeCommunitySource({ ...source, ...override }), /identity-like/i);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('normalizes compatibility characters before personal-path classification', () => {
|
|
135
|
+
for (const baseUrl of ['https://example.com/profile/candidate/jobs', 'https://example.com/user/candidate/jobs']) {
|
|
136
|
+
assert.throws(() => normalizeCommunitySource({ ...source, baseUrl }), /identity-like/i, baseUrl);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('removes every trailing path separator when canonicalizing source URLs', async () => {
|
|
141
|
+
const canonical = normalizeCommunitySource({ ...source, baseUrl: 'https://example.com/jobs' });
|
|
142
|
+
const redundant = normalizeCommunitySource({ ...source, baseUrl: 'https://example.com/jobs///' });
|
|
143
|
+
|
|
144
|
+
assert.equal(redundant.baseUrl, canonical.baseUrl);
|
|
145
|
+
assert.equal(await communitySourceId(redundant), await communitySourceId(canonical));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('normalizes trailing DNS root dots before rejecting private hosts', () => {
|
|
149
|
+
for (const baseUrl of ['https://localhost./jobs', 'https://service.local./careers', 'https://service.internal./openings']) {
|
|
150
|
+
assert.throws(() => normalizeCommunitySource({ ...source, baseUrl }), /public internet hostname/i, baseUrl);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('rejects identity-like public hostnames after normalizing a trailing DNS root dot', () => {
|
|
155
|
+
for (const baseUrl of ['https://14155550100.example.org./jobs', 'https://candidate-14155550100.example.org/openings']) {
|
|
156
|
+
assert.throws(() => normalizeCommunitySource({ ...source, baseUrl }), /identity-like/i, baseUrl);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('source IDs normalize scheme and hostname case while preserving path case', async () => {
|
|
161
|
+
const upperHost = await communitySourceId({ ...source, baseUrl: 'HTTPS://EXAMPLE.COM/Jobs' });
|
|
162
|
+
const lowerHost = await communitySourceId({ ...source, baseUrl: 'https://example.com/Jobs' });
|
|
163
|
+
const lowerPath = await communitySourceId({ ...source, baseUrl: 'https://example.com/jobs' });
|
|
164
|
+
assert.equal(upperHost, lowerHost);
|
|
165
|
+
assert.notEqual(upperHost, lowerPath);
|
|
166
|
+
});
|
|
@@ -30,7 +30,7 @@ test('new installations disclose and send the first event immediately', async (t
|
|
|
30
30
|
assert.equal(result.sent, true);
|
|
31
31
|
assert.equal(relay.requests.length, 3);
|
|
32
32
|
assert.equal((await client.beginCommand('search')).installationEventPending, false);
|
|
33
|
-
assert.equal((await stat(join(directory, 'telemetry.json'))).mode & 0o777, 0o600);
|
|
33
|
+
if (process.platform !== 'win32') assert.equal((await stat(join(directory, 'telemetry.json'))).mode & 0o777, 0o600);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
test('existing installations receive a one-command grace period without backfill', async (t) => {
|