feat: enhance moderation functionality with type improvements and global broadcaster integration
This commit is contained in:
@@ -4,9 +4,9 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
const originalEnv = process.env;
|
||||
|
||||
describe("Drizzle ORM Database", () => {
|
||||
let config: any;
|
||||
let drizzle: any;
|
||||
let logger: any;
|
||||
let config: typeof import("../src/config").config;
|
||||
let drizzle: typeof import("../src/database/drizzle");
|
||||
let logger: ReturnType<typeof import("../src/logger").createChildLogger>;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Set up environment for config loading
|
||||
|
||||
@@ -1,17 +1,70 @@
|
||||
import type { Mock } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createBroadcaster } from "../../src/moderation/broadcaster";
|
||||
import {
|
||||
type BroadcasterClient,
|
||||
createBroadcaster,
|
||||
} from "../../src/moderation/broadcaster";
|
||||
import type {
|
||||
AttachmentRecord,
|
||||
MessageRecord,
|
||||
} from "../../src/moderation/types";
|
||||
|
||||
function client() {
|
||||
type TestClient = BroadcasterClient & { send: Mock };
|
||||
|
||||
function client(): TestClient {
|
||||
return { readyState: 1, send: vi.fn() };
|
||||
}
|
||||
|
||||
function messageRecord(overrides: Partial<MessageRecord> = {}): MessageRecord {
|
||||
return {
|
||||
id: "m1",
|
||||
guild_id: "guild-1",
|
||||
channel_id: "channel-1",
|
||||
thread_id: null,
|
||||
user_id: "user-1",
|
||||
username: "alice",
|
||||
avatar_url: null,
|
||||
content: "test",
|
||||
edited_content: null,
|
||||
created_at: 1,
|
||||
edited_at: null,
|
||||
deleted_at: null,
|
||||
type: "text",
|
||||
metadata: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function attachmentRecord(
|
||||
overrides: Partial<AttachmentRecord> = {},
|
||||
): AttachmentRecord {
|
||||
return {
|
||||
id: "a1",
|
||||
message_id: "m1",
|
||||
guild_id: "guild-1",
|
||||
channel_id: "channel-1",
|
||||
thread_id: null,
|
||||
user_id: "user-1",
|
||||
filename: "image.png",
|
||||
size: 1,
|
||||
type: "image/png",
|
||||
discord_url: "https://example.com/image.png",
|
||||
uploaded_url: null,
|
||||
upload_status: "pending",
|
||||
upload_error: null,
|
||||
created_at: 1,
|
||||
uploaded_at: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("createBroadcaster", () => {
|
||||
it("sends JSON events to open clients", () => {
|
||||
const ws = client();
|
||||
const broadcaster = createBroadcaster();
|
||||
|
||||
broadcaster.addClient(ws as any);
|
||||
broadcaster.messageAnalyzed({ id: "m1", ai_status: "clean" } as any);
|
||||
broadcaster.addClient(ws);
|
||||
broadcaster.messageAnalyzed(messageRecord({ ai_status: "clean" }));
|
||||
|
||||
expect(ws.send).toHaveBeenCalledTimes(1);
|
||||
expect(JSON.parse(ws.send.mock.calls[0][0])).toMatchObject({
|
||||
@@ -21,10 +74,10 @@ describe("createBroadcaster", () => {
|
||||
});
|
||||
|
||||
it("skips closed clients", () => {
|
||||
const ws = { readyState: 3, send: vi.fn() };
|
||||
const ws: TestClient = { readyState: 3, send: vi.fn() };
|
||||
const broadcaster = createBroadcaster();
|
||||
|
||||
broadcaster.addClient(ws as any);
|
||||
broadcaster.addClient(ws);
|
||||
broadcaster.messageDeleted({ id: "m1", deleted_at: 123 });
|
||||
|
||||
expect(ws.send).not.toHaveBeenCalled();
|
||||
@@ -36,14 +89,11 @@ describe("createBroadcaster", () => {
|
||||
const ws3 = client();
|
||||
const broadcaster = createBroadcaster();
|
||||
|
||||
broadcaster.addClient(ws1 as any);
|
||||
broadcaster.addClient(ws2 as any);
|
||||
broadcaster.addClient(ws3 as any);
|
||||
broadcaster.addClient(ws1);
|
||||
broadcaster.addClient(ws2);
|
||||
broadcaster.addClient(ws3);
|
||||
|
||||
broadcaster.messageCreated({
|
||||
id: "m1",
|
||||
content: "test",
|
||||
} as any);
|
||||
broadcaster.messageCreated(messageRecord());
|
||||
|
||||
expect(ws1.send).toHaveBeenCalledTimes(1);
|
||||
expect(ws2.send).toHaveBeenCalledTimes(1);
|
||||
@@ -61,14 +111,14 @@ describe("createBroadcaster", () => {
|
||||
throw new Error("Send failed");
|
||||
});
|
||||
|
||||
broadcaster.addClient(ws1 as any);
|
||||
broadcaster.addClient(ws2 as any);
|
||||
broadcaster.addClient(ws3 as any);
|
||||
broadcaster.addClient(ws1);
|
||||
broadcaster.addClient(ws2);
|
||||
broadcaster.addClient(ws3);
|
||||
|
||||
broadcaster.messageUpdated({
|
||||
id: "m1",
|
||||
content: "updated",
|
||||
} as any);
|
||||
});
|
||||
|
||||
// ws1 attempted send (threw)
|
||||
expect(ws1.send).toHaveBeenCalledTimes(1);
|
||||
@@ -84,16 +134,16 @@ describe("createBroadcaster", () => {
|
||||
|
||||
expect(broadcaster.clientCount()).toBe(0);
|
||||
|
||||
broadcaster.addClient(ws1 as any);
|
||||
broadcaster.addClient(ws1);
|
||||
expect(broadcaster.clientCount()).toBe(1);
|
||||
|
||||
broadcaster.addClient(ws2 as any);
|
||||
broadcaster.addClient(ws2);
|
||||
expect(broadcaster.clientCount()).toBe(2);
|
||||
|
||||
broadcaster.removeClient(ws1 as any);
|
||||
broadcaster.removeClient(ws1);
|
||||
expect(broadcaster.clientCount()).toBe(1);
|
||||
|
||||
broadcaster.removeClient(ws2 as any);
|
||||
broadcaster.removeClient(ws2);
|
||||
expect(broadcaster.clientCount()).toBe(0);
|
||||
});
|
||||
|
||||
@@ -101,11 +151,8 @@ describe("createBroadcaster", () => {
|
||||
const ws = client();
|
||||
const broadcaster = createBroadcaster();
|
||||
|
||||
broadcaster.addClient(ws as any);
|
||||
broadcaster.attachmentCreated({
|
||||
id: "a1",
|
||||
message_id: "m1",
|
||||
} as any);
|
||||
broadcaster.addClient(ws);
|
||||
broadcaster.attachmentCreated(attachmentRecord());
|
||||
|
||||
expect(ws.send).toHaveBeenCalledTimes(1);
|
||||
const payload = JSON.parse(ws.send.mock.calls[0][0]);
|
||||
|
||||
@@ -16,6 +16,14 @@ import {
|
||||
} from "../../src/moderation/messageStore";
|
||||
import type { MessageRecord } from "../../src/moderation/types";
|
||||
|
||||
interface TestDatabase {
|
||||
run(sql: string): void;
|
||||
}
|
||||
|
||||
function getTestDatabase(): TestDatabase {
|
||||
return getDatabase() as unknown as TestDatabase;
|
||||
}
|
||||
|
||||
const logger = createChildLogger("messageStoreQueries.test");
|
||||
|
||||
describe("message cursor helpers", () => {
|
||||
@@ -36,7 +44,7 @@ describe("message query integration tests", () => {
|
||||
beforeAll(async () => {
|
||||
await initializeDatabase();
|
||||
// Create tables using Drizzle schema (SQLite doesn't support migrations with PostgreSQL syntax)
|
||||
const db = getDatabase() as any;
|
||||
const db = getTestDatabase();
|
||||
try {
|
||||
// Create messages table
|
||||
await db.run(`
|
||||
@@ -75,7 +83,7 @@ describe("message query integration tests", () => {
|
||||
beforeEach(async () => {
|
||||
// Clear messages table before each test
|
||||
try {
|
||||
const db = getDatabase() as any;
|
||||
const db = getTestDatabase();
|
||||
await db.run(`DELETE FROM "messages"`);
|
||||
} catch (error) {
|
||||
logger.debug({ error }, "Could not clear messages table");
|
||||
|
||||
Reference in New Issue
Block a user