cpns / packages /api /src /__tests__ /package.integration.test.ts
pramudya-yb
Rename project from Labas to Pram
2b62b0c
Raw
History Blame Contribute Delete
7.88 kB
import { describe, expect, it, beforeAll, afterAll, mock } from "bun:test";
import { eq } from "drizzle-orm";
import { drizzle } from "drizzle-orm/pglite";
import * as schema from "../../../db/src/schema";
import { closeTestPGlite, getTestPGlite } from "./test-setup";
mock.module("@pram/env/server", () => ({
env: {
DATABASE_URL: "postgres://localhost:5432/test",
BETTER_AUTH_SECRET: "a".repeat(32),
BETTER_AUTH_URL: "http://localhost:3000",
CORS_ORIGIN: "http://localhost:5173",
API_KEY_ENCRYPTION_KEY: "z".repeat(32),
REDIS_URL: "redis://localhost:6379",
SMTP_HOST: "localhost",
SMTP_USER: "test",
SMTP_PASS: "test",
SMTP_FROM: "test@test.com",
},
}));
describe("package router", () => {
let packageRouter: any;
let caller: any;
let protectedCaller: any;
let testDb: any;
let ownerId: string;
let pkgId: string;
let sectionId: string;
let questionId: string;
beforeAll(async () => {
const pg = await getTestPGlite();
testDb = drizzle(pg, { schema });
mock.module("@pram/db", () => ({
__esModule: true,
...schema,
db: testDb,
}));
await testDb.insert(schema.examType).values([
{ id: "IELTS", name: "IELTS", language: "English" },
]);
await testDb.insert(schema.sectionType).values([
{ id: "READING", name: "Reading" },
{ id: "WRITING", name: "Writing" },
]);
ownerId = "pkg-owner";
await testDb.insert(schema.user).values({ id: ownerId, name: "Owner", email: "owner@test.com" });
const mod = await import("../routers/package");
packageRouter = mod.packageRouter;
caller = packageRouter.createCaller({ session: null, auth: null });
protectedCaller = packageRouter.createCaller({
session: { user: { id: ownerId }, expiresAt: new Date() },
auth: null,
});
});
afterAll(async () => {
await closeTestPGlite();
});
it("create makes a new package", async () => {
const result = await protectedCaller.create({
title: "My IELTS Pack",
examTypeId: "IELTS",
isPublic: false,
});
expect(result).toHaveProperty("id");
expect(result.title).toBe("My IELTS Pack");
pkgId = result.id;
});
it("getById returns package with sections", async () => {
const result = await protectedCaller.getById({ id: pkgId });
expect(result).not.toBeNull();
expect(result.title).toBe("My IELTS Pack");
expect(result.sections).toEqual([]);
});
it("getById returns null for non-existent", async () => {
const { randomUUID } = await import("node:crypto");
const result = await caller.getById({ id: randomUUID() });
expect(result).toBeNull();
});
it("list returns public packages", async () => {
// Make it public first
await testDb.update(schema.testPackage).set({ isPublic: true }).where(eq(schema.testPackage.id, pkgId));
const result = await caller.list();
expect(result.packages.length).toBeGreaterThan(0);
expect(result.packages[0].id).toBe(pkgId);
// Set back to private for upcoming tests
await testDb.update(schema.testPackage).set({ isPublic: false }).where(eq(schema.testPackage.id, pkgId));
});
it("list hides private packages from anonymous users", async () => {
const result = await caller.list();
const ids = result.packages.map((p: any) => p.id);
expect(ids).not.toContain(pkgId);
});
it("list shows private packages to owner via auth", async () => {
const result = await protectedCaller.list();
const ids = result.packages.map((p: any) => p.id);
expect(ids).toContain(pkgId);
});
it("list filters by examTypeId", async () => {
await testDb.update(schema.testPackage).set({ isPublic: true }).where(eq(schema.testPackage.id, pkgId));
const result = await caller.list({ examTypeId: "TOEFL" });
expect(result.packages).toHaveLength(0);
});
it("update changes package fields", async () => {
const result = await protectedCaller.update({
id: pkgId,
title: "Updated Pack",
});
expect(result.title).toBe("Updated Pack");
});
it("update rejects non-owner", async () => {
const otherCaller = packageRouter.createCaller({
session: { user: { id: "other-user" }, expiresAt: new Date() },
auth: null,
});
expect(otherCaller.update({ id: pkgId, title: "Hacked" })).rejects.toThrow("Forbidden");
});
it("addSection creates a section", async () => {
const result = await protectedCaller.addSection({
packageId: pkgId,
sectionTypeId: "READING",
title: "Reading Section",
orderIndex: 0,
});
expect(result).toHaveProperty("id");
sectionId = result.id;
});
it("addSection rejects non-owner", async () => {
const otherCaller = packageRouter.createCaller({
session: { user: { id: "other-user" }, expiresAt: new Date() },
auth: null,
});
expect(otherCaller.addSection({ packageId: pkgId, sectionTypeId: "READING", title: "X" })).rejects.toThrow("Forbidden");
});
it("getById includes sections after adding", async () => {
const result = await caller.getById({ id: pkgId });
expect(result.sections).toHaveLength(1);
expect(result.sections[0].sectionTypeId).toBe("READING");
});
it("addQuestion adds question to section", async () => {
const [q] = await testDb.insert(schema.question).values({
examTypeId: "IELTS", sectionTypeId: "READING", format: "multiple_choice",
passageText: "Q".repeat(60), questionText: "Test?",
options: [{ key: "A", text: "A" }, { key: "B", text: "B" }],
correctAnswer: "A", explanation: "Exp", difficulty: 1,
skillTags: ["test"], creatorUserId: ownerId, isPublic: true,
}).returning();
questionId = q.id;
const result = await protectedCaller.addQuestion({
sectionId,
questionId: q.id,
orderIndex: 0,
});
expect(result).toHaveProperty("id");
});
it("getById shows questions for owner, strips answers for guests", async () => {
const ownerResult = await protectedCaller.getById({ id: pkgId });
const section = ownerResult.sections.find((s: any) => s.id === sectionId);
expect(section.questions).toHaveLength(1);
expect(section.questions[0].correctAnswer).toBe("A");
const guestResult = await caller.getById({ id: pkgId });
const guestSection = guestResult.sections.find((s: any) => s.id === sectionId);
expect(guestSection.questions[0].correctAnswer).toBeUndefined();
});
it("myPackages returns owned packages", async () => {
const result = await protectedCaller.myPackages();
expect(result.packages.length).toBeGreaterThan(0);
const ids = result.packages.map((p: any) => p.id);
expect(ids).toContain(pkgId);
});
it("featured returns empty when no featured packages", async () => {
const result = await caller.featured();
expect(result).toEqual([]);
});
it("trending returns public packages sorted by usage", async () => {
await testDb.update(schema.testPackage).set({ isPublic: true }).where(eq(schema.testPackage.id, pkgId));
const result = await caller.trending();
expect(result.length).toBeGreaterThan(0);
});
it("removeQuestion removes question from section", async () => {
const sq = await protectedCaller.addQuestion({
sectionId, questionId, orderIndex: 0,
});
const result = await protectedCaller.removeQuestion({ sectionQuestionId: sq.id });
expect(result.success).toBe(true);
});
it("removeSection removes section", async () => {
const result = await protectedCaller.removeSection({ sectionId });
expect(result.success).toBe(true);
});
it("delete removes package", async () => {
const result = await protectedCaller.delete({ id: pkgId });
expect(result.success).toBe(true);
const fetched = await caller.getById({ id: pkgId });
expect(fetched).toBeNull();
});
});