Improve Reddit chat and media reliability

This commit is contained in:
jjbrunton committed 2026-08-08 20:37:25 +01:00
1 parent a44768c089
commit 345ec99348
14 files changed
+323 -676

No files matched your search

+136 -1
View File
@@ -85,7 +85,7 @@ describe('ChatService text delta sync', () => {
const [conversation] = await service.getCachedConversations();
expect(conversation.messages).toEqual([
{ sender: 'alice', text: 'hello', timestamp: now - 1_000, isSelf: false },
{ sender: 'Me', text: 'AI-assisted reply', timestamp: now, isSelf: true },
{ eventId: '$sent', sender: 'Me', text: 'AI-assisted reply', timestamp: now, isSelf: true },
]);
const persisted = JSON.parse(await fs.readFile(cacheFile, 'utf-8')) as {
@@ -93,4 +93,139 @@ describe('ChatService text delta sync', () => {
};
expect(persisted.chats[0].messages).toHaveLength(2);
});
it('reconciles an optimistic opening message with its Matrix event', async () => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'chat-text-reconcile-'));
temporaryDirectories.push(directory);
const cacheFile = path.join(directory, 'chat-cache.json');
const serverTimestamp = Date.now();
await fs.writeFile(cacheFile, JSON.stringify({
version: 1,
savedAt: serverTimestamp,
chats: [{
username: 'alice',
roomName: 'alice',
roomId: '!alice:example.test',
lastMessageTime: serverTimestamp + 80,
lastMessageSnippet: 'hello',
isFromUs: true,
unread: false,
messages: [{
eventId: '$opening',
sender: 'Me',
text: 'hello',
timestamp: serverTimestamp + 80,
isSelf: true,
}],
}],
nextBatch: 's1',
lastDeltaSyncAt: serverTimestamp,
}), 'utf-8');
const logger = createMockLogger();
const matrix = new MatrixClient({
homeserver: 'https://matrix.example.test',
accessToken: 'token',
userId: '@me:example.test',
capturedAt: serverTimestamp,
}, logger, { minRequestIntervalMs: 0 });
matrix.sync = vi.fn(async () => ({
next_batch: 's2',
rooms: {
join: {
'!alice:example.test': {
timeline: {
events: [{
event_id: '$opening',
type: 'm.room.message',
sender: '@me:example.test',
origin_server_ts: serverTimestamp,
content: { msgtype: 'm.text', body: 'hello' },
}],
},
},
},
},
})) as typeof matrix.sync;
const service = new ChatService(
matrix,
logger,
undefined,
undefined,
undefined,
undefined,
new ChatCache(logger, cacheFile),
undefined,
undefined,
authenticatedAccountAuth,
);
await service.loadCache();
await service.refreshTopChats();
const [conversation] = await service.getCachedConversations();
expect(conversation.messages).toEqual([{
eventId: '$opening',
sender: 'Me',
text: 'hello',
timestamp: serverTimestamp,
isSelf: true,
}]);
});
it('repairs a legacy cached opening-message duplicate', async () => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'chat-text-legacy-repair-'));
temporaryDirectories.push(directory);
const cacheFile = path.join(directory, 'chat-cache.json');
const serverTimestamp = Date.now();
const message = { sender: 'Me', text: 'hello', isSelf: true };
await fs.writeFile(cacheFile, JSON.stringify({
version: 1,
savedAt: serverTimestamp,
chats: [{
username: 'alice',
roomName: 'alice',
roomId: '!alice:example.test',
lastMessageTime: serverTimestamp,
lastMessageSnippet: 'hello',
isFromUs: true,
unread: false,
messages: [
{ ...message, timestamp: serverTimestamp + 80 },
{ ...message, timestamp: serverTimestamp },
],
}],
nextBatch: 's1',
lastDeltaSyncAt: serverTimestamp,
}), 'utf-8');
const logger = createMockLogger();
const matrix = new MatrixClient({
homeserver: 'https://matrix.example.test',
accessToken: 'token',
userId: '@me:example.test',
capturedAt: serverTimestamp,
}, logger, { minRequestIntervalMs: 0 });
const service = new ChatService(
matrix,
logger,
undefined,
undefined,
undefined,
undefined,
new ChatCache(logger, cacheFile),
undefined,
undefined,
authenticatedAccountAuth,
);
await service.loadCache();
const [conversation] = await service.getCachedConversations();
expect(conversation.messages).toEqual([{ ...message, timestamp: serverTimestamp }]);
const persisted = JSON.parse(await fs.readFile(cacheFile, 'utf-8')) as {
chats: Array<{ messages: unknown[] }>;
};
expect(persisted.chats[0].messages).toHaveLength(1);
});
});
@@ -94,6 +94,9 @@ describe('ChatService username persistence', () => {
const chat = await svc.startConversation('alice', 'hello', 't2_abc123');
expect(chat?.username).toBe('alice');
expect(chat?.messages).toEqual([
expect.objectContaining({ eventId: '$event', text: 'hello', isSelf: true }),
]);
expect(usernameResolver.resolveUsernameToT2).not.toHaveBeenCalled();
expect(matrix.sendInitialDirectMessage).toHaveBeenCalledWith(
'@t2_abc123:reddit.com',
+32
View File
@@ -1,6 +1,7 @@
import express from 'express';
import type { AddressInfo } from 'node:net';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { MediaResponseSchema } from '../../packages/api-contracts/src/media.js';
import type { WebContext } from '../../src/web/context.js';
import { createMediaRouter } from '../../src/web/routes/media.js';
@@ -63,4 +64,35 @@ describe('media route username presentation', () => {
expect(JSON.stringify(body)).not.toContain('t2_missing');
expect(body[0]).toMatchObject({ chatUsername: null, sender: 'Unknown Reddit user' });
});
it('keeps the internal account ID out of the public media item', async () => {
const context = {
logger: { error: vi.fn() },
services: {
media: { getMediaItems: vi.fn().mockResolvedValue([{
accountId: 't2_owner', id: 'one', chatUsername: 'alice', chatRoomId: 'room-one', sender: 'alice',
timestamp: 1, originalUrl: 'mxc://one', fileName: 'one.jpg', relativePath: 'one.jpg', createdAt: 2,
}]) },
chat: {
getCachedConversations: vi.fn().mockResolvedValue([]),
resolveSenderDisplay: vi.fn((identity: string) => identity),
},
redditAccounts: {
get: vi.fn().mockReturnValue({ accountId: 't2_owner', redditUsername: 'owner' }),
},
},
} as unknown as WebContext;
const app = express().use('/api', createMediaRouter(context));
const response = await request(app, '/api/media');
const body: unknown = await response.json();
expect(MediaResponseSchema.parse(body)).toEqual([
expect.objectContaining({
id: 'one',
owner: { accountId: 't2_owner', redditUsername: 'owner' },
}),
]);
expect(body).not.toEqual([expect.objectContaining({ accountId: expect.anything() })]);
});
});