import { sql } from "drizzle-orm"; import { drizzle } from "drizzle-orm/postgres-js"; import { migrate } from "drizzle-orm/postgres-js/migrator"; import postgres from "postgres"; import * as schema from "../src/db/schema"; const DATABASE_URL = process.env.DATABASE_URL || "postgresql://gearbox:gearbox@localhost:5432/gearbox"; export async function seedTestDatabase() { const client = postgres(DATABASE_URL, { max: 1 }); const db = drizzle(client, { schema }); // Run migrations await migrate(db, { migrationsFolder: "./drizzle-pg" }); // Clean all tables for a fresh seed const tables = [ "setup_items", "setups", "thread_candidates", "threads", "items", "global_item_tags", "global_items", "tags", "oauth_tokens", "oauth_codes", "oauth_clients", "api_keys", "settings", "categories", "users", ]; for (const t of tables) { await db.execute(sql.raw(`TRUNCATE TABLE "${t}" RESTART IDENTITY CASCADE`)); } // ── User ── const [user] = await db .insert(schema.users) .values({ logtoSub: "e2e-test-user" }) .returning(); const userId = user.id; // ── Categories ── const [uncategorized] = await db .insert(schema.categories) .values({ name: "Uncategorized", icon: "package", userId }) .returning(); const [shelter] = await db .insert(schema.categories) .values({ name: "Shelter", icon: "tent", userId }) .returning(); const [sleep] = await db .insert(schema.categories) .values({ name: "Sleep System", icon: "moon", userId }) .returning(); const [cook] = await db .insert(schema.categories) .values({ name: "Cook Kit", icon: "flame", userId }) .returning(); // ── Items ── const [tent] = await db .insert(schema.items) .values({ name: "Zpacks Duplex", weightGrams: 539, priceCents: 67900, categoryId: shelter.id, userId, notes: "DCF shelter, 2-person", }) .returning(); await db.insert(schema.items).values({ name: "Borah Gear Tarp", weightGrams: 156, priceCents: 11000, categoryId: shelter.id, userId, }); const [quilt] = await db .insert(schema.items) .values({ name: "Enlightened Equipment Enigma 20", weightGrams: 595, priceCents: 34000, categoryId: sleep.id, userId, notes: "20F quilt", }) .returning(); const [pad] = await db .insert(schema.items) .values({ name: "Therm-a-Rest NeoAir XLite", weightGrams: 354, priceCents: 20999, categoryId: sleep.id, userId, }) .returning(); const [stove] = await db .insert(schema.items) .values({ name: "BRS-3000T Stove", weightGrams: 25, priceCents: 2000, categoryId: cook.id, userId, }) .returning(); await db.insert(schema.items).values({ name: "Toaks 750ml Pot", weightGrams: 103, priceCents: 3000, categoryId: cook.id, userId, }); // ── Active Thread with 3 Candidates ── const [activeThread] = await db .insert(schema.threads) .values({ name: "New Backpack", status: "active", categoryId: uncategorized.id, userId, }) .returning(); await db.insert(schema.threadCandidates).values({ threadId: activeThread.id, name: "ULA Circuit", weightGrams: 1077, priceCents: 27500, categoryId: uncategorized.id, pros: "Great hip belt\nLarge capacity", cons: "Heavier than competitors", sortOrder: 1000, status: "researching", }); await db.insert(schema.threadCandidates).values({ threadId: activeThread.id, name: "Gossamer Gear Mariposa", weightGrams: 737, priceCents: 28500, categoryId: uncategorized.id, pros: "Very lightweight\nGood ventilation", cons: "Smaller hip belt pockets", sortOrder: 2000, status: "researching", }); await db.insert(schema.threadCandidates).values({ threadId: activeThread.id, name: "Granite Gear Crown2 38", weightGrams: 850, priceCents: 18000, categoryId: uncategorized.id, sortOrder: 3000, status: "ordered", }); // ── Resolved Thread ── const [resolvedThread] = await db .insert(schema.threads) .values({ name: "Camp Stove", status: "resolved", categoryId: cook.id, userId, resolvedCandidateId: 1, }) .returning(); await db.insert(schema.threadCandidates).values({ threadId: resolvedThread.id, name: "BRS-3000T", weightGrams: 25, priceCents: 2000, categoryId: cook.id, sortOrder: 1000, status: "arrived", }); // ── Setup with Items ── const [setup] = await db .insert(schema.setups) .values({ name: "Weekend Overnighter", userId }) .returning(); await db.insert(schema.setupItems).values([ { setupId: setup.id, itemId: tent.id, classification: "base" }, { setupId: setup.id, itemId: quilt.id, classification: "base" }, { setupId: setup.id, itemId: pad.id, classification: "base" }, { setupId: setup.id, itemId: stove.id, classification: "consumable" }, ]); // ── API Key for E2E Authentication ── const rawKey = "e2e-test-api-key-for-gearbox-testing"; const keyHash = await Bun.password.hash(rawKey); const keyPrefix = rawKey.slice(0, 8); await db .insert(schema.apiKeys) .values({ name: "E2E Test Key", keyHash, keyPrefix, userId }); // ── Settings ── await db.insert(schema.settings).values([ { key: "weightUnit", value: "g", userId }, { key: "currency", value: "USD", userId }, { key: "onboardingComplete", value: "true", userId }, ]); await client.end(); console.log("E2E test database seeded via", DATABASE_URL); }