Track pending browsing upvotes

This commit is contained in:
jjbrunton committed 2026-08-11 22:05:54 +01:00
1 parent 540590f43e
commit 1fd154e1eb
13 files changed
+73 -27

No files matched your search

+24 -9
View File
@@ -151,7 +151,7 @@ describe('AccountBrowsingSimulationService', () => {
const result = await fixture.instance.runNow();
expect(result).toMatchObject({
kind: 'simulated', decision: { thingFullname: 't3_one', voteSubmitted: true },
kind: 'simulated', decision: { thingFullname: 't3_one', voteSubmitted: true, voteStatus: 'submitted' },
});
expect(fixture.getHotPosts).toHaveBeenCalledOnce();
expect(voter.upvote).toHaveBeenCalledWith('t3_one');
@@ -168,7 +168,7 @@ describe('AccountBrowsingSimulationService', () => {
});
await expect(fixture.instance.runNow()).resolves.toMatchObject({
kind: 'simulated', decision: { voteSubmitted: false },
kind: 'simulated', decision: { voteSubmitted: false, voteStatus: 'simulated' },
});
expect(voter.upvote).not.toHaveBeenCalled();
});
@@ -192,9 +192,9 @@ describe('AccountBrowsingSimulationService', () => {
const result = await service({ voter }).instance.tick({ allowVoting: true });
expect(voter.upvote).toHaveBeenCalledWith('t3_one');
expect(result).toMatchObject({ kind: 'simulated', decision: { voteSubmitted: true, reason: 'Reddit upvote submitted' } });
expect(result).toMatchObject({ kind: 'simulated', decision: { voteSubmitted: true, voteStatus: 'submitted', reason: 'Reddit upvote submitted' } });
expect(repository.listRecentSelections(ACCOUNT)).toEqual([
expect.objectContaining({ thingFullname: 't3_one', voteSubmitted: true }),
expect.objectContaining({ thingFullname: 't3_one', voteSubmitted: true, voteStatus: 'submitted' }),
]);
});
@@ -206,7 +206,22 @@ describe('AccountBrowsingSimulationService', () => {
const result = await service({ voter }).instance.tick();
expect(voter.upvote).not.toHaveBeenCalled();
expect(result).toMatchObject({ kind: 'simulated', decision: { voteSubmitted: false } });
expect(result).toMatchObject({ kind: 'simulated', decision: { voteSubmitted: false, voteStatus: 'simulated' } });
});
it('persists an unsuccessful automatic vote as failed instead of mislabelling it simulated', async () => {
enable({ postWeight: 100, commentWeight: 0, autoVoteEnabled: true });
repository.setNextDueAt(ACCOUNT, NOW, NOW - 1);
const voter = { upvote: vi.fn().mockResolvedValue({ status: 'rate-limited', reason: 'slow down' }) };
const result = await service({ voter }).instance.tick({ allowVoting: true });
expect(result).toMatchObject({
kind: 'simulated', decision: { voteSubmitted: false, voteStatus: 'failed', reason: 'Reddit upvote not submitted: slow down' },
});
expect(repository.listRecentSelections(ACCOUNT)).toEqual([
expect.objectContaining({ voteStatus: 'failed', reason: 'Reddit upvote not submitted: slow down' }),
]);
});
it('rejects owned authors, outreach recipients, NSFW and cross-account duplicates', async () => {
@@ -214,7 +229,7 @@ describe('AccountBrowsingSimulationService', () => {
const otherSelection: BrowsingDecisionRecord = {
accountId: OTHER, thingFullname: 't3_taken', thingKind: 'post', postId: 'taken',
subreddit: 'AskUK', author: 'someone', permalink: '/taken', decision: 'simulated-upvote',
reason: 'test', discoveredAt: NOW - 1, decidedAt: NOW - 1, scheduledSessionId: 'other', voteSubmitted: false,
reason: 'test', discoveredAt: NOW - 1, decidedAt: NOW - 1, scheduledSessionId: 'other', voteSubmitted: false, voteStatus: 'simulated',
};
repository.recordDecision(otherSelection);
enable({ postWeight: 100, commentWeight: 0, allowNsfw: false });
@@ -244,7 +259,7 @@ describe('AccountBrowsingSimulationService', () => {
const first: BrowsingDecisionRecord = {
accountId: ACCOUNT, thingFullname: 't3_same', thingKind: 'post', postId: 'same',
subreddit: 'AskUK', author: 'author', permalink: '/same', decision: 'simulated-upvote',
reason: 'test', discoveredAt: NOW, decidedAt: NOW, scheduledSessionId: 'first', voteSubmitted: false,
reason: 'test', discoveredAt: NOW, decidedAt: NOW, scheduledSessionId: 'first', voteSubmitted: false, voteStatus: 'simulated',
};
expect(repository.recordDecision(first)).toBe(true);
expect(repository.recordDecision({ ...first, accountId: OTHER, scheduledSessionId: 'second' }))
@@ -256,7 +271,7 @@ describe('AccountBrowsingSimulationService', () => {
repository.recordDecision({
accountId: ACCOUNT, thingFullname: 't3_done', thingKind: 'post', postId: 'done',
subreddit: 'AskUK', author: 'author', permalink: '/done', decision: 'simulated-upvote',
reason: 'test', discoveredAt: NOW - 1, decidedAt: NOW - 1, scheduledSessionId: 'done', voteSubmitted: false,
reason: 'test', discoveredAt: NOW - 1, decidedAt: NOW - 1, scheduledSessionId: 'done', voteSubmitted: false, voteStatus: 'simulated',
});
repository.setNextDueAt(ACCOUNT, NOW, NOW - 1);
const fixture = service();
@@ -290,7 +305,7 @@ describe('browsing migration', () => {
runMigrations(db);
const version = db.pragma('user_version', { simple: true }) as number;
db.exec('DROP TABLE account_browsing_decisions; DROP TABLE account_browsing_settings;');
db.pragma(`user_version = ${version - 2}`);
db.pragma(`user_version = ${version - 3}`);
runMigrations(db);
insertAccount(db, ACCOUNT, 'owner');
+1 -1
View File
@@ -12,7 +12,7 @@ const OTHER_ACCOUNT = '@t2_other:reddit.com';
/** Migrations that make up account growth, replayed together in the upgrade test. */
// Browsing migrations depend on the growth tables, so a pre-growth fixture
// must rewind past both the growth sequence and the later browsing additions.
const GROWTH_MIGRATION_COUNT = 5;
const GROWTH_MIGRATION_COUNT = 6;
function thread(id: string, overrides: Partial<RedditPost> = {}): RedditPost {
return {