Enable automatic browsing upvotes

This commit is contained in:
jjbrunton committed 2026-08-11 21:52:54 +01:00
1 parent 27012399ec
commit 7bf1aa9d24
26 files changed
+339 -57

No files matched your search

+43 -4
View File
@@ -79,6 +79,7 @@ describe('AccountBrowsingSimulationService', () => {
posts?: RedditPost[];
comments?: BrowsingThing[];
random?: number;
voter?: { upvote: ReturnType<typeof vi.fn> };
} = {}) {
const getHotPosts = vi.fn().mockResolvedValue(options.posts ?? [post('one')]);
const getBrowsingComments = vi.fn().mockResolvedValue(options.comments ?? []);
@@ -88,6 +89,7 @@ describe('AccountBrowsingSimulationService', () => {
growthSubreddits: () => [{ subreddit: 'AskUK', weight: 1 }] as never,
reddit: { getHotPosts, getBrowsingComments },
sink: repository,
voter: options.voter,
logger: createMockLogger(),
now: () => NOW,
random: () => options.random ?? 0,
@@ -141,6 +143,18 @@ describe('AccountBrowsingSimulationService', () => {
]);
});
it('runs a requested simulation immediately without submitting a vote', async () => {
enable({ postWeight: 100, commentWeight: 0 });
const fixture = service();
const result = await fixture.instance.runNow();
expect(result).toMatchObject({
kind: 'simulated', decision: { thingFullname: 't3_one', voteSubmitted: false },
});
expect(fixture.getHotPosts).toHaveBeenCalledOnce();
});
it('honours comment weighting when eligible comments exist', async () => {
enable({ postWeight: 0, commentWeight: 100 });
repository.setNextDueAt(ACCOUNT, NOW, NOW - 1);
@@ -152,12 +166,37 @@ describe('AccountBrowsingSimulationService', () => {
});
});
it('submits the selected upvote only when the automatic action is enabled and allowed', async () => {
enable({ postWeight: 100, commentWeight: 0, autoVoteEnabled: true });
repository.setNextDueAt(ACCOUNT, NOW, NOW - 1);
const voter = { upvote: vi.fn().mockResolvedValue({ status: 'submitted' }) };
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(repository.listRecentSelections(ACCOUNT)).toEqual([
expect.objectContaining({ thingFullname: 't3_one', voteSubmitted: true }),
]);
});
it('keeps automatic voting disabled during a simulation-only pass', async () => {
enable({ postWeight: 100, commentWeight: 0, autoVoteEnabled: true });
repository.setNextDueAt(ACCOUNT, NOW, NOW - 1);
const voter = { upvote: vi.fn().mockResolvedValue({ status: 'submitted' }) };
const result = await service({ voter }).instance.tick();
expect(voter.upvote).not.toHaveBeenCalled();
expect(result).toMatchObject({ kind: 'simulated', decision: { voteSubmitted: false } });
});
it('rejects owned authors, outreach recipients, NSFW and cross-account duplicates', async () => {
db.prepare(`INSERT INTO recipient_suppressions (recipient, reason, created_at) VALUES ('contacted', 'test', 0)`).run();
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',
reason: 'test', discoveredAt: NOW - 1, decidedAt: NOW - 1, scheduledSessionId: 'other', voteSubmitted: false,
};
repository.recordDecision(otherSelection);
enable({ postWeight: 100, commentWeight: 0, allowNsfw: false });
@@ -187,7 +226,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',
reason: 'test', discoveredAt: NOW, decidedAt: NOW, scheduledSessionId: 'first', voteSubmitted: false,
};
expect(repository.recordDecision(first)).toBe(true);
expect(repository.recordDecision({ ...first, accountId: OTHER, scheduledSessionId: 'second' }))
@@ -199,7 +238,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',
reason: 'test', discoveredAt: NOW - 1, decidedAt: NOW - 1, scheduledSessionId: 'done', voteSubmitted: false,
});
repository.setNextDueAt(ACCOUNT, NOW, NOW - 1);
const fixture = service();
@@ -233,7 +272,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 - 1}`);
db.pragma(`user_version = ${version - 2}`);
runMigrations(db);
insertAccount(db, ACCOUNT, 'owner');