feat: establish core identity foundation
Adds Core-owned identity contracts, a Core-owned Better Auth database schema (ekklesia_core_user/session/account/verification), a first-class Core-owned migration source that coexists with module-owned migrations under the exact existing migration system, and an explicit Better Auth construction boundary. No authentication endpoint or sign-in UI is exposed yet (ADR 0010). Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
df0f20d8af
commit
679a5d225c
@@ -0,0 +1,91 @@
|
||||
import { getTableName } from "drizzle-orm";
|
||||
import { getTableConfig } from "drizzle-orm/pg-core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
ekklesiaCoreAccount,
|
||||
ekklesiaCoreSession,
|
||||
ekklesiaCoreUser,
|
||||
ekklesiaCoreVerification,
|
||||
identityAdapterSchema,
|
||||
} from "../src/identity/schema.js";
|
||||
|
||||
describe("Core identity Drizzle schema", () => {
|
||||
it("uses explicit, unambiguous ekklesia_core_* physical table names", () => {
|
||||
expect(getTableName(ekklesiaCoreUser)).toBe("ekklesia_core_user");
|
||||
expect(getTableName(ekklesiaCoreSession)).toBe("ekklesia_core_session");
|
||||
expect(getTableName(ekklesiaCoreAccount)).toBe("ekklesia_core_account");
|
||||
expect(getTableName(ekklesiaCoreVerification)).toBe("ekklesia_core_verification");
|
||||
});
|
||||
|
||||
it("explicitly maps every Better Auth logical model to its physical table object", () => {
|
||||
expect(identityAdapterSchema).toEqual({
|
||||
account: ekklesiaCoreAccount,
|
||||
session: ekklesiaCoreSession,
|
||||
user: ekklesiaCoreUser,
|
||||
verification: ekklesiaCoreVerification,
|
||||
});
|
||||
});
|
||||
|
||||
it("gives the user table a unique email and a default-false verification flag", () => {
|
||||
const config = getTableConfig(ekklesiaCoreUser);
|
||||
const columns = Object.fromEntries(config.columns.map((column) => [column.name, column]));
|
||||
|
||||
expect(columns.id?.primary).toBe(true);
|
||||
expect(columns.email?.isUnique).toBe(true);
|
||||
expect(columns.email?.notNull).toBe(true);
|
||||
expect(columns.email_verified?.hasDefault).toBe(true);
|
||||
expect(columns.email_verified?.notNull).toBe(true);
|
||||
});
|
||||
|
||||
it("scopes session and account rows to a user with cascading delete", () => {
|
||||
for (const table of [ekklesiaCoreSession, ekklesiaCoreAccount]) {
|
||||
const config = getTableConfig(table);
|
||||
expect(config.foreignKeys).toHaveLength(1);
|
||||
const foreignKey = config.foreignKeys[0];
|
||||
if (!foreignKey) {
|
||||
throw new Error("Expected a foreign key.");
|
||||
}
|
||||
const reference = foreignKey.reference();
|
||||
expect(reference.columns.map((column) => column.name)).toEqual(["user_id"]);
|
||||
expect(reference.foreignColumns.map((column) => column.name)).toEqual(["id"]);
|
||||
expect(getTableName(reference.foreignTable)).toBe("ekklesia_core_user");
|
||||
expect(foreignKey.onDelete).toBe("cascade");
|
||||
}
|
||||
});
|
||||
|
||||
it("indexes session.user_id, account.user_id, and verification.identifier for lookup", () => {
|
||||
expect(
|
||||
getTableConfig(ekklesiaCoreSession).indexes.map((index) =>
|
||||
index.config.columns.map((column) => ("name" in column ? column.name : undefined)),
|
||||
),
|
||||
).toEqual([["user_id"]]);
|
||||
expect(
|
||||
getTableConfig(ekklesiaCoreAccount).indexes.map((index) =>
|
||||
index.config.columns.map((column) => ("name" in column ? column.name : undefined)),
|
||||
),
|
||||
).toEqual([["user_id"]]);
|
||||
expect(
|
||||
getTableConfig(ekklesiaCoreVerification).indexes.map((index) =>
|
||||
index.config.columns.map((column) => ("name" in column ? column.name : undefined)),
|
||||
),
|
||||
).toEqual([["identifier"]]);
|
||||
});
|
||||
|
||||
it("requires a unique session token", () => {
|
||||
const config = getTableConfig(ekklesiaCoreSession);
|
||||
const token = config.columns.find((column) => column.name === "token");
|
||||
expect(token?.isUnique).toBe(true);
|
||||
expect(token?.notNull).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps every table in the default public PostgreSQL schema", () => {
|
||||
for (const table of [
|
||||
ekklesiaCoreUser,
|
||||
ekklesiaCoreSession,
|
||||
ekklesiaCoreAccount,
|
||||
ekklesiaCoreVerification,
|
||||
]) {
|
||||
expect(getTableConfig(table).schema).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user