901 lines · 33.4 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect";import { type InStatement } from "@libsql/client"; import {  ADMIN_DEFAULT_PAGE_SIZE,  ADMIN_MAX_PAGE_SIZE,  collectTables,  createExecutor,  type Executor,} from "./executor";import { StorageError } from "./fuma-runtime";import { createSqliteTestFumaDb, type SqliteTestFumaDb } from "./sqlite-test-db";import {  ConnectionName,  IntegrationSlug,  OAuthClientSlug,  ProviderItemId,  ProviderKey,  Subject,  Tenant,} from "./ids";import { definePlugin } from "./plugin";import { resetSubjectTouchCache, touchSubject } from "./subject-registry"; // The platform view: `executor.admin`, an OPT-IN read-only surface that reads// across every subject in the tenant. Written against the real SQLite bring-up// so the tenant-reach policy, the bigint `last_seen_at` round-trip, and the// field allowlist are all live. const TENANT = "t1";const OTHER_TENANT = "t2";const SUBJECT_A = "user_a";const SUBJECT_B = "user_b"; /** Every column on `connection` that could resolve, or help resolve, a *  credential. NONE of these may appear on an admin shape — enumerated so a *  future column addition has to be argued with this list. */const FORBIDDEN_CONNECTION_FIELDS = [  "item_ids",  "itemIds",  "refresh_item_id",  "refreshItemId",  "provider",  "provider_state",  "providerState",  "oauth_token_url",  "oauthTokenUrl",  "template",  "client_secret",  "clientSecret",  "secret",  "token",] as const; /** * The upstream-derived halves of a stored health verdict: the account identity * extracted from the configured `identityField` (typically the user's email AT * the provider), a raw diagnostic that can quote an upstream error body, and a * bounded sample of the real response body. * * These are NOT on the list above, and that is deliberate rather than an * oversight. This layer is the in-process platform view, not a wire shape: * `AdminConnection.lastHealth` is the full `HealthCheckResult`, and the * narrowing to status + checkedAt happens where the response is BUILT, in * `@executor-js/api`'s `admin/reads.ts` → `AdminConnectionHealth`. Listing them * as forbidden top-level connection keys would pass without testing anything, * because they never were top-level keys — they live nested inside * `lastHealth`. That vacuous-green assertion is precisely the failure mode this * comment exists to prevent. * * So the assertion below pins the SEAM instead: this layer DOES carry them, and * anyone who "fixes" the leak here rather than at the projection has moved the * boundary. The absence assertions live in `packages/core/api`'s * `admin-users.test.ts`, against real HTTP responses. */const UPSTREAM_HEALTH_FIELDS = ["identity", "detail", "responseSample"] as const; const withDb = <A, E>(body: (db: SqliteTestFumaDb) => Effect.Effect<A, E>): Effect.Effect<A, E> =>  Effect.acquireUseRelease(    Effect.promise(() => createSqliteTestFumaDb({ tables: collectTables() })),    body,    (db) => Effect.promise(() => db.close()),  ); const insertConnection = (  db: SqliteTestFumaDb,  row: {    readonly rowId: string;    readonly tenant: string;    readonly owner: string;    readonly subject: string;    readonly integration: string;    readonly name: string;    readonly oauthScope?: string | null;  },): Effect.Effect<void> =>  Effect.promise(async () => {    await db.client.execute({      sql: `INSERT INTO connection (          row_id, tenant, owner, subject, integration, name, template, provider,          item_ids, refresh_item_id, oauth_scope, last_health, created_at, updated_at        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,      args: [        row.rowId,        row.tenant,        row.owner,        row.subject,        row.integration,        row.name,        "oauth2",        "memory",        // Deliberately secret-bearing: the assertions below prove these never        // leave the storage layer.        JSON.stringify({ token: `SECRET-item-${row.rowId}` }),        `SECRET-refresh-${row.rowId}`,        row.oauthScope ?? null,        // A full stored verdict, upstream-derived fields included — that is        // what a real health check writes. This layer is entitled to read them        // back (see UPSTREAM_HEALTH_FIELDS); the API's projection is what keeps        // them off the wire.        JSON.stringify({          status: "healthy",          checkedAt: 1_700_000_000_000,          identity: `UPSTREAM-identity-${row.rowId}@provider.test`,          detail: `UPSTREAM-detail-${row.rowId}`,          responseSample: [{ path: "user.email", value: `UPSTREAM-sample-${row.rowId}` }],        }),        Date.now(),        Date.now(),      ],    });  }); /** A tenant-scoped `integration` row, inserted raw. Needed because *  `integrations.remove` short-circuits on an absent slug — without a real row *  the call returns success having touched no storage, which would make a *  read-only assertion vacuous. */const insertIntegration = (db: SqliteTestFumaDb, slug: string): Effect.Effect<void> =>  Effect.promise(async () => {    await db.client.execute({      sql: `INSERT INTO integration (          row_id, tenant, slug, plugin_id, name, description, config, can_remove,          can_refresh, created_at, updated_at        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,      args: [        `i-${slug}`,        TENANT,        slug,        "demo",        slug,        null,        JSON.stringify({}),        1,        0,        Date.now(),        Date.now(),      ],    });  }); /** Seed two users and an org connection under `t1`, plus an untouchable other *  tenant. Subjects go in through `touchSubject` (the only legitimate writer); *  connections go in raw, because no single bound executor could have written *  another subject's rows. */const seed = (db: SqliteTestFumaDb): Effect.Effect<void> =>  Effect.gen(function* () {    // `touchSubject` keeps a process-local memory of principals it already    // filed, so a second seed against a FRESH database would be skipped as a    // repeat sighting and leave the table empty. Each seed is a new world.    resetSubjectTouchCache();     yield* touchSubject(db.db, { tenant: TENANT, externalId: SUBJECT_A });    yield* touchSubject(db.db, { tenant: TENANT, externalId: SUBJECT_B });    yield* touchSubject(db.db, { tenant: OTHER_TENANT, externalId: "user_elsewhere" });     yield* insertConnection(db, {      rowId: "c-a1",      tenant: TENANT,      owner: "user",      subject: SUBJECT_A,      integration: "github",      name: "personal",      oauthScope: "repo read:user",    });    yield* insertConnection(db, {      rowId: "c-a2",      tenant: TENANT,      owner: "user",      subject: SUBJECT_A,      integration: "linear",      name: "work",    });    yield* insertConnection(db, {      rowId: "c-b1",      tenant: TENANT,      owner: "user",      subject: SUBJECT_B,      integration: "github",      name: "b-personal",      oauthScope: "repo",    });    yield* insertConnection(db, {      rowId: "c-org",      tenant: TENANT,      owner: "org",      subject: "",      integration: "stripe",      name: "shared",    });    yield* insertConnection(db, {      rowId: "c-other",      tenant: OTHER_TENANT,      owner: "user",      subject: SUBJECT_A,      integration: "github",      name: "other-tenant",    });  }); /** * A plugin contributing one writable credential provider. * * `oauth.createClient` stores the client secret out-of-band and fails BEFORE * touching storage when no writable provider is registered. Without this, the * read-only assertion below would pass on the wrong error and prove nothing — * so the provider is here to make the write actually reach the owner policy. */const providerPlugin = definePlugin(() => {  const store = new Map<string, string>();  return {    id: "memory-provider" as const,    storage: () => ({}),    credentialProviders: [      {        key: ProviderKey.make("memory"),        writable: true,        get: (id: ProviderItemId) => Effect.sync(() => store.get(String(id)) ?? null),        set: (id: ProviderItemId, value: string) =>          Effect.sync(() => void store.set(String(id), value)),        delete: (id: ProviderItemId) => Effect.sync(() => void store.delete(String(id))),      },    ],  };})(); const makePlatformExecutor = (  db: SqliteTestFumaDb,  options?: { readonly platformView?: boolean; readonly subject?: string | null },): Effect.Effect<Executor, never> =>  createExecutor({    tenant: Tenant.make(TENANT),    ...(options?.subject === null ? {} : { subject: Subject.make(options?.subject ?? SUBJECT_A) }),    db: db.db,    plugins: [providerPlugin],    onElicitation: "accept-all",    ...(options?.platformView === false ? {} : { platformView: true }),  }).pipe(Effect.orDie); const requireAdmin = (executor: Executor) => {  const admin = executor.admin;  if (!admin) return Effect.die("expected the platform view to be enabled");  return Effect.succeed(admin);}; describe("platform view — admin.listSubjects", () => {  it.effect("lists every subject in the tenant and no other tenant's", () =>    withDb((db) =>      Effect.gen(function* () {        yield* seed(db);        const executor = yield* makePlatformExecutor(db);        const admin = yield* requireAdmin(executor);         const subjects = yield* admin.listSubjects();         // `user_elsewhere` lives under t2 — invisible at any reach.        expect(subjects.map((entry) => entry.externalId).sort()).toEqual([SUBJECT_A, SUBJECT_B]);      }),    ),  );   it.effect("returns created_at and the bigint last_seen_at through the ORM", () =>    withDb((db) =>      Effect.gen(function* () {        const before = Date.now();        yield* seed(db);        const executor = yield* makePlatformExecutor(db);        const admin = yield* requireAdmin(executor);         const subjects = yield* admin.listSubjects();        const entry = subjects.find((row) => row.externalId === SUBJECT_A);         expect(entry?.createdAt).toBeInstanceOf(Date);        // A raw SQL read would hand back an unusable blob here.        expect(typeof entry?.lastSeenAt).toBe("number");        expect(entry?.lastSeenAt ?? 0).toBeGreaterThanOrEqual(before);        expect(entry?.status).toBeNull();      }),    ),  );   it.effect("pages with a stable order", () =>    withDb((db) =>      Effect.gen(function* () {        yield* seed(db);        const executor = yield* makePlatformExecutor(db);        const admin = yield* requireAdmin(executor);         const all = yield* admin.listSubjects();        const first = yield* admin.listSubjects({ limit: 1 });        const second = yield* admin.listSubjects({ limit: 1, offset: 1 });         expect(all).toHaveLength(2);        expect(first.map((entry) => entry.externalId)).toEqual([all[0]?.externalId]);        expect(second.map((entry) => entry.externalId)).toEqual([all[1]?.externalId]);      }),    ),  );   // Drizzle's SQLite dialect emits OFFSET only alongside LIMIT, and SQLite  // rejects a bare OFFSET — so forwarding the two independently made  // `{ offset }` a SQL syntax error on every SQLite host. The default limit is  // what makes an offset-only page a legal query at all.  it.effect("accepts an offset with no limit", () =>    withDb((db) =>      Effect.gen(function* () {        yield* seed(db);        const admin = yield* requireAdmin(yield* makePlatformExecutor(db));         const all = yield* admin.listSubjects();        const skipped = yield* admin.listSubjects({ offset: 1 });         expect(skipped.map((entry) => entry.externalId)).toEqual([all[1]?.externalId]);        // Past the end is an empty page, not an error.        expect(yield* admin.listSubjects({ offset: 50 })).toEqual([]);        expect(yield* admin.listSubjectsWithConnections({ offset: 1 })).toHaveLength(1);      }),    ),  );   // A fraction is a 400 at the HTTP contract, but the SDK is a public entry  // point of its own: a fractional limit reaching the driver is a `datatype  // mismatch` failure, so it is floored here rather than forwarded.  it.effect("floors a fractional limit and offset instead of failing the query", () =>    withDb((db) =>      Effect.gen(function* () {        yield* seed(db);        const admin = yield* requireAdmin(yield* makePlatformExecutor(db));         const all = yield* admin.listSubjects();         expect(yield* admin.listSubjects({ limit: 1.5 })).toEqual([all[0]]);        expect(yield* admin.listSubjects({ offset: 1.9 })).toEqual([all[1]]);      }),    ),  );   // The no-args call is the one an API consumer writes by default, and  // `listSubjectsWithConnections` turns each returned row into its own  // sequential connection query. Unbounded, that is the whole tenant inside one  // request.  it.effect("bounds an unpaged call to the default page size, and clamps past the maximum", () =>    withDb((db) =>      Effect.gen(function* () {        // One more subject than a default page holds, so "no arguments" and        // "everything" are distinguishable.        for (let index = 0; index < ADMIN_DEFAULT_PAGE_SIZE + 5; index += 1) {          yield* touchSubject(db.db, {            tenant: TENANT,            // Zero-padded: `external_id` breaks `created_at` ties, and these            // rows are written inside the same millisecond.            externalId: `user_${String(index).padStart(4, "0")}`,          });        }         const admin = yield* requireAdmin(yield* makePlatformExecutor(db));         expect(yield* admin.listSubjects()).toHaveLength(ADMIN_DEFAULT_PAGE_SIZE);        expect(yield* admin.listSubjectsWithConnections()).toHaveLength(ADMIN_DEFAULT_PAGE_SIZE);        // A caller may ask for more, up to the ceiling — and no further.        expect(yield* admin.listSubjects({ limit: ADMIN_MAX_PAGE_SIZE * 10 })).toHaveLength(          ADMIN_DEFAULT_PAGE_SIZE + 5,        );      }),    ),  );}); // ---------------------------------------------------------------------------// The platform view is read-only across EVERY surface, not just `admin`.//// A platform executor is subject-less but still bound to the tenant's ORG// partition, so before `writes: "denied"` its ordinary product surfaces formed// an unguarded pure-org executor: it could create, update and delete every// `owner: "org"` row in the tenant through `connections`, `policies`,// `integrations` and `oauth`, while only the `admin` handle was actually// guarded. These pin that the guarantee now covers the whole executor.// --------------------------------------------------------------------------- /** * The owner policy refuses a write by failing the storage effect. `Effect.flip` * makes a SUCCEEDING write a test failure outright rather than something to * assert on afterwards. * * The message is checked too, not just the failure: several of these surfaces * can fail for unrelated reasons BEFORE reaching storage (an absent row, a * missing credential provider), and such a failure would let the assertion pass * without the policy ever having been consulted. */const expectWriteRefused = <A, E extends { readonly _tag: string }>(  effect: Effect.Effect<A, E | StorageError>,) =>  // The surfaces under test fail with different unions (StorageFailure,  // ConnectionNotFoundError, …). The refusal this pins is always a  // StorageError, so the effect is widened for the catch; `orDie` below still  // turns every OTHER tag into a test failure, and a write that SUCCEEDS dies  // rather than reaching an assertion — so the hole can never pass silently.  (effect as Effect.Effect<A, StorageError>).pipe(    Effect.flatMap(() => Effect.die("expected the platform view to refuse this write")),    Effect.catchTag("StorageError", (error: StorageError) => {      expect(error.message).toContain("read-only");      return Effect.void;    }),    Effect.orDie,  ); describe("platform view — read-only across every surface", () => {  it.effect("refuses org-row writes through policies and oauth", () =>    withDb((db) =>      Effect.gen(function* () {        yield* seed(db);        const executor = yield* makePlatformExecutor(db, { subject: null });         yield* expectWriteRefused(          executor.policies.create({ owner: "org", pattern: "tools.*", action: "block" }),        );        yield* expectWriteRefused(          executor.oauth.createClient({            owner: "org",            slug: OAuthClientSlug.make("intruder"),            authorizationUrl: "https://acme.test/authorize",            tokenUrl: "https://acme.test/token",            grant: "authorization_code",            clientId: "intruder-id",            clientSecret: "intruder-secret",          }),        );         // Nothing landed.        expect(yield* executor.policies.list()).toEqual([]);        expect(yield* executor.oauth.listClients()).toEqual([]);      }),    ),  );   // A DELETE is the sharpest case: it carries no values to check against, so  // the reach/writes guard is the only thing between this executor and the  // tenant's org rows.  it.effect("refuses to delete the tenant's org rows", () =>    withDb((db) =>      Effect.gen(function* () {        yield* seed(db);        // A real `integration` row: `integrations.remove` returns success        // without touching storage when the slug is absent, so asserting        // against a missing row would prove nothing.        yield* insertIntegration(db, "stripe");        const executor = yield* makePlatformExecutor(db, { subject: null });         yield* expectWriteRefused(          executor.connections.remove({            owner: "org",            integration: IntegrationSlug.make("stripe"),            name: ConnectionName.make("shared"),          }),        );        yield* expectWriteRefused(          executor.oauth.removeClient("org", OAuthClientSlug.make("anything")),        );        yield* expectWriteRefused(executor.integrations.remove(IntegrationSlug.make("stripe")));         // Every row survived.        const admin = yield* requireAdmin(executor);        expect(yield* admin.listSubjects()).toHaveLength(2);        const connections = yield* Effect.promise(() =>          db.client.execute({ sql: `SELECT name FROM connection WHERE owner = 'org'`, args: [] }),        );        expect(connections.rows).toHaveLength(1);        const integrations = yield* Effect.promise(() =>          db.client.execute({ sql: `SELECT slug FROM integration`, args: [] }),        );        expect(integrations.rows).toHaveLength(1);      }),    ),  );   // Read-only is the WRITE axis only. The ordinary surfaces keep BOUND reach on  // purpose: widening them would hand every subject's connection row —  // credential item ids included — to a surface that only ever needed the admin  // projection.  it.effect("does not widen the ordinary surfaces' reads to other subjects", () =>

Lines 1–500 of 901.