UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

303 lines (249 loc) 9.59 kB
import { type Alepha, AlephaError, z } from "alepha"; import { currentUserAtom } from "alepha/security"; import { expect } from "vitest"; import { $entity, $repository, db } from "../core/index.ts"; const entity = $entity({ name: "test_org_entity", schema: z.object({ id: db.primaryKey(), organization: db.organization(), name: z.text().optional(), }), }); class App { repository = $repository(entity); } const setup = async (alepha: Alepha) => { const app = alepha.inject(App); await alepha.start(); return { repository: app.repository, alepha }; }; // A tenant-scoped entity that opts into fail-closed semantics: no "global // row" visibility (the `IS NULL` escape is dropped) and queries/inserts with // no resolved tenant are refused rather than silently seeing/writing everything. // `nullable: true` is kept so a NULL row CAN be written directly (legacy data), // which lets us prove such a row is invisible to a scoped tenant. const strictEntity = $entity({ name: "test_org_strict_entity", schema: z.object({ id: db.primaryKey(), organization: db.organization({ strict: true, nullable: true }), name: z.text().optional(), }), }); // A NON-strict view over the SAME physical table, used only to seed a legacy // NULL/global row — something a strict repository (correctly) refuses to write. // It lets us prove such a pre-existing row is invisible to a scoped tenant. const strictSeedEntity = $entity({ name: "test_org_strict_entity", schema: z.object({ id: db.primaryKey(), organization: db.organization(), name: z.text().optional(), }), }); class StrictApp { repository = $repository(strictEntity); seed = $repository(strictSeedEntity); } const setupStrict = async (alepha: Alepha) => { const app = alepha.inject(StrictApp); await alepha.start(); return { repository: app.repository, seed: app.seed, alepha }; }; export const testOrgUserSeesOwnAndGlobalRows = async (alepha: Alepha) => { const { repository, alepha: app } = await setup(alepha); // Create rows for org-a, org-b, and global (null) await repository.create({ name: "org-a-row", organization: "a0000000-0000-0000-0000-000000000001", }); await repository.create({ name: "org-b-row", organization: "b0000000-0000-0000-0000-000000000002", }); await repository.create({ name: "global-row" }); // User in org-a should see org-a + global app.store.set(currentUserAtom, { id: "user-1", organization: "a0000000-0000-0000-0000-000000000001", }); const results = await repository.findMany(); expect(results).toHaveLength(2); expect(results.map((r: any) => r.name).sort()).toEqual([ "global-row", "org-a-row", ]); // Count should also be filtered expect(await repository.count()).toEqual(2); }; export const testMasterUserSeesEverything = async (alepha: Alepha) => { const { repository, alepha: app } = await setup(alepha); await repository.create({ name: "org-a-row", organization: "a0000000-0000-0000-0000-000000000001", }); await repository.create({ name: "org-b-row", organization: "b0000000-0000-0000-0000-000000000002", }); await repository.create({ name: "global-row" }); // Master user (no org) sees everything app.store.set(currentUserAtom, { id: "master-user", }); const results = await repository.findMany(); expect(results).toHaveLength(3); expect(await repository.count()).toEqual(3); }; export const testNoUserSeesEverything = async (alepha: Alepha) => { const { repository } = await setup(alepha); await repository.create({ name: "org-a-row", organization: "a0000000-0000-0000-0000-000000000001", }); await repository.create({ name: "global-row" }); // No user in context = no filter const results = await repository.findMany(); expect(results).toHaveLength(2); }; export const testAutoStampOnCreate = async (alepha: Alepha) => { const { repository, alepha: app } = await setup(alepha); app.store.set(currentUserAtom, { id: "user-1", organization: "a0000000-0000-0000-0000-000000000001", }); // Create without specifying organization — should be auto-stamped const entity = await repository.create({ name: "auto-stamped" }); expect(entity.organization).toEqual("a0000000-0000-0000-0000-000000000001"); }; export const testAutoStampNullForMasterUser = async (alepha: Alepha) => { const { repository, alepha: app } = await setup(alepha); app.store.set(currentUserAtom, { id: "master-user", }); // Master user creates a row — org stays null (global) const entity = await repository.create({ name: "master-row" }); expect(entity.organization).toBeUndefined(); }; export const testAutoStampDoesNotOverrideExplicit = async (alepha: Alepha) => { const { repository, alepha: app } = await setup(alepha); app.store.set(currentUserAtom, { id: "user-1", organization: "a0000000-0000-0000-0000-000000000001", }); // Explicitly set organization — should not be overridden const entity = await repository.create({ name: "explicit-org", organization: "b0000000-0000-0000-0000-000000000002", }); expect(entity.organization).toEqual("b0000000-0000-0000-0000-000000000002"); }; export const testAutoStampOnCreateMany = async (alepha: Alepha) => { const { repository, alepha: app } = await setup(alepha); app.store.set(currentUserAtom, { id: "user-1", organization: "a0000000-0000-0000-0000-000000000001", }); const entities = await repository.createMany([ { name: "row-1" }, { name: "row-2" }, ]); expect(entities[0].organization).toEqual( "a0000000-0000-0000-0000-000000000001", ); expect(entities[1].organization).toEqual( "a0000000-0000-0000-0000-000000000001", ); }; export const testOrgFilterOnUpdateOne = async (alepha: Alepha) => { const { repository, alepha: app } = await setup(alepha); const row = await repository.create({ name: "org-b-row", organization: "b0000000-0000-0000-0000-000000000002", }); // User in org-a cannot update org-b row app.store.set(currentUserAtom, { id: "user-1", organization: "a0000000-0000-0000-0000-000000000001", }); const updated = await repository.updateMany( { id: { eq: row.id } }, { name: "hacked" }, ); expect(updated).toHaveLength(0); // Verify row is unchanged (read as master) app.store.set(currentUserAtom, { id: "master" }); const check = await repository.getById(row.id); expect(check.name).toEqual("org-b-row"); }; export const testStrictHidesGlobalRows = async (alepha: Alepha) => { const { repository, seed, alepha: app } = await setupStrict(alepha); await repository.create({ name: "org-a-row", organization: "a0000000-0000-0000-0000-000000000001", }); await repository.create({ name: "org-b-row", organization: "b0000000-0000-0000-0000-000000000002", }); // A NULL/global row exists (e.g. legacy data written before the table was // made strict) — seeded via the non-strict view over the same table. await seed.create({ name: "global-row" }); app.store.set(currentUserAtom, { id: "user-1", organization: "a0000000-0000-0000-0000-000000000001", }); // Strict: the org-a tenant must see ONLY its own row — the global NULL row // is NOT visible (the `OR org IS NULL` escape is dropped). const results = await repository.findMany(); expect(results.map((r: any) => r.name).sort()).toEqual(["org-a-row"]); expect(await repository.count()).toEqual(1); }; export const testStrictFailsClosedWithoutTenant = async (alepha: Alepha) => { const { repository } = await setupStrict(alepha); await repository.create({ name: "org-a-row", organization: "a0000000-0000-0000-0000-000000000001", }); // No tenant/user in context: a strict entity must REFUSE to return rows, // not fall through to an unfiltered "see everything" query. await expect(repository.findMany()).rejects.toThrow(AlephaError); await expect(repository.count()).rejects.toThrow(AlephaError); }; export const testStrictFailsClosedForOrglessUser = async (alepha: Alepha) => { const { repository, alepha: app } = await setupStrict(alepha); await repository.create({ name: "org-a-row", organization: "a0000000-0000-0000-0000-000000000001", }); // A user with no organization resolves no tenant → still fail closed // (there is no "master sees everything" for a strict entity). app.store.set(currentUserAtom, { id: "orgless-user" }); await expect(repository.findMany()).rejects.toThrow(AlephaError); }; export const testStrictRefusesInsertWithoutTenant = async (alepha: Alepha) => { const { repository } = await setupStrict(alepha); // Inserting with neither an explicit org nor a resolved tenant would create // an unscoped NULL row; strict refuses it. await expect(repository.create({ name: "orphan" })).rejects.toThrow( AlephaError, ); }; export const testOrgFilterOnDelete = async (alepha: Alepha) => { const { repository, alepha: app } = await setup(alepha); const row = await repository.create({ name: "org-b-row", organization: "b0000000-0000-0000-0000-000000000002", }); // User in org-a cannot delete org-b row app.store.set(currentUserAtom, { id: "user-1", organization: "a0000000-0000-0000-0000-000000000001", }); const deleted = await repository.deleteMany({ id: { eq: row.id } }); expect(deleted).toHaveLength(0); // Verify row still exists app.store.set(currentUserAtom, { id: "master" }); expect(await repository.count()).toEqual(1); };