Add conversation image replacement

This commit is contained in:
jjbrunton committed 2026-08-14 12:56:00 +01:00
1 parent 2500f0a23c
commit 8523873831
18 files changed
+812 -3

No files matched your search

+75
View File
@@ -37,6 +37,81 @@ afterEach(async () => {
});
describe('conversations route', () => {
it('lists authoritative replacement targets and generates a private preview', async () => {
const accountId = '@t2_one:reddit.com';
const assetId = '11111111-1111-4111-8111-111111111111';
const conversation = {
username: 'alice', roomName: 'alice', roomId: 'room-one', url: '/room/room-one',
lastMessageTime: 2, lastMessageSnippet: '[Image]', isFromUs: false, unread: true,
messages: [{
sender: 'alice', text: '[Image]', timestamp: 2, isSelf: false,
media: [{ originalUrl: 'mxc://reddit.com/one', localPath: 'account/one.jpg', fileName: 'one.jpg' }],
}],
};
const asset = {
id: assetId, tags: ['selfie'], safety: 'sfw', classificationStatus: 'ready', description: 'phone template', confidence: 1,
fileName: 'green-phone.jpg', relativePath: 'green-phone.jpg', mimeType: 'image/jpeg', sizeBytes: 4,
width: 10, height: 10, enabled: true, consentConfirmedAt: 1, createdAt: 1, updatedAt: 1,
sendCount: 0, lastSentAt: null,
};
const imageReplacement = {
generate: vi.fn().mockResolvedValue({ bytes: Buffer.from('generated'), mimeType: 'image/jpeg', width: 100, height: 200 }),
};
const runtime = {
snapshot: () => ({ state: 'running' }),
resources: {
chatService: { getCachedConversations: vi.fn().mockResolvedValue([conversation]) },
messagesRepo: {
getByChatRoomId: vi.fn().mockReturnValue({ postImageUrl: 'https://i.redd.it/original.jpg' }),
getLatestByRecipient: vi.fn(),
},
},
};
const context = {
config: { reddit: { userAgent: 'test-agent' } },
logger: { error: vi.fn() },
repos: { messages: {} },
services: {
chat: {}, imageReplacement,
mediaLibrary: {
list: vi.fn().mockReturnValue([asset]),
get: vi.fn().mockReturnValue(asset),
resolvePath: vi.fn().mockReturnValue('/private/green-phone.jpg'),
},
accountRuntimes: {
list: () => [{ accountId, redditUsername: 'owner-one', state: 'running' }],
get: (requested: string) => requested === accountId ? runtime : null,
},
},
} as unknown as WebContext;
const app = express().use(express.json()).use('/api', createConversationsRouter(context));
const ownedId = encodeURIComponent(`${encodeURIComponent(accountId)}~room-one`);
const optionsResponse = await request(app, `/api/conversations/${ownedId}/image-replacement-options`);
expect(optionsResponse.status).toBe(200);
expect(await optionsResponse.json()).toMatchObject({
reason: null,
sources: [{ id: assetId, urlPath: '/library-media/green-phone.jpg' }],
targets: [
{ id: 'original-post', label: 'Original post image', urlPath: 'https://i.redd.it/original.jpg' },
{ id: 'message-0-0', urlPath: '/media/account/one.jpg' },
],
});
const generateResponse = await request(app, `/api/conversations/${ownedId}/generate-image-replacement`, {
method: 'POST', headers: { 'content-type': 'application/json' },
body: JSON.stringify({ sourceAssetId: assetId, targetId: 'message-0-0' }),
});
expect(generateResponse.status).toBe(200);
expect(await generateResponse.json()).toEqual({
imageDataUrl: `data:image/jpeg;base64,${Buffer.from('generated').toString('base64')}`,
mimeType: 'image/jpeg', fileName: 'replacement-alice.jpg', width: 100, height: 200,
});
expect(imageReplacement.generate).toHaveBeenCalledWith('/private/green-phone.jpg', {
kind: 'conversation-media', localPath: 'account/one.jpg',
});
});
it('aggregates simultaneous account runtimes and labels identical room IDs with their owner', async () => {
const chat = (username: string) => ({
username, roomName: username, roomId: 'same-room', lastMessageTime: 1,