Add outreach candidate editing controls
This commit is contained in:
1 parent
c75e71f26e
commit
79c508be2b
19 files changed
+434
-12
No files matched your search
@@ -197,6 +197,50 @@ describe('CandidateQueueRepository', () => {
|
||||
expect(reasons).toEqual([{ last_error: 'Cleared manually by operator' }]);
|
||||
});
|
||||
|
||||
it('edits only prepared unsent messages and clears generated attribution', () => {
|
||||
queue.enqueue([post('editable', 'alice', 200), post('waiting', 'bob', 100)], 'image', 1, 1_000);
|
||||
const editable = queue.claimForScreening('screen', 1_000, 1_001)!;
|
||||
queue.markReady(editable.postId, 'screen', prepared(editable.post), 1_002);
|
||||
db.prepare(`
|
||||
INSERT INTO processed_posts (account_id, id, subreddit, author, title, generated_message, processed_at)
|
||||
VALUES ('account-a', ?, 'test', 'alice', 'title', 'prepared message', 1)
|
||||
`).run(editable.postId);
|
||||
|
||||
expect(queue.updatePreparedMessage(editable.postId, 'Operator-written opener', 1_003)).toBe('updated');
|
||||
expect(queue.listActive().find((candidate) => candidate.postId === editable.postId)?.prepared).toMatchObject({
|
||||
generatedMessage: 'Operator-written opener',
|
||||
strategyId: null,
|
||||
experimentId: null,
|
||||
promptVersion: null,
|
||||
selectionReason: null,
|
||||
modelId: 'operator-edited',
|
||||
operatorEdited: true,
|
||||
});
|
||||
expect((db.prepare(`SELECT generated_message FROM processed_posts WHERE id = ?`).get(editable.postId) as {
|
||||
generated_message: string;
|
||||
}).generated_message).toBe('Operator-written opener');
|
||||
expect(queue.updatePreparedMessage('waiting', 'Nope', 1_004)).toBe('not-editable');
|
||||
expect(queue.updatePreparedMessage('missing', 'Nope', 1_004)).toBe('not-found');
|
||||
|
||||
queue.claimReady('account-a', 'send', 1_000, 1_005);
|
||||
expect(queue.updatePreparedMessage(editable.postId, 'Too late', 1_006)).toBe('not-editable');
|
||||
});
|
||||
|
||||
it('removes one unsent candidate but refuses to cancel an in-flight send', () => {
|
||||
queue.enqueue([post('remove', 'alice')], 'image', 1, 1_000);
|
||||
expect(queue.cancelUnsent('remove', 1_001)).toBe('cancelled');
|
||||
expect(queue.getStats()).toMatchObject({ byStatus: { rejected: 1 } });
|
||||
expect(queue.cancelUnsent('remove', 1_002)).toBe('not-cancellable');
|
||||
expect(queue.cancelUnsent('missing', 1_002)).toBe('not-found');
|
||||
|
||||
queue.enqueue([post('sending', 'bob')], 'image', 1, 1_003);
|
||||
const candidate = queue.claimForScreening('screen', 1_000, 1_004)!;
|
||||
queue.markReady(candidate.postId, 'screen', prepared(candidate.post), 1_005);
|
||||
queue.claimReady('account-a', 'send', 1_000, 1_006);
|
||||
expect(queue.cancelUnsent('sending', 1_007)).toBe('not-cancellable');
|
||||
expect(queue.listActive()).toEqual([expect.objectContaining({ postId: 'sending', status: 'sending' })]);
|
||||
});
|
||||
|
||||
it('retries a rate-limited prepared candidate and preserves its generated message', () => {
|
||||
queue.enqueue([post('p1', 'alice')], 'image', 1, 1_000);
|
||||
const screening = queue.claimForScreening('screen', 100, 1_001)!;
|
||||
|
||||
Reference in new issue
Block a user