Skip to content

Commit 8925d2f

Browse files
committed
Harden scoped FumaDB policy tests
1 parent e983a6a commit 8925d2f

9 files changed

Lines changed: 582 additions & 41 deletions

File tree

apps/local/src/server/executor.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import * as fs from "node:fs";
55
import { homedir } from "node:os";
66
import { basename, join } from "node:path";
77

8-
import { Scope, ScopeId, collectTables, createExecutor, type AnyPlugin } from "@executor-js/sdk";
8+
import {
9+
Scope,
10+
ScopeId,
11+
collectTables,
12+
createExecutor,
13+
type AnyPlugin,
14+
withQueryContext,
15+
} from "@executor-js/sdk";
916
import { loadPluginsFromJsonc } from "@executor-js/config";
1017

1118
import executorConfig from "../../executor.config";
@@ -189,7 +196,9 @@ const importLegacySqliteIfNeeded = async (options: {
189196
const result = await importSqliteDataToFuma({
190197
sqlitePath: storage.sqlitePath,
191198
markerPath: storage.importMarkerPath,
192-
db: target.db,
199+
target: withQueryContext(target.db, {
200+
allowedScopeIds: new Set([scopeId]),
201+
}),
193202
tables,
194203
scopeId,
195204
});

apps/local/src/server/sqlite-import.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,11 @@ describe("importSqliteDataToFuma", () => {
8181
path: join(workDir, "target.db"),
8282
});
8383

84+
const scopedDb = withQueryContext(sqlite.db, { allowedScopeIds: new Set(["scope_a"]) });
8485
const result = await importSqliteDataToFuma({
8586
sqlitePath,
8687
markerPath,
87-
db: sqlite.db,
88+
target: scopedDb,
8889
tables,
8990
scopeId: "scope_a",
9091
});
@@ -96,9 +97,7 @@ describe("importSqliteDataToFuma", () => {
9697
expect(existsSync(sqlitePath)).toBe(false);
9798
expect(result.backupPath && existsSync(result.backupPath)).toBe(true);
9899

99-
const db = withQueryContext(sqlite.db, { allowedScopeIds: new Set(["scope_a"]) });
100-
101-
const source = (await db.findFirst("source", {
100+
const source = (await scopedDb.findFirst("source", {
102101
where: (b) => b("id", "=", "src_1"),
103102
})) as Record<string, unknown>;
104103
expect(source.scope_id).toBe("scope_a");
@@ -107,7 +106,7 @@ describe("importSqliteDataToFuma", () => {
107106
expect(source.can_edit).toBe(true);
108107
expect(source.created_at).toBeInstanceOf(Date);
109108

110-
const blob = (await db.findFirst("blob", {
109+
const blob = (await scopedDb.findFirst("blob", {
111110
where: (b) => b("id", "=", JSON.stringify(["scope_a/plugin", "spec"])),
112111
})) as Record<string, unknown>;
113112
expect(blob.value).toBe("{}");

apps/local/src/server/sqlite-import.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@ import { dirname } from "node:path";
66

77
/* oxlint-disable executor/no-json-parse, executor/no-switch-statement, executor/no-try-catch-or-throw -- boundary: one-shot legacy SQLite importer normalizes unknown rows and wraps native sqlite failures */
88

9-
import {
10-
withQueryContext,
11-
type AnyColumn,
12-
type AnyTable,
13-
type FumaDb,
14-
type FumaTables,
15-
} from "@executor-js/sdk";
9+
import { type AnyColumn, type AnyTable, type FumaTables } from "@executor-js/sdk";
1610

1711
type SqliteRow = Record<string, unknown>;
1812

@@ -31,7 +25,7 @@ export class LocalSqliteImportError extends Data.TaggedError("LocalSqliteImportE
3125
export interface LocalSqliteImportOptions {
3226
readonly sqlitePath: string;
3327
readonly markerPath: string;
34-
readonly db: FumaDb;
28+
readonly target: ImportFumaDb;
3529
readonly tables: FumaTables;
3630
readonly scopeId: string;
3731
}
@@ -186,11 +180,8 @@ export const importSqliteDataToFuma = async (
186180
sqlite = new Database(options.sqlitePath, { readonly: true });
187181
const importedTables: string[] = [];
188182
let importedRows = 0;
189-
const dbWithScopeContext = withQueryContext(options.db, {
190-
allowedScopeIds: new Set([options.scopeId]),
191-
});
192183

193-
await (dbWithScopeContext as ImportFumaDb).transaction(async (db) => {
184+
await options.target.transaction(async (db) => {
194185
for (const [tableKey, table] of Object.entries(options.tables)) {
195186
const tableName = table.names.sql;
196187
if (!tableExists(sqlite!, tableName)) continue;

packages/core/api/src/scoped-targets.test.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
definePlugin,
1616
makeTestConfig,
1717
type Executor,
18-
withQueryContext,
1918
} from "@executor-js/sdk";
2019
import { memorySecretsPlugin } from "@executor-js/sdk/testing";
2120

@@ -176,9 +175,7 @@ describe("core API explicit target scopes", () => {
176175
expect(response.status).toBe(200);
177176
const rows = toScopeRows(
178177
yield* Effect.promise(() =>
179-
withQueryContext(config.db, {
180-
allowedScopeIds: new Set([String(userScope), String(orgScope)]),
181-
}).findMany("connection", {
178+
config.db.findMany("connection", {
182179
where: (b) => b("id", "=", connectionId),
183180
}),
184181
),
@@ -225,11 +222,7 @@ describe("core API explicit target scopes", () => {
225222
);
226223

227224
expect(response.status).toBe(400);
228-
const sessions = yield* Effect.promise(() =>
229-
withQueryContext(config.db, {
230-
allowedScopeIds: new Set([String(userScope), String(orgScope)]),
231-
}).findMany("oauth2_session"),
232-
);
225+
const sessions = yield* Effect.promise(() => config.db.findMany("oauth2_session"));
233226
expect(sessions).toEqual([]);
234227
}),
235228
);

packages/core/sdk/src/executor.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { OAuthEndpointUrlPolicy } from "./oauth-helpers";
1919
import { generateKeyBetween } from "fractional-indexing";
2020
import {
2121
StorageError,
22+
isStorageFailure,
2223
makeFumaClient,
2324
type FumaDb,
2425
type FumaRow,
@@ -400,13 +401,20 @@ export const collectTables = (plugins: readonly AnyPlugin[]): FumaTables => {
400401
}
401402
}
402403

403-
for (const tableDef of Object.values(merged)) {
404-
assertExecutorScopePolicyTable(tableDef);
405-
}
404+
validateExecutorScopePolicyTables(merged);
406405

407406
return merged;
408407
};
409408

409+
const validateExecutorScopePolicyTables = (tables: FumaTables): void => {
410+
for (const tableDef of Object.values(tables)) {
411+
assertExecutorScopePolicyTable(tableDef);
412+
}
413+
};
414+
415+
const storageFailureFromUnknown = (message: string, cause: unknown): StorageFailure =>
416+
isStorageFailure(cause) ? cause : new StorageError({ message, cause });
417+
410418
const createDefaultMemoryDb = (tables: FumaTables): ExecutorDb => {
411419
const version = "1.0.0";
412420
const latestSchema = fumaSchema<string, FumaTables, RelationsMap<FumaTables>>({
@@ -790,14 +798,22 @@ export const createExecutor = <const TPlugins extends readonly AnyPlugin[] = rea
790798
return empty as TPlugins;
791799
};
792800
const { scopes, plugins = defaultPlugins() } = config;
801+
const tables = yield* Effect.try({
802+
try: () => collectTables(plugins),
803+
catch: (cause) => storageFailureFromUnknown("Failed to collect executor tables", cause),
804+
});
793805
const dbInput = yield* Effect.suspend(() => {
794-
if (!config.db) return Effect.succeed(createDefaultMemoryDb(collectTables(plugins)));
806+
if (!config.db) return Effect.succeed(createDefaultMemoryDb(tables));
795807
if (typeof config.db !== "function") return Effect.succeed(config.db);
796-
const out = config.db({ tables: collectTables(plugins) });
808+
const out = config.db({ tables });
797809
return Effect.isEffect(out) ? out : Effect.succeed(out);
798810
});
799811
const rootDbUntyped = "db" in dbInput ? dbInput.db : dbInput;
800812
const closeDb = "db" in dbInput ? dbInput.close : undefined;
813+
yield* Effect.try({
814+
try: () => validateExecutorScopePolicyTables(rootDbUntyped.internal.tables),
815+
catch: (cause) => storageFailureFromUnknown("Failed to validate executor tables", cause),
816+
});
801817

802818
if (scopes.length === 0) {
803819
return yield* new StorageError({

0 commit comments

Comments
 (0)