Restructure chat service, web routes, and add test suite
Split monolithic chat service into modular structure with Matrix client support, extract web server into per-resource route modules, introduce bootstrap/main-loop separation, and add comprehensive vitest coverage across services, repositories, and utilities. Adds project docs and CLAUDE.md agent instructions.
This commit is contained in:
1 parent
dfa0580859
commit
633bb0ec04
84 files changed
+19278
-3888
No files matched your search
@@ -0,0 +1,563 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import type { Database } from 'better-sqlite3';
|
||||
import { createTestRepositories } from '../tests/testUtils.js';
|
||||
import {
|
||||
PostsRepository,
|
||||
MessagesRepository,
|
||||
SuccessfulMessagesRepository,
|
||||
} from '../src/database/repositories.js';
|
||||
|
||||
describe('PostsRepository', () => {
|
||||
let postsRepo: PostsRepository;
|
||||
let messagesRepo: MessagesRepository;
|
||||
let db: Database;
|
||||
|
||||
beforeEach(() => {
|
||||
const repos = createTestRepositories();
|
||||
postsRepo = repos.postsRepo;
|
||||
messagesRepo = repos.messagesRepo;
|
||||
db = repos.db;
|
||||
});
|
||||
|
||||
it('inserts and retrieves a post by id', () => {
|
||||
postsRepo.insert({
|
||||
id: 'post1',
|
||||
subreddit: 'test',
|
||||
author: 'user1',
|
||||
title: 'Test Post',
|
||||
imageUrl: 'http://example.com/img.jpg',
|
||||
processedAt: Date.now(),
|
||||
});
|
||||
|
||||
const post = postsRepo.getById('post1');
|
||||
expect(post).toBeDefined();
|
||||
expect(post!.id).toBe('post1');
|
||||
expect(post!.subreddit).toBe('test');
|
||||
expect(post!.author).toBe('user1');
|
||||
expect(post!.title).toBe('Test Post');
|
||||
expect(post!.imageUrl).toBe('http://example.com/img.jpg');
|
||||
expect(post!.messageSent).toBe(false);
|
||||
expect(post!.imageAnalysis).toBeNull();
|
||||
expect(post!.error).toBeNull();
|
||||
});
|
||||
|
||||
it('checks if a post is processed', () => {
|
||||
expect(postsRepo.isProcessed('nonexistent')).toBe(false);
|
||||
|
||||
postsRepo.insert({
|
||||
id: 'post2',
|
||||
subreddit: 'test',
|
||||
author: 'user2',
|
||||
title: 'Another Post',
|
||||
imageUrl: null,
|
||||
processedAt: Date.now(),
|
||||
});
|
||||
|
||||
expect(postsRepo.isProcessed('post2')).toBe(true);
|
||||
});
|
||||
|
||||
it('marks a message as sent', () => {
|
||||
postsRepo.insert({
|
||||
id: 'post3',
|
||||
subreddit: 'test',
|
||||
author: 'user3',
|
||||
title: 'Sent Post',
|
||||
imageUrl: 'http://example.com/img.jpg',
|
||||
processedAt: Date.now(),
|
||||
});
|
||||
|
||||
postsRepo.markMessageSent('post3');
|
||||
const post = postsRepo.getById('post3');
|
||||
expect(post!.messageSent).toBe(true);
|
||||
});
|
||||
|
||||
it('updates analysis and generated message', () => {
|
||||
postsRepo.insert({
|
||||
id: 'post4',
|
||||
subreddit: 'test',
|
||||
author: 'user4',
|
||||
title: 'Analysis Post',
|
||||
imageUrl: 'http://example.com/img.jpg',
|
||||
processedAt: Date.now(),
|
||||
});
|
||||
|
||||
postsRepo.updateAnalysis('post4', 'Beautiful woman with blonde hair', 'Hey gorgeous...');
|
||||
const post = postsRepo.getById('post4');
|
||||
expect(post!.imageAnalysis).toBe('Beautiful woman with blonde hair');
|
||||
expect(post!.generatedMessage).toBe('Hey gorgeous...');
|
||||
});
|
||||
|
||||
it('sets error on a post', () => {
|
||||
postsRepo.insert({
|
||||
id: 'post5',
|
||||
subreddit: 'test',
|
||||
author: 'user5',
|
||||
title: 'Error Post',
|
||||
imageUrl: 'http://example.com/img.jpg',
|
||||
processedAt: Date.now(),
|
||||
});
|
||||
|
||||
postsRepo.setError('post5', 'Failed to download image');
|
||||
const post = postsRepo.getById('post5');
|
||||
expect(post!.error).toBe('Failed to download image');
|
||||
});
|
||||
|
||||
it('gets posts by author (case insensitive)', () => {
|
||||
postsRepo.insert({ id: 'p1', subreddit: 'a', author: 'TestUser', title: 'T1', imageUrl: null, processedAt: Date.now() });
|
||||
postsRepo.insert({ id: 'p2', subreddit: 'b', author: 'testuser', title: 'T2', imageUrl: null, processedAt: Date.now() });
|
||||
postsRepo.insert({ id: 'p3', subreddit: 'c', author: 'TESTUSER', title: 'T3', imageUrl: null, processedAt: Date.now() });
|
||||
|
||||
const results = postsRepo.getByAuthor('testuser');
|
||||
expect(results.length).toBe(3);
|
||||
// Should be ordered by processed_at DESC
|
||||
expect(results[0].id).toBe('p3');
|
||||
expect(results[2].id).toBe('p1');
|
||||
});
|
||||
|
||||
it('limits getByAuthor to 10 results', () => {
|
||||
for (let i = 0; i < 15; i++) {
|
||||
postsRepo.insert({
|
||||
id: `pa${i}`,
|
||||
subreddit: 'test',
|
||||
author: 'bulkuser',
|
||||
title: `Bulk ${i}`,
|
||||
imageUrl: null,
|
||||
processedAt: Date.now() + i,
|
||||
});
|
||||
}
|
||||
const results = postsRepo.getByAuthor('bulkuser');
|
||||
expect(results.length).toBe(10);
|
||||
});
|
||||
|
||||
it('returns undefined for non-existent post', () => {
|
||||
expect(postsRepo.getById('nonexistent')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('maps database columns to camelCase correctly', () => {
|
||||
postsRepo.insert({
|
||||
id: 'post6',
|
||||
subreddit: 'camelcase',
|
||||
author: 'user6',
|
||||
title: 'Camel Case Test',
|
||||
imageUrl: 'http://example.com/test.png',
|
||||
processedAt: 1234567890,
|
||||
});
|
||||
|
||||
const post = postsRepo.getById('post6');
|
||||
expect(post).toMatchObject({
|
||||
id: 'post6',
|
||||
subreddit: 'camelcase',
|
||||
author: 'user6',
|
||||
title: 'Camel Case Test',
|
||||
imageUrl: 'http://example.com/test.png',
|
||||
processedAt: 1234567890,
|
||||
messageSent: false,
|
||||
imageAnalysis: null,
|
||||
generatedMessage: null,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('MessagesRepository', () => {
|
||||
let postsRepo: PostsRepository;
|
||||
let messagesRepo: MessagesRepository;
|
||||
let db: Database;
|
||||
|
||||
beforeEach(() => {
|
||||
const repos = createTestRepositories();
|
||||
postsRepo = repos.postsRepo;
|
||||
messagesRepo = repos.messagesRepo;
|
||||
db = repos.db;
|
||||
});
|
||||
|
||||
it('inserts and retrieves a message', () => {
|
||||
const messageId = messagesRepo.insert({
|
||||
postId: 'post1',
|
||||
recipient: 'recipient1',
|
||||
subject: 'Hello',
|
||||
message: 'Hey there!',
|
||||
sentAt: Date.now(),
|
||||
strategyId: 'variant-a',
|
||||
});
|
||||
|
||||
expect(messageId).toBeGreaterThan(0);
|
||||
|
||||
const msg = messagesRepo.getByPostId('post1');
|
||||
expect(msg).toBeDefined();
|
||||
expect(msg!.postId).toBe('post1');
|
||||
expect(msg!.recipient).toBe('recipient1');
|
||||
expect(msg!.subject).toBe('Hello');
|
||||
expect(msg!.message).toBe('Hey there!');
|
||||
expect(msg!.strategyId).toBe('variant-a');
|
||||
expect(msg!.gotResponse).toBe(false);
|
||||
});
|
||||
|
||||
it('retrieves recent messages', () => {
|
||||
const baseTime = Date.now();
|
||||
for (let i = 0; i < 5; i++) {
|
||||
messagesRepo.insert({
|
||||
postId: `post${i}`,
|
||||
recipient: `recipient${i}`,
|
||||
subject: `Subject ${i}`,
|
||||
message: `Message ${i}`,
|
||||
// Later i => more recent sentAt, so msg4 should come back first.
|
||||
sentAt: baseTime + i * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
const recent = messagesRepo.getRecent(3);
|
||||
expect(recent.length).toBe(3);
|
||||
// Should be ordered by sent_at DESC
|
||||
expect(recent[0].recipient).toBe('recipient4');
|
||||
});
|
||||
|
||||
it('marks a message as having gotten a response', () => {
|
||||
messagesRepo.insert({
|
||||
postId: 'post1',
|
||||
recipient: 'recipient1',
|
||||
subject: 'Hello',
|
||||
message: 'Hey!',
|
||||
sentAt: Date.now(),
|
||||
});
|
||||
|
||||
const msg = messagesRepo.getByPostId('post1');
|
||||
expect(msg!.gotResponse).toBe(false);
|
||||
|
||||
messagesRepo.markGotResponse(msg!.id);
|
||||
const updated = messagesRepo.getByPostId('post1');
|
||||
expect(updated!.gotResponse).toBe(true);
|
||||
expect(updated!.responseCheckedAt).toBeDefined();
|
||||
});
|
||||
|
||||
it('marks a message as checked without response', () => {
|
||||
messagesRepo.insert({
|
||||
postId: 'post1',
|
||||
recipient: 'recipient1',
|
||||
subject: 'Hello',
|
||||
message: 'Hey!',
|
||||
sentAt: Date.now(),
|
||||
});
|
||||
|
||||
const msg = messagesRepo.getByPostId('post1');
|
||||
messagesRepo.markChecked(msg!.id);
|
||||
|
||||
const updated = messagesRepo.getByPostId('post1');
|
||||
expect(updated!.gotResponse).toBe(false);
|
||||
expect(updated!.responseCheckedAt).toBeDefined();
|
||||
});
|
||||
|
||||
it('gets the latest sent message by recipient with joined post info', () => {
|
||||
postsRepo.insert({
|
||||
id: 'post-a',
|
||||
subreddit: 'gonewild',
|
||||
author: 'alice',
|
||||
title: 'First post',
|
||||
imageUrl: null,
|
||||
processedAt: Date.now(),
|
||||
});
|
||||
postsRepo.insert({
|
||||
id: 'post-b',
|
||||
subreddit: 'gonewild',
|
||||
author: 'alice',
|
||||
title: 'Second post',
|
||||
imageUrl: null,
|
||||
processedAt: Date.now(),
|
||||
});
|
||||
|
||||
const base = Date.now();
|
||||
messagesRepo.insert({ postId: 'post-a', recipient: 'alice', subject: 's', message: 'm', sentAt: base });
|
||||
messagesRepo.insert({ postId: 'post-b', recipient: 'alice', subject: 's', message: 'm', sentAt: base + 1000 });
|
||||
|
||||
const latest = messagesRepo.getLatestByRecipient('alice');
|
||||
expect(latest).toBeDefined();
|
||||
expect(latest!.postId).toBe('post-b');
|
||||
expect(latest!.subreddit).toBe('gonewild');
|
||||
expect(latest!.postTitle).toBe('Second post');
|
||||
|
||||
expect(messagesRepo.getLatestByRecipient('ALICE')).toBeDefined();
|
||||
expect(messagesRepo.getLatestByRecipient('nobody')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('checks if a message has been sent to a recipient', () => {
|
||||
expect(messagesRepo.hasSentTo('someone')).toBe(false);
|
||||
|
||||
messagesRepo.insert({
|
||||
postId: 'post1',
|
||||
recipient: 'recipient1',
|
||||
subject: 'Hello',
|
||||
message: 'Hey!',
|
||||
sentAt: Date.now(),
|
||||
});
|
||||
|
||||
expect(messagesRepo.hasSentTo('recipient1')).toBe(true);
|
||||
expect(messagesRepo.hasSentTo('someone')).toBe(false);
|
||||
});
|
||||
|
||||
it('gets strategy stats from database', () => {
|
||||
const now = Date.now();
|
||||
|
||||
messagesRepo.insert({ postId: 'p1', recipient: 'r1', subject: 's', message: 'm', sentAt: now, strategyId: 'variant-a' });
|
||||
messagesRepo.insert({ postId: 'p2', recipient: 'r2', subject: 's', message: 'm', sentAt: now, strategyId: 'variant-a' });
|
||||
messagesRepo.insert({ postId: 'p3', recipient: 'r3', subject: 's', message: 'm', sentAt: now, strategyId: 'variant-b' });
|
||||
|
||||
messagesRepo.markGotResponse(1);
|
||||
messagesRepo.markGotResponse(3);
|
||||
|
||||
const stats = messagesRepo.getStrategyStats();
|
||||
expect(stats.length).toBe(2);
|
||||
|
||||
const variantA = stats.find(s => s.strategyId === 'variant-a');
|
||||
expect(variantA!.totalSent).toBe(2);
|
||||
expect(variantA!.totalResponses).toBe(1);
|
||||
expect(variantA!.responseRate).toBe(50);
|
||||
|
||||
const variantB = stats.find(s => s.strategyId === 'variant-b');
|
||||
expect(variantB!.totalSent).toBe(1);
|
||||
expect(variantB!.totalResponses).toBe(1);
|
||||
expect(variantB!.responseRate).toBe(100);
|
||||
});
|
||||
|
||||
it('excludes messages without strategy_id from stats', () => {
|
||||
messagesRepo.insert({ postId: 'p1', recipient: 'r1', subject: 's', message: 'm', sentAt: Date.now() });
|
||||
|
||||
const stats = messagesRepo.getStrategyStats();
|
||||
expect(stats.length).toBe(0);
|
||||
});
|
||||
|
||||
it('calculates 0 response rate when no messages sent', () => {
|
||||
// No messages inserted
|
||||
const stats = messagesRepo.getStrategyStats();
|
||||
expect(stats.length).toBe(0);
|
||||
});
|
||||
|
||||
it('gets unchecked messages with correct time filters', () => {
|
||||
const now = Date.now();
|
||||
|
||||
// Old message (2 hours ago, no check) - should be returned
|
||||
messagesRepo.insert({ postId: 'p1', recipient: 'r1', subject: 's', message: 'm', sentAt: now - 2 * 3600000 });
|
||||
|
||||
// Recent message (30 min ago) - should NOT be returned (too young)
|
||||
messagesRepo.insert({ postId: 'p2', recipient: 'r2', subject: 's', message: 'm', sentAt: now - 30 * 60000 });
|
||||
|
||||
// Already checked message - should NOT be returned
|
||||
messagesRepo.insert({ postId: 'p3', recipient: 'r3', subject: 's', message: 'm', sentAt: now - 2 * 3600000 });
|
||||
messagesRepo.markChecked(3);
|
||||
|
||||
const unchecked = messagesRepo.getUncheckedMessages(60 * 60 * 1000, 6 * 60 * 60 * 1000, 10);
|
||||
expect(unchecked.length).toBe(1);
|
||||
expect(unchecked[0].postId).toBe('p1');
|
||||
});
|
||||
|
||||
it('maps sent_at as number and response_checked_at as number | null', () => {
|
||||
messagesRepo.insert({
|
||||
postId: 'post1',
|
||||
recipient: 'r1',
|
||||
subject: 's',
|
||||
message: 'm',
|
||||
sentAt: 1234567890,
|
||||
});
|
||||
|
||||
const msg = messagesRepo.getByPostId('post1');
|
||||
expect(typeof msg!.sentAt).toBe('number');
|
||||
expect(msg!.responseCheckedAt).toBeNull();
|
||||
});
|
||||
|
||||
it('returns optional fields as undefined when not present', () => {
|
||||
messagesRepo.insert({
|
||||
postId: 'post1',
|
||||
recipient: 'r1',
|
||||
subject: 's',
|
||||
message: 'm',
|
||||
sentAt: Date.now(),
|
||||
});
|
||||
|
||||
const msg = messagesRepo.getByPostId('post1');
|
||||
expect(msg!.subreddit).toBeUndefined();
|
||||
expect(msg!.postTitle).toBeUndefined();
|
||||
expect(msg!.postImageUrl).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SuccessfulMessagesRepository', () => {
|
||||
let successfulMessagesRepo: SuccessfulMessagesRepository;
|
||||
let db: Database;
|
||||
|
||||
beforeEach(() => {
|
||||
const repos = createTestRepositories();
|
||||
successfulMessagesRepo = repos.successfulMessagesRepo;
|
||||
db = repos.db;
|
||||
});
|
||||
|
||||
it('inserts and retrieves a successful message', () => {
|
||||
successfulMessagesRepo.insert({
|
||||
sentMessageId: 1,
|
||||
originalMessage: 'Hey gorgeous!',
|
||||
imageAnalysis: 'Blonde woman, rating 8/10',
|
||||
postTitle: 'My selfie',
|
||||
responseSnippet: 'Aww thanks!',
|
||||
subreddit: 'gonewild',
|
||||
});
|
||||
|
||||
const recent = successfulMessagesRepo.getRecent(5);
|
||||
expect(recent.length).toBe(1);
|
||||
expect(recent[0].originalMessage).toBe('Hey gorgeous!');
|
||||
expect(recent[0].imageAnalysis).toBe('Blonde woman, rating 8/10');
|
||||
expect(recent[0].postTitle).toBe('My selfie');
|
||||
expect(recent[0].responseSnippet).toBe('Aww thanks!');
|
||||
expect(recent[0].subreddit).toBe('gonewild');
|
||||
});
|
||||
|
||||
it('checks if a message already exists', () => {
|
||||
successfulMessagesRepo.insert({
|
||||
sentMessageId: 1,
|
||||
originalMessage: 'Unique message',
|
||||
imageAnalysis: null,
|
||||
postTitle: null,
|
||||
responseSnippet: null,
|
||||
subreddit: null,
|
||||
});
|
||||
|
||||
expect(successfulMessagesRepo.existsByMessage('Unique message')).toBe(true);
|
||||
expect(successfulMessagesRepo.existsByMessage('Non-existent')).toBe(false);
|
||||
});
|
||||
|
||||
it('gets weighted examples for a subreddit', () => {
|
||||
successfulMessagesRepo.insert({ sentMessageId: 1, originalMessage: 'Msg 1', imageAnalysis: null, postTitle: null, responseSnippet: null, subreddit: 'gonewild' });
|
||||
successfulMessagesRepo.insert({ sentMessageId: 2, originalMessage: 'Msg 2', imageAnalysis: null, postTitle: null, responseSnippet: null, subreddit: 'gonewild' });
|
||||
successfulMessagesRepo.insert({ sentMessageId: 3, originalMessage: 'Msg 3', imageAnalysis: null, postTitle: null, responseSnippet: null, subreddit: 'homemadesfW' });
|
||||
|
||||
const examples = successfulMessagesRepo.getWeightedExamples('gonewild', 3);
|
||||
// Returns up to `limit` examples weighted toward the target subreddit but
|
||||
// including off-subreddit fallback rows when there's headroom.
|
||||
expect(examples.length).toBe(3);
|
||||
expect(examples[0].subreddit).toBe('gonewild');
|
||||
expect(examples[1].subreddit).toBe('gonewild');
|
||||
});
|
||||
|
||||
it('falls back to recent when no subreddit match', () => {
|
||||
successfulMessagesRepo.insert({ sentMessageId: 1, originalMessage: 'Msg 1', imageAnalysis: null, postTitle: null, responseSnippet: null, subreddit: 'gonewild' });
|
||||
successfulMessagesRepo.insert({ sentMessageId: 2, originalMessage: 'Msg 2', imageAnalysis: null, postTitle: null, responseSnippet: null, subreddit: 'other' });
|
||||
|
||||
const examples = successfulMessagesRepo.getWeightedExamples('nonexistent', 3);
|
||||
expect(examples.length).toBe(2);
|
||||
});
|
||||
|
||||
it('counts successful messages', () => {
|
||||
expect(successfulMessagesRepo.count()).toBe(0);
|
||||
|
||||
successfulMessagesRepo.insert({ sentMessageId: 1, originalMessage: 'Msg 1', imageAnalysis: null, postTitle: null, responseSnippet: null, subreddit: null });
|
||||
successfulMessagesRepo.insert({ sentMessageId: 2, originalMessage: 'Msg 2', imageAnalysis: null, postTitle: null, responseSnippet: null, subreddit: null });
|
||||
|
||||
expect(successfulMessagesRepo.count()).toBe(2);
|
||||
});
|
||||
|
||||
it('handles null fields correctly', () => {
|
||||
successfulMessagesRepo.insert({
|
||||
sentMessageId: null,
|
||||
originalMessage: 'No context',
|
||||
imageAnalysis: null,
|
||||
postTitle: null,
|
||||
responseSnippet: null,
|
||||
subreddit: null,
|
||||
});
|
||||
|
||||
const recent = successfulMessagesRepo.getRecent(1);
|
||||
expect(recent[0].sentMessageId).toBeNull();
|
||||
expect(recent[0].imageAnalysis).toBeNull();
|
||||
expect(recent[0].postTitle).toBeNull();
|
||||
expect(recent[0].responseSnippet).toBeNull();
|
||||
expect(recent[0].subreddit).toBeNull();
|
||||
});
|
||||
|
||||
it('returns most recent examples first', () => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
successfulMessagesRepo.insert({
|
||||
sentMessageId: i,
|
||||
originalMessage: `Msg ${i}`,
|
||||
imageAnalysis: null,
|
||||
postTitle: null,
|
||||
responseSnippet: null,
|
||||
subreddit: null,
|
||||
});
|
||||
}
|
||||
|
||||
const recent = successfulMessagesRepo.getRecent(3);
|
||||
expect(recent[0].originalMessage).toBe('Msg 4');
|
||||
expect(recent[2].originalMessage).toBe('Msg 2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Repository edge cases', () => {
|
||||
it('handles special characters in post titles', () => {
|
||||
const repos = createTestRepositories();
|
||||
repos.postsRepo.insert({
|
||||
id: 'special',
|
||||
subreddit: 'test',
|
||||
author: "user's \"quotes\"",
|
||||
title: 'Title with <html> & "special" chars',
|
||||
imageUrl: null,
|
||||
processedAt: Date.now(),
|
||||
});
|
||||
|
||||
const post = repos.postsRepo.getById('special');
|
||||
expect(post!.author).toBe("user's \"quotes\"");
|
||||
expect(post!.title).toBe('Title with <html> & "special" chars');
|
||||
});
|
||||
|
||||
it('handles very long messages', () => {
|
||||
const repos = createTestRepositories();
|
||||
const longMessage = 'x'.repeat(10000);
|
||||
|
||||
repos.messagesRepo.insert({
|
||||
postId: 'post1',
|
||||
recipient: 'r1',
|
||||
subject: 's',
|
||||
message: longMessage,
|
||||
sentAt: Date.now(),
|
||||
});
|
||||
|
||||
const msg = repos.messagesRepo.getByPostId('post1');
|
||||
expect(msg!.message.length).toBe(10000);
|
||||
});
|
||||
|
||||
it('handles empty string fields', () => {
|
||||
const repos = createTestRepositories();
|
||||
repos.postsRepo.insert({
|
||||
id: 'empty',
|
||||
subreddit: '',
|
||||
author: '',
|
||||
title: '',
|
||||
imageUrl: '',
|
||||
processedAt: Date.now(),
|
||||
});
|
||||
|
||||
const post = repos.postsRepo.getById('empty');
|
||||
expect(post!.subreddit).toBe('');
|
||||
expect(post!.author).toBe('');
|
||||
expect(post!.title).toBe('');
|
||||
});
|
||||
|
||||
it('handles duplicate post insert (should throw due to PRIMARY KEY)', () => {
|
||||
const repos = createTestRepositories();
|
||||
repos.postsRepo.insert({ id: 'dup', subreddit: 't', author: 'a', title: 't', imageUrl: null, processedAt: Date.now() });
|
||||
|
||||
expect(() => {
|
||||
repos.postsRepo.insert({ id: 'dup', subreddit: 't', author: 'a', title: 't', imageUrl: null, processedAt: Date.now() });
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('handles messages with strategy_id null', () => {
|
||||
const repos = createTestRepositories();
|
||||
const id = repos.messagesRepo.insert({
|
||||
postId: 'p1',
|
||||
recipient: 'r1',
|
||||
subject: 's',
|
||||
message: 'm',
|
||||
sentAt: Date.now(),
|
||||
});
|
||||
|
||||
const msg = repos.messagesRepo.getByPostId('p1');
|
||||
expect(msg!.strategyId).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user