From b600dad011e4e21913ad6c4c2b46bd1fbcc8e4c8 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Thu, 14 May 2026 15:47:03 +0700 Subject: [PATCH] fix: correct import ordering and update tests for drizzle-orm migration --- src/database/drizzle.ts | 11 ++- src/database/schema.ts | 28 +++---- src/index.ts | 2 +- src/moderation/aiAnalyzer.ts | 8 +- src/moderation/backlogSync.ts | 4 +- src/moderation/messageCapture.ts | 8 +- src/moderation/messageStore.ts | 8 +- src/muxer-queue.ts | 21 +++-- src/webserver.ts | 8 +- tests/database.test.ts | 129 ++++++++++--------------------- 10 files changed, 87 insertions(+), 140 deletions(-) diff --git a/src/database/drizzle.ts b/src/database/drizzle.ts index 39c251a..c7f0ad7 100644 --- a/src/database/drizzle.ts +++ b/src/database/drizzle.ts @@ -1,6 +1,6 @@ -import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; -import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3"; import Database from "better-sqlite3"; +import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3"; +import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; import { Pool } from "pg"; import { config } from "../config"; import { createChildLogger } from "../logger"; @@ -8,7 +8,10 @@ import * as schema from "./schema"; const logger = createChildLogger("drizzle"); -let db: ReturnType | ReturnType | null = null; +let db: + | ReturnType + | ReturnType + | null = null; /** * Initialize the database connection based on DATABASE_TYPE config @@ -51,7 +54,7 @@ export async function initializeDatabase() { export function getDatabase() { if (db === null) { throw new Error( - "Database not initialized. Call initializeDatabase() first." + "Database not initialized. Call initializeDatabase() first.", ); } return db; diff --git a/src/database/schema.ts b/src/database/schema.ts index bfaec80..ace2b52 100644 --- a/src/database/schema.ts +++ b/src/database/schema.ts @@ -1,18 +1,18 @@ import { + bigint as pgBigint, + foreignKey as pgForeignKey, + index as pgIndex, + integer as pgInteger, + real as pgReal, pgTable, text as pgText, - integer as pgInteger, - bigint as pgBigint, - real as pgReal, - index as pgIndex, - foreignKey as pgForeignKey, } from "drizzle-orm/pg-core"; import { - sqliteTable, - text as sqliteText, + index as sqliteIndex, integer as sqliteInteger, real as sqliteReal, - index as sqliteIndex, + sqliteTable, + text as sqliteText, } from "drizzle-orm/sqlite-core"; import { config } from "../config"; @@ -257,14 +257,10 @@ export const sqliteUIStateTable = sqliteTable("ui_state", { // ======================================== export const muxerJobsTable = - config.DATABASE_TYPE === "postgres" - ? pgMuxerJobsTable - : sqliteMuxerJobsTable; + config.DATABASE_TYPE === "postgres" ? pgMuxerJobsTable : sqliteMuxerJobsTable; export const messagesTable = - config.DATABASE_TYPE === "postgres" - ? pgMessagesTable - : sqliteMessagesTable; + config.DATABASE_TYPE === "postgres" ? pgMessagesTable : sqliteMessagesTable; export const attachmentsTable = config.DATABASE_TYPE === "postgres" @@ -272,9 +268,7 @@ export const attachmentsTable = : sqliteAttachmentsTable; export const uiStateTable = - config.DATABASE_TYPE === "postgres" - ? pgUIStateTable - : sqliteUIStateTable; + config.DATABASE_TYPE === "postgres" ? pgUIStateTable : sqliteUIStateTable; // Export table types for use in queries export type MuxerJob = typeof muxerJobsTable.$inferSelect; diff --git a/src/index.ts b/src/index.ts index 9ef9c2b..fd60a60 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import "@snazzah/davey"; import "dotenv/config"; import { Client } from "discord.js-selfbot-v13"; import { config } from "./config"; -import { initializeDatabase, closeDatabase } from "./database/drizzle"; +import { closeDatabase, initializeDatabase } from "./database/drizzle"; import { createChildLogger } from "./logger"; import { startPendingAIAnalysisWorker } from "./moderation/aiAnalyzer"; import { syncBacklogMessages } from "./moderation/backlogSync"; diff --git a/src/moderation/aiAnalyzer.ts b/src/moderation/aiAnalyzer.ts index 5db8c7d..83d4617 100644 --- a/src/moderation/aiAnalyzer.ts +++ b/src/moderation/aiAnalyzer.ts @@ -245,9 +245,7 @@ Satu JSON object per pesan dalam array.`, return { results, raw: response }; } -async function analyzeAndStoreBatch( - messages: MessageRecord[], -): Promise { +async function analyzeAndStoreBatch(messages: MessageRecord[]): Promise { if (messages.length === 0) return; const analyzableMessages = messages.filter( @@ -359,9 +357,7 @@ async function drainQueue(): Promise { } } -export function queueMessageAnalysis( - messageId: string, -): void { +export function queueMessageAnalysis(messageId: string): void { if (!config.AI_ANALYSIS_ENABLED) return; logger.debug({ messageId }, "Queueing AI analysis"); queuedMessageIds.add(messageId); diff --git a/src/moderation/backlogSync.ts b/src/moderation/backlogSync.ts index 85b3eff..2735556 100644 --- a/src/moderation/backlogSync.ts +++ b/src/moderation/backlogSync.ts @@ -40,9 +40,7 @@ async function syncChannelMessages( return synced; } -export async function syncBacklogMessages( - client: Client, -): Promise { +export async function syncBacklogMessages(client: Client): Promise { if (!config.MONITOR_GUILD_ID) { logger.warn("MONITOR_GUILD_ID not configured, skipping backlog sync"); return; diff --git a/src/moderation/messageCapture.ts b/src/moderation/messageCapture.ts index 6373ad9..992c901 100644 --- a/src/moderation/messageCapture.ts +++ b/src/moderation/messageCapture.ts @@ -1,9 +1,9 @@ import type { Client, Message } from "discord.js-selfbot-v13"; +import { eq } from "drizzle-orm"; import { config } from "../config"; -import { createChildLogger } from "../logger"; import { getDatabase } from "../database/drizzle"; import { messagesTable } from "../database/schema"; -import { eq } from "drizzle-orm"; +import { createChildLogger } from "../logger"; import { queueMessageAnalysis } from "./aiAnalyzer"; import { getDisplayContent, @@ -94,9 +94,7 @@ export async function captureMessage( ); } -export function registerMessageCapture( - client: Client, -): void { +export function registerMessageCapture(client: Client): void { client.on("messageCreate", async (message) => { if (!message.guildId || message.guildId !== config.MONITOR_GUILD_ID) return; if (message.author?.bot) return; diff --git a/src/moderation/messageStore.ts b/src/moderation/messageStore.ts index e569c56..4989d95 100644 --- a/src/moderation/messageStore.ts +++ b/src/moderation/messageStore.ts @@ -1,14 +1,12 @@ +import { and, asc, desc, eq, isNull, or } from "drizzle-orm"; import { getDatabase } from "../database/drizzle"; -import { messagesTable, attachmentsTable } from "../database/schema"; -import { eq, or, desc, asc, and, isNull } from "drizzle-orm"; +import { attachmentsTable, messagesTable } from "../database/schema"; import { createChildLogger } from "../logger"; import type { AttachmentRecord, MessageRecord } from "./types"; const logger = createChildLogger("message-store"); -export async function insertMessage( - message: MessageRecord, -): Promise { +export async function insertMessage(message: MessageRecord): Promise { try { const db = getDatabase() as any; await db.insert(messagesTable).values(message).onConflictDoNothing(); diff --git a/src/muxer-queue.ts b/src/muxer-queue.ts index 320ef8b..58a5de5 100644 --- a/src/muxer-queue.ts +++ b/src/muxer-queue.ts @@ -1,6 +1,9 @@ -import { getDatabase as getDrizzleDatabase, initializeDatabase } from "./database/drizzle"; +import { and, asc, eq, lt, sql } from "drizzle-orm"; +import { + getDatabase as getDrizzleDatabase, + initializeDatabase, +} from "./database/drizzle"; import { muxerJobsTable, uiStateTable } from "./database/schema"; -import { eq, asc, lt, and, sql } from "drizzle-orm"; import { createChildLogger } from "./logger"; const logger = createChildLogger("muxer-queue"); @@ -224,9 +227,10 @@ export async function cleanupCompletedJobs( ), ); - const deletedCount = typeof result === "object" && "rowsAffected" in result - ? result.rowsAffected - : 0; + const deletedCount = + typeof result === "object" && "rowsAffected" in result + ? result.rowsAffected + : 0; logger.info({ deletedCount }, "Cleaned up completed jobs"); @@ -258,9 +262,10 @@ export async function getJobStats(): Promise<{ }; for (const row of rows) { - const count = typeof row.count === "object" && "count" in row.count - ? (row.count as any).count - : Number(row.count); + const count = + typeof row.count === "object" && "count" in row.count + ? (row.count as any).count + : Number(row.count); if (row.status === "pending") stats.pending = count; else if (row.status === "processing") stats.processing = count; else if (row.status === "completed") stats.completed = count; diff --git a/src/webserver.ts b/src/webserver.ts index 7c96c3c..7a86f36 100644 --- a/src/webserver.ts +++ b/src/webserver.ts @@ -5,6 +5,7 @@ import http from "http"; import path from "path"; import * as prism from "prism-media"; import { WebSocketServer } from "ws"; +import { getDatabase } from "./database/drizzle"; import { AppError } from "./errors"; import { createChildLogger, logger } from "./logger"; import { getMetrics, uptimeGauge } from "./metrics"; @@ -18,7 +19,6 @@ import { getPersistedValue, setPersistedValue, } from "./muxer-queue"; -import { getDatabase } from "./database/drizzle"; import { discordPlayer } from "./player"; import type { VoiceController } from "./voiceController"; @@ -296,7 +296,11 @@ export async function startWebserver( count: attachments.length, }); } else { - const messages = await getMessagesByChannel(channel, limitNum, offsetNum); + const messages = await getMessagesByChannel( + channel, + limitNum, + offsetNum, + ); res.json({ type: "text", data: messages, diff --git a/tests/database.test.ts b/tests/database.test.ts index eef53e0..ace7b23 100644 --- a/tests/database.test.ts +++ b/tests/database.test.ts @@ -3,10 +3,9 @@ import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; const originalEnv = process.env; -describe("PostgreSQL Connection", () => { - let skipPostgresTests = false; +describe("Drizzle ORM Database", () => { let config: any; - let postgres: any; + let drizzle: any; let logger: any; beforeAll(async () => { @@ -23,118 +22,70 @@ describe("PostgreSQL Connection", () => { // Import after environment is set const configModule = await import("../src/config"); - const postgresModule = await import("../src/database/postgres"); + const drizzleModule = await import("../src/database/drizzle"); const loggerModule = await import("../src/logger"); config = configModule.config; - postgres = postgresModule; + drizzle = drizzleModule; logger = loggerModule.createChildLogger("database.test"); - if (config.DATABASE_TYPE !== "postgres") { - skipPostgresTests = true; - logger.info("Skipping PostgreSQL tests (DATABASE_TYPE != postgres)"); - } + logger.info(`Testing with DATABASE_TYPE: ${config.DATABASE_TYPE}`); }); afterAll(async () => { - if (config && config.DATABASE_TYPE === "postgres") { - try { - await postgres.closePool(); - } catch (error) { - if (logger) { - logger.error( - { error: error instanceof Error ? error.message : String(error) }, - "Error closing pool in afterAll", - ); - } + try { + await drizzle.closeDatabase(); + } catch (error) { + if (logger) { + logger.error( + { error: error instanceof Error ? error.message : String(error) }, + "Error closing database in afterAll", + ); } } process.env = originalEnv; }); - it("should initialize connection pool", async () => { - if (skipPostgresTests) { - logger.info("Skipping test: DATABASE_TYPE is not postgres"); - return; - } + it("should initialize database connection", async () => { + const db = await drizzle.initializeDatabase(); - const pool = postgres.getPool(); - - expect(pool).toBeDefined(); - expect(pool).toHaveProperty("connect"); - expect(pool).toHaveProperty("query"); - expect(pool).toHaveProperty("end"); + expect(db).toBeDefined(); + expect(db).toHaveProperty("query"); + expect(db).toHaveProperty("select"); }); - it("should execute query", async () => { - if (skipPostgresTests) { - logger.info("Skipping test: DATABASE_TYPE is not postgres"); - return; - } + it("should return same instance on subsequent calls", async () => { + const db1 = await drizzle.initializeDatabase(); + const db2 = await drizzle.initializeDatabase(); - const result = await postgres.query("SELECT 1 as num"); - - expect(result).toBeDefined(); - expect(result.rows).toBeDefined(); - expect(result.rows.length).toBeGreaterThan(0); - expect(result.rows[0]).toHaveProperty("num"); - expect(result.rows[0].num).toBe(1); + expect(db1).toBe(db2); }); - it("should handle connection errors gracefully", async () => { - if (skipPostgresTests) { - logger.info("Skipping test: DATABASE_TYPE is not postgres"); - return; - } + it("should get database instance", async () => { + await drizzle.initializeDatabase(); + const db = drizzle.getDatabase(); - // Test that invalid queries throw errors appropriately - try { - await postgres.query("SELECT * FROM nonexistent_table_xyz"); - // If we get here, the test should fail - expect.fail("Expected query to throw an error"); - } catch (error) { - // Expected behavior: query should throw an error for invalid table - expect(error).toBeDefined(); - expect(error instanceof Error).toBe(true); - } + expect(db).toBeDefined(); + expect(db).toHaveProperty("query"); }); - it("should acquire and release client from pool", async () => { - if (skipPostgresTests) { - logger.info("Skipping test: DATABASE_TYPE is not postgres"); - return; - } + it("should throw error if database not initialized", async () => { + // Reset the database state + vi.resetModules(); - const client = await postgres.getClient(); + const drizzleModule = await import("../src/database/drizzle"); - expect(client).toBeDefined(); - expect(client).toHaveProperty("query"); - expect(client).toHaveProperty("release"); - - // Execute a simple query with the client - const result = await client.query("SELECT 1 as num"); - expect(result.rows[0].num).toBe(1); - - // Release the client back to the pool - client.release(); + expect(() => { + drizzleModule.getDatabase(); + }).toThrow("Database not initialized"); }); - it("should build config from DATABASE_URL", () => { - if (skipPostgresTests) { - logger.info("Skipping test: DATABASE_TYPE is not postgres"); - return; - } + it("should close database connection", async () => { + await drizzle.initializeDatabase(); + await drizzle.closeDatabase(); - // Test buildConfig function with a sample DATABASE_URL - const pgConfig = postgres.buildConfig(); - - expect(pgConfig).toBeDefined(); - expect(pgConfig).toHaveProperty("host"); - expect(pgConfig).toHaveProperty("port"); - expect(pgConfig).toHaveProperty("min"); - expect(pgConfig).toHaveProperty("max"); - expect(pgConfig.port).toBeGreaterThan(0); - expect(pgConfig.min).toBeGreaterThan(0); - expect(pgConfig.max).toBeGreaterThanOrEqual(pgConfig.min); + expect(() => { + drizzle.getDatabase(); + }).toThrow("Database not initialized"); }); });