Blur gallery images by default, sync progress UI, and add chat/monitor tests
- Blur NSFW media gallery images until hover/reveal - Surface chat sync progress in the dashboard header - Extend chat service, cache, and monitor with delta-media handling - Add tests for chat cooldown, delta media, and monitor flows Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
633bb0ec04
commit
2260388dc8
15 files changed
+675
-41
No files matched your search
@@ -1,5 +1,9 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { ChatService } from '../src/services/chat/service.js';
|
||||
import { ChatCache } from '../src/services/chat/cache.js';
|
||||
import { MatrixClient, MatrixRateLimitError } from '../src/services/chat/matrix/client.js';
|
||||
import { createMockLogger } from './mockLogger.js';
|
||||
|
||||
@@ -80,6 +84,82 @@ describe('ChatService invitation cooldown', () => {
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('persists the cooldown to disk and restores it after a fresh service load', async () => {
|
||||
const logger = createMockLogger();
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'chat-cooldown-'));
|
||||
const cacheFile = path.join(tmpDir, 'chat-cache.json');
|
||||
try {
|
||||
const matrix = new MatrixClient(creds, logger, { minRequestIntervalMs: 0 });
|
||||
const cache1 = new ChatCache(logger, cacheFile);
|
||||
const svc1 = new ChatService(matrix, logger, undefined, undefined, undefined, undefined, cache1);
|
||||
svc1.setInvitationCooldown(
|
||||
new MatrixRateLimitError('429', 120_000, 'M_LIMIT_EXCEEDED', null),
|
||||
);
|
||||
// setInvitationCooldown kicks off an async save; flush the queue.
|
||||
await cache1.save();
|
||||
|
||||
const cache2 = new ChatCache(logger, cacheFile);
|
||||
const svc2 = new ChatService(matrix, logger, undefined, undefined, undefined, undefined, cache2);
|
||||
await svc2.loadCache();
|
||||
const restored = svc2.getInvitationCooldown();
|
||||
expect(restored).not.toBeNull();
|
||||
expect(restored!.until).toBeGreaterThan(Date.now() + 100_000);
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves on-disk chats and lastFullSyncAt when a cooldown fires before loadCache', async () => {
|
||||
const logger = createMockLogger();
|
||||
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'chat-cooldown-preserve-'));
|
||||
const cacheFile = path.join(tmpDir, 'chat-cache.json');
|
||||
try {
|
||||
// Pre-seed an existing cache with a completed full sync and known chats.
|
||||
const existing = {
|
||||
version: 1,
|
||||
savedAt: Date.now() - 60_000,
|
||||
chats: [
|
||||
{
|
||||
username: 'alice',
|
||||
roomName: 'alice',
|
||||
lastMessageTime: Date.now() - 30_000,
|
||||
lastMessageSnippet: 'hi',
|
||||
isFromUs: false,
|
||||
unread: false,
|
||||
roomId: '!room:example.test',
|
||||
},
|
||||
],
|
||||
nextBatch: 's42_1',
|
||||
lastFullSyncAt: Date.now() - 120_000,
|
||||
lastDeltaSyncAt: Date.now() - 30_000,
|
||||
cooldown: null,
|
||||
};
|
||||
await fs.writeFile(cacheFile, JSON.stringify(existing), 'utf-8');
|
||||
|
||||
const matrix = new MatrixClient(creds, logger, { minRequestIntervalMs: 0 });
|
||||
const cache = new ChatCache(logger, cacheFile);
|
||||
const svc = new ChatService(matrix, logger, undefined, undefined, undefined, undefined, cache);
|
||||
|
||||
// Cooldown fires before loadCache() — the in-memory chats are still [].
|
||||
svc.setInvitationCooldown(
|
||||
new MatrixRateLimitError('429', 60_000, 'M_LIMIT_EXCEEDED', null),
|
||||
);
|
||||
// Flush the async persist (read + write).
|
||||
await cache.persistCooldown();
|
||||
|
||||
const raw = await fs.readFile(cacheFile, 'utf-8');
|
||||
const after = JSON.parse(raw);
|
||||
expect(after.chats).toHaveLength(1);
|
||||
expect(after.chats[0].username).toBe('alice');
|
||||
expect(after.lastFullSyncAt).toBe(existing.lastFullSyncAt);
|
||||
expect(after.nextBatch).toBe('s42_1');
|
||||
expect(after.cooldown).not.toBeNull();
|
||||
expect(after.cooldown.reason).toMatch(/M_LIMIT_EXCEEDED/);
|
||||
} finally {
|
||||
await fs.rm(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('sendChatMessage records a cooldown when the homeserver rate-limits a reply', async () => {
|
||||
const logger = createMockLogger();
|
||||
const matrix = new MatrixClient(creds, logger, { minRequestIntervalMs: 0 });
|
||||
|
||||
Reference in new issue
Block a user