83 lines
3.5 KiB
TypeScript
83 lines
3.5 KiB
TypeScript
import { expect, test } from "bun:test";
|
|
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { createDb, loadMigrations, migrate } from "@wrnexus/db";
|
|
import { sqlite } from "@wrnexus/db/sqlite";
|
|
import seed from "../app/db/seed.ts";
|
|
|
|
const root = join(import.meta.dir, "..");
|
|
const source = (path: string) => readFileSync(join(root, path), "utf8");
|
|
|
|
test("the CRM migration applies to SQLite, is idempotent, and the seed is repeatable", async () => {
|
|
const db = createDb(sqlite());
|
|
const migrations = join(root, "app", "db", "migrations");
|
|
|
|
expect(loadMigrations(migrations).map(({ name }) => name)).toEqual(["001_crm"]);
|
|
expect(await migrate(db, migrations)).toEqual(["001_crm"]);
|
|
expect(await migrate(db, migrations)).toEqual([]);
|
|
await seed(db);
|
|
await seed(db);
|
|
|
|
expect(await db.one<{ count: number }>("SELECT COUNT(*) count FROM crm_contacts")).toEqual({
|
|
count: 2,
|
|
});
|
|
expect(await db.one<{ count: number }>("SELECT COUNT(*) count FROM crm_deals")).toEqual({
|
|
count: 1,
|
|
});
|
|
expect(
|
|
await db.all(
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('_wrn_authz_assignment','_wrn_authz_grant') ORDER BY name",
|
|
),
|
|
).toHaveLength(2);
|
|
await db.close();
|
|
});
|
|
|
|
test("authentication uses SQL, plugin migrations, signup/login components, and protected routes", () => {
|
|
expect(source("app/lib/auth.ts")).toContain("SqlAuthStore");
|
|
expect(source("wrnexus.config.ts")).toContain("migrations: true");
|
|
expect(source("app/pages/login.wrn")).toContain("<SignIn");
|
|
expect(source("app/pages/signup.wrn")).toContain("<SignUp");
|
|
expect(source("app/middleware/protected.ts")).toContain('requireAuth({ loginPath: "/login" })');
|
|
expect(source("app/pages/dashboard.wrn")).toContain("/api/auth/logout");
|
|
expect(source("app/pages/login.wrn")).toContain("AuthSplitLayout");
|
|
expect(source("app/pages/login.wrn")).toContain('data-slot="form"');
|
|
expect(source("app/pages/signup.wrn")).not.toContain("Already registered?");
|
|
expect(source("wrnexus.config.ts")).toContain('entry: "app/styles/global.css"');
|
|
});
|
|
|
|
test("authorization is database-backed and guards both APIs and administration", () => {
|
|
expect(source("app/middleware/authz.ts")).toContain("dbPermissionStore(getDb())");
|
|
expect(source("app/lib/auth.ts")).toContain('assignRole(user.id, "sales-rep")');
|
|
expect(source("app/api/contacts.ts")).toContain('can(ctx, "contact:write")');
|
|
expect(source("app/api/deals.ts")).toContain('can(ctx, "deal:read")');
|
|
expect(source("app/middleware/protected.ts")).toContain('can(ctx, "admin:access")');
|
|
});
|
|
|
|
test("the app includes public, authentication, and protected CRM pages", () => {
|
|
for (const page of [
|
|
"index",
|
|
"pricing",
|
|
"login",
|
|
"signup",
|
|
"dashboard",
|
|
"contacts",
|
|
"deals",
|
|
"admin",
|
|
"forbidden",
|
|
]) {
|
|
expect(source(`app/pages/${page}.wrn`)).toContain("page ");
|
|
}
|
|
});
|
|
|
|
test("protected CRM data is loaded on the server without browser API bindings", () => {
|
|
for (const page of ["contacts", "deals", "dashboard"]) {
|
|
const contents = source(`app/pages/${page}.wrn`);
|
|
expect(contents).toContain("load server");
|
|
expect(contents).not.toContain(' api="');
|
|
}
|
|
expect(source("app/api/dashboard.ts")).toContain("pipeline_value_cents");
|
|
expect(source("app/lib/workspace.ts")).toContain("INSERT OR IGNORE INTO crm_contacts");
|
|
expect(source("app/lib/auth.ts")).toContain("ensureCrmWorkspace(user.id)");
|
|
expect(source("app/middleware/protected.ts")).toContain('new URL("/forbidden", ctx.url)');
|
|
});
|