mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-12 22:38:33 +00:00
Harden jobs provider scheduling
This commit is contained in:
@@ -20,6 +20,25 @@ test('diagnostics schema redacts raw payloads, paths, command lines, and provide
|
||||
assert.doesNotMatch(json, /Secret Artist|Secret Song|secret\.wav|abc123|rawPayload|commandLine|never export|ffmpeg -i/);
|
||||
});
|
||||
|
||||
test('caller-supplied refs and fingerprints are exported as safe correlation keys', async () => {
|
||||
const window = loadJobs();
|
||||
const { provider } = makeProvider();
|
||||
await dispatch(window, 'register-provider', { provider });
|
||||
const enqueued = await dispatch(window, 'enqueue', enqueuePayload({
|
||||
target: { targetRef: 'Secret Song_p.psarc', id: 'Private Library Entry' },
|
||||
inputs: { fingerprint: 'Secret Input Cache.psarc' },
|
||||
logicalJobKey: 'Secret Song_p.psarc',
|
||||
}));
|
||||
const bridge = await dispatch(window, 'record-bridge-hit', { logicalJobKey: 'Secret Song_p.psarc', safeReason: 'legacy path observed' });
|
||||
const snapshot = diagnosticsSnapshot(window);
|
||||
const job = snapshot.jobs.active[0];
|
||||
|
||||
assert.equal(bridge.payload.bridge.jobId, enqueued.payload.job.jobId);
|
||||
assert.equal(job.targetRef.startsWith('target-'), true);
|
||||
assert.equal(job.inputFingerprint.startsWith('input-'), true);
|
||||
assert.doesNotMatch(JSON.stringify(snapshot), /Secret Song|Secret Input|Private Library|\.psarc/);
|
||||
});
|
||||
|
||||
test('recoverable job references are the only active state persisted across reloads', async () => {
|
||||
const window = loadJobs();
|
||||
const { provider } = makeProvider({ providerId: 'provider.recover', recoverySupport: { queued: true, running: true, paused: false } });
|
||||
|
||||
@@ -35,6 +35,23 @@ test('provider capacity starts one job and keeps the rest queued in priority ord
|
||||
assert.equal(snapshot.jobs.active[0].safeLabel, 'Third');
|
||||
});
|
||||
|
||||
test('provider queue capacity rejects excess queued work before creating a job', async () => {
|
||||
const window = loadJobs();
|
||||
const { provider } = makeProvider({ capacity: { maxRunning: 1, maxQueued: 1 } });
|
||||
await dispatch(window, 'register-provider', { provider });
|
||||
|
||||
const running = await dispatch(window, 'enqueue', enqueuePayload({ logicalJobKey: 'capacity-running' }));
|
||||
const queued = await dispatch(window, 'enqueue', enqueuePayload({ logicalJobKey: 'capacity-queued' }));
|
||||
const rejected = await dispatch(window, 'enqueue', enqueuePayload({ logicalJobKey: 'capacity-rejected' }));
|
||||
const snapshot = diagnosticsSnapshot(window);
|
||||
|
||||
assert.equal(running.status, 'applied');
|
||||
assert.equal(queued.status, 'queued');
|
||||
assert.equal(rejected.status, 'unavailable');
|
||||
assert.equal(snapshot.jobs.active.length, 1);
|
||||
assert.equal(snapshot.jobs.queued.length, 1);
|
||||
});
|
||||
|
||||
test('progress, completion, and terminal retention are reflected in events and diagnostics', async () => {
|
||||
const window = loadJobs();
|
||||
const events = captureEvents(window);
|
||||
@@ -54,6 +71,63 @@ test('progress, completion, and terminal retention are reflected in events and d
|
||||
assert.ok(events.some(event => event.event === 'completed'));
|
||||
});
|
||||
|
||||
test('provider enqueue exceptions fail safely without leaking private details', async () => {
|
||||
const window = loadJobs();
|
||||
const { provider } = makeProvider({
|
||||
operationHandlers: {
|
||||
'job.enqueue': () => { throw new Error('failed near /Users/example/private/song.psarc token=abc123'); },
|
||||
},
|
||||
});
|
||||
await dispatch(window, 'register-provider', { provider });
|
||||
|
||||
const result = await dispatch(window, 'enqueue', enqueuePayload({ logicalJobKey: 'throws-on-start' }));
|
||||
const snapshot = diagnosticsSnapshot(window);
|
||||
|
||||
assert.equal(result.status, 'error');
|
||||
assert.equal(result.outcome, 'failed');
|
||||
assert.equal(snapshot.jobs.active.length, 0);
|
||||
assert.equal(snapshot.jobs.recentTerminal[0].terminalOutcome.category, 'provider-failure');
|
||||
assert.doesNotMatch(JSON.stringify(snapshot), /Users\/example|song\.psarc|abc123/);
|
||||
});
|
||||
|
||||
test('provider enqueue failed results become terminal failures', async () => {
|
||||
const window = loadJobs();
|
||||
const { provider } = makeProvider({
|
||||
operationHandlers: {
|
||||
'job.enqueue': () => ({ outcome: 'failed', category: 'external-dependency', safeReason: 'tool failed near /Users/example/private/cache.bin' }),
|
||||
},
|
||||
});
|
||||
await dispatch(window, 'register-provider', { provider });
|
||||
|
||||
const result = await dispatch(window, 'enqueue', enqueuePayload({ logicalJobKey: 'failed-result' }));
|
||||
const snapshot = diagnosticsSnapshot(window);
|
||||
|
||||
assert.equal(result.status, 'error');
|
||||
assert.equal(result.outcome, 'failed');
|
||||
assert.equal(snapshot.jobs.recentTerminal[0].terminalOutcome.category, 'external-dependency');
|
||||
assert.doesNotMatch(JSON.stringify(snapshot), /Users\/example|cache\.bin/);
|
||||
});
|
||||
|
||||
test('async provider enqueue rejections become terminal provider failures', async () => {
|
||||
const window = loadJobs();
|
||||
const { provider } = makeProvider({
|
||||
operationHandlers: {
|
||||
'job.enqueue': () => Promise.reject(new Error('secret path /Users/example/private/cache.bin')),
|
||||
},
|
||||
});
|
||||
await dispatch(window, 'register-provider', { provider });
|
||||
|
||||
const result = await dispatch(window, 'enqueue', enqueuePayload({ logicalJobKey: 'rejects-after-start' }));
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
const snapshot = diagnosticsSnapshot(window);
|
||||
|
||||
assert.equal(result.status, 'applied');
|
||||
assert.equal(snapshot.jobs.active.length, 0);
|
||||
assert.equal(snapshot.jobs.recentTerminal[0].terminalOutcome.category, 'provider-failure');
|
||||
assert.doesNotMatch(JSON.stringify(snapshot), /Users\/example|cache\.bin/);
|
||||
});
|
||||
|
||||
test('cancel, pause, resume, retry, and stale transitions use canonical outcomes', async () => {
|
||||
const window = loadJobs();
|
||||
const { provider } = makeProvider({ capacity: { maxRunning: 1, maxQueued: 10 } });
|
||||
|
||||
Reference in New Issue
Block a user