Files
RedditBot/tests/media-sync-auth.test.ts
Jamie BruntonandClaude Opus 5 ac12a3146a feat: recover missing conversations, fix media attachment, add manual inbox tab
Missing conversations: recipient Reddit accounts that are deleted or suspended
left conversations unaccounted for. A new RecipientAccountStatusService polls
Reddit for the users behind cached conversations and records their account
status durably, with progressive backfill and delta-media fixes in ChatService
so history and images are no longer dropped mid-sync.

Media: attachMedia now surfaces download failures as MediaAttachmentError so the
sync cursor can be retried instead of silently losing an image, recognizes image
bytes when Matrix returns a generic content type, and keeps separate gallery
occurrences when the same asset appears in more than one event.

Manual inbox tab: the conversations list now carries the reply-loop status, so
the inbox can separate conversations the operator is driving by hand from the
ones automation is still handling. A Manual filter pill and a per-row chip
explain why each conversation was handed over.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 11:03:33 +01:00

75 lines
2.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { MediaSyncService } from '../src/services/media-sync.js';
import { createMockLogger } from './mockLogger.js';
describe('MediaSyncService account authentication gate', () => {
it('does not touch chat while the global account context is unauthenticated', async () => {
const chat = {
refreshTopChats: vi.fn(),
fetchAllMessages: vi.fn(),
};
const auth = { isFullyAuthenticated: vi.fn(() => false) };
const service = new MediaSyncService(
chat as never,
createMockLogger(),
30 * 60 * 1000,
auth,
);
await expect(service.triggerSync()).resolves.toBe(false);
expect(chat.refreshTopChats).not.toHaveBeenCalled();
expect(chat.fetchAllMessages).not.toHaveBeenCalled();
});
it('reports the actual number of rooms changed by an incremental sync', async () => {
const chat = {
refreshTopChats: vi.fn().mockResolvedValue(3),
fetchAllMessages: vi.fn(),
};
const service = new MediaSyncService(
chat as never,
createMockLogger(),
30 * 60 * 1000,
{ isFullyAuthenticated: () => true },
);
await expect(service.triggerSync()).resolves.toBe(true);
expect(service.getStatus().lastSyncChats).toBe(3);
});
it('requires a complete historical backfill for a full media sync', async () => {
const chat = {
refreshTopChats: vi.fn(),
fetchAllMessages: vi.fn().mockResolvedValue([]),
};
const service = new MediaSyncService(
chat as never,
createMockLogger(),
30 * 60 * 1000,
{ isFullyAuthenticated: () => true },
);
await service.triggerSync(true);
expect(chat.fetchAllMessages).toHaveBeenCalledWith({ requireComplete: true });
});
it('returns failure and preserves the operator-visible error when sync work is incomplete', async () => {
const chat = {
refreshTopChats: vi.fn().mockRejectedValue(new Error('media history incomplete')),
fetchAllMessages: vi.fn(),
};
const service = new MediaSyncService(
chat as never,
createMockLogger(),
30 * 60 * 1000,
{ isFullyAuthenticated: () => true },
);
await expect(service.triggerSync()).resolves.toBe(false);
expect(service.getStatus()).toMatchObject({
lastSyncAt: null,
lastError: 'media history incomplete',
});
});
});