132 lines
5.3 KiB
TypeScript
132 lines
5.3 KiB
TypeScript
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';
|
|
|
|
const servers: Array<ReturnType<express.Express['listen']>> = [];
|
|
|
|
async function request(app: express.Express, path: string): Promise<Response> {
|
|
const server = app.listen(0);
|
|
servers.push(server);
|
|
await new Promise<void>((resolve) => server.once('listening', resolve));
|
|
const { port } = server.address() as AddressInfo;
|
|
return fetch(`http://127.0.0.1:${port}${path}`);
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(servers.splice(0).map((server) => new Promise<void>((resolve) => server.close(() => resolve()))));
|
|
});
|
|
|
|
describe('media route username presentation', () => {
|
|
it('lists received media while excluding items sent by the owning account', async () => {
|
|
const mediaItem = (id: string, sender: string) => ({
|
|
accountId: 't2_owner', id, chatUsername: 'alice', chatRoomId: 'room-one', sender,
|
|
timestamp: 1, originalUrl: `mxc://${id}`, fileName: `${id}.jpg`, relativePath: `${id}.jpg`, createdAt: 2,
|
|
});
|
|
const context = {
|
|
logger: { error: vi.fn() },
|
|
services: {
|
|
media: { getMediaItems: vi.fn().mockResolvedValue([
|
|
mediaItem('received', 'alice'),
|
|
mediaItem('sent-marker', 'Me'),
|
|
mediaItem('sent-matrix-id', '@t2_owner:reddit.com'),
|
|
mediaItem('sent-username', 'owner'),
|
|
]) },
|
|
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');
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(await response.json()).toEqual([
|
|
expect.objectContaining({ id: 'received', sender: 'alice' }),
|
|
]);
|
|
});
|
|
|
|
it('upgrades historical t2 fields through the chat username map', async () => {
|
|
const context = {
|
|
logger: { error: vi.fn() },
|
|
services: {
|
|
media: { getMediaItems: vi.fn().mockResolvedValue([{
|
|
id: 'one', chatUsername: 't2_known', chatRoomId: 'room-one', sender: 't2_known',
|
|
timestamp: 1, originalUrl: 'mxc://one', fileName: 'one.jpg', relativePath: 'one.jpg', createdAt: 2,
|
|
}]) },
|
|
chat: {
|
|
getCachedConversations: vi.fn().mockResolvedValue([]),
|
|
resolveSenderDisplay: vi.fn((identity: string) => identity === 't2_known' ? 'alice' : identity),
|
|
},
|
|
},
|
|
} as unknown as WebContext;
|
|
const app = express().use('/api', createMediaRouter(context));
|
|
|
|
const response = await request(app, '/api/media');
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(await response.json()).toEqual([expect.objectContaining({ chatUsername: 'alice', sender: 'alice' })]);
|
|
});
|
|
|
|
it('uses a safe fallback when no username mapping exists', async () => {
|
|
const context = {
|
|
logger: { error: vi.fn() },
|
|
services: {
|
|
media: { getMediaItems: vi.fn().mockResolvedValue([{
|
|
id: 'one', chatUsername: 't2_missing', chatRoomId: 'room-one', sender: '@t2_missing:reddit.com',
|
|
timestamp: 1, originalUrl: 'mxc://one', fileName: 'one.jpg', relativePath: 'one.jpg', createdAt: 2,
|
|
}]) },
|
|
chat: {
|
|
getCachedConversations: vi.fn().mockResolvedValue([]),
|
|
resolveSenderDisplay: vi.fn((identity: string) => identity),
|
|
},
|
|
},
|
|
} as unknown as WebContext;
|
|
const app = express().use('/api', createMediaRouter(context));
|
|
|
|
const response = await request(app, '/api/media');
|
|
const body = await response.json();
|
|
|
|
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() })]);
|
|
});
|
|
});
|