CI / verify (pull_request) Successful in 1m0s
CI / infrastructure-smoke (pull_request) Successful in 36s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 23s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m30s
551 lines
21 KiB
TypeScript
551 lines
21 KiB
TypeScript
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
discoverModuleSchemas,
|
|
ModuleDiscoveryError,
|
|
ModuleSchemaDiscoveryError,
|
|
type ModuleSchemaDiscoveryIssueCode,
|
|
} from "../src/modules/index.js";
|
|
|
|
interface SchemaModuleFixture {
|
|
readonly manifest?: Record<string, unknown>;
|
|
readonly packageJson?: Record<string, unknown>;
|
|
readonly schemaSource?: string;
|
|
readonly serverSource?: string;
|
|
}
|
|
|
|
const drizzlePgCoreUrl = import.meta.resolve("drizzle-orm/pg-core");
|
|
const temporaryDirectories: string[] = [];
|
|
const eventSymbols: symbol[] = [];
|
|
|
|
afterEach(async () => {
|
|
const globalValues = globalThis as unknown as Record<PropertyKey, unknown>;
|
|
for (const symbol of eventSymbols.splice(0)) {
|
|
delete globalValues[symbol];
|
|
}
|
|
await Promise.all(
|
|
temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true })),
|
|
);
|
|
});
|
|
|
|
async function createModulesDirectory(): Promise<string> {
|
|
const directory = await mkdtemp(path.join(tmpdir(), "ekklesia-schema-"));
|
|
temporaryDirectories.push(directory);
|
|
return directory;
|
|
}
|
|
|
|
function manifestFor(
|
|
id: string,
|
|
options: {
|
|
database?: { abi: 1 };
|
|
dependencies?: Record<string, unknown>;
|
|
schema?: string | false;
|
|
} = {},
|
|
): Record<string, unknown> {
|
|
return {
|
|
manifestVersion: 8,
|
|
id,
|
|
name: `${id} module`,
|
|
version: "0.1.0",
|
|
compatibility: { platform: "^0.1.0", moduleSdk: "^0.1.0" },
|
|
...(options.dependencies ? { dependencies: options.dependencies } : {}),
|
|
capabilities: [],
|
|
permissions: [],
|
|
...(options.database ? { database: options.database } : {}),
|
|
entrypoints: {
|
|
server: "./dist/server/index.js",
|
|
...(options.schema === false ? {} : { schema: options.schema ?? "./dist/schema/index.js" }),
|
|
},
|
|
};
|
|
}
|
|
|
|
async function createSchemaModule(
|
|
modulesDirectory: string,
|
|
id: string,
|
|
fixture: SchemaModuleFixture = {},
|
|
): Promise<void> {
|
|
const moduleDirectory = path.join(modulesDirectory, id);
|
|
await mkdir(path.join(moduleDirectory, "dist/server"), { recursive: true });
|
|
if (fixture.schemaSource !== undefined) {
|
|
await mkdir(path.join(moduleDirectory, "dist/schema"), { recursive: true });
|
|
}
|
|
await Promise.all([
|
|
writeFile(
|
|
path.join(moduleDirectory, "package.json"),
|
|
JSON.stringify(
|
|
fixture.packageJson ?? {
|
|
name: `@test/module-${id}`,
|
|
version: "0.1.0",
|
|
type: "module",
|
|
ekklesia: { layer: "module" },
|
|
},
|
|
),
|
|
),
|
|
writeFile(
|
|
path.join(moduleDirectory, "ekklesia.module.json"),
|
|
JSON.stringify(fixture.manifest ?? manifestFor(id)),
|
|
),
|
|
writeFile(
|
|
path.join(moduleDirectory, "dist/server/index.js"),
|
|
fixture.serverSource ?? `export default { id: ${JSON.stringify(id)}, version: "0.1.0" };`,
|
|
),
|
|
...(fixture.schemaSource === undefined
|
|
? []
|
|
: [writeFile(path.join(moduleDirectory, "dist/schema/index.js"), fixture.schemaSource)]),
|
|
]);
|
|
}
|
|
|
|
function createGlobalEventRecorder(): { events: string[]; expression: string } {
|
|
const key = `ekklesia.schema.test.${eventSymbols.length}.${Date.now()}.${Math.random()}`;
|
|
const symbol = Symbol.for(key);
|
|
const events: string[] = [];
|
|
(globalThis as unknown as Record<PropertyKey, unknown>)[symbol] = events;
|
|
eventSymbols.push(symbol);
|
|
return { events, expression: `globalThis[Symbol.for(${JSON.stringify(key)})]` };
|
|
}
|
|
|
|
function discoveryOptions(modulesDirectory: string) {
|
|
return {
|
|
moduleSdkVersion: "0.1.0",
|
|
modulesDirectory,
|
|
platformVersion: "0.1.0",
|
|
};
|
|
}
|
|
|
|
async function captureSchemaError(modulesDirectory: string): Promise<ModuleSchemaDiscoveryError> {
|
|
try {
|
|
await discoverModuleSchemas(discoveryOptions(modulesDirectory));
|
|
} catch (error) {
|
|
if (error instanceof ModuleSchemaDiscoveryError) {
|
|
return error;
|
|
}
|
|
throw error;
|
|
}
|
|
throw new Error("Expected module schema discovery to fail.");
|
|
}
|
|
|
|
function issueCodes(error: ModuleSchemaDiscoveryError): ModuleSchemaDiscoveryIssueCode[] {
|
|
return error.issues.map((issue) => issue.code);
|
|
}
|
|
|
|
describe("module schema discovery", () => {
|
|
it("loads ABI-v1 schema entrypoints as plain metadata without importing an ORM", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
await createSchemaModule(modulesDirectory, "calendar", {
|
|
manifest: manifestFor("calendar", { database: { abi: 1 } }),
|
|
schemaSource: `
|
|
export default {
|
|
moduleId: "calendar",
|
|
tables: [
|
|
{ name: "calendar_events", foreignKeys: [] },
|
|
{ name: "calendar_attendees", foreignKeys: [{ table: "calendar_events" }] }
|
|
]
|
|
};
|
|
`,
|
|
});
|
|
|
|
const aggregate = await discoverModuleSchemas(discoveryOptions(modulesDirectory));
|
|
|
|
expect(aggregate.tables.map((table) => table.name)).toEqual([
|
|
"calendar_attendees",
|
|
"calendar_events",
|
|
]);
|
|
expect(aggregate.tables.every((table) => table.table === undefined)).toBe(true);
|
|
expect(aggregate.drizzleSchema).toEqual({});
|
|
});
|
|
|
|
it("aggregates ABI-v1 prefix, duplicate, cross-module FK, missing target, and malformed errors", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
await createSchemaModule(modulesDirectory, "alpha", {
|
|
manifest: manifestFor("alpha", { database: { abi: 1 } }),
|
|
schemaSource: `export default {
|
|
moduleId: "alpha",
|
|
tables: [
|
|
{ name: "wrong_records", foreignKeys: [] },
|
|
{ name: "alpha_records", foreignKeys: [{ table: "beta_records" }] },
|
|
{ name: "alpha_records", foreignKeys: [{ table: "alpha_missing" }] }
|
|
]
|
|
};`,
|
|
});
|
|
await createSchemaModule(modulesDirectory, "malformed", {
|
|
manifest: manifestFor("malformed", { database: { abi: 1 } }),
|
|
schemaSource: `export default { moduleId: "malformed", tables: [{ name: "malformed_rows", unknown: true }] };`,
|
|
});
|
|
|
|
const error = await captureSchemaError(modulesDirectory);
|
|
|
|
expect(issueCodes(error)).toEqual(
|
|
expect.arrayContaining([
|
|
"MODULE_SCHEMA_CROSS_MODULE_FOREIGN_KEY",
|
|
"MODULE_SCHEMA_EXPORT_INVALID",
|
|
"MODULE_SCHEMA_FOREIGN_KEY_TARGET_MISSING",
|
|
"MODULE_SCHEMA_TABLE_DUPLICATE",
|
|
"MODULE_SCHEMA_TABLE_NAMESPACE_INVALID",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("returns a frozen empty aggregate without importing module servers", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
const recorder = createGlobalEventRecorder();
|
|
await createSchemaModule(modulesDirectory, "server-only", {
|
|
manifest: manifestFor("server-only", { schema: false }),
|
|
serverSource: `${recorder.expression}.push("server-imported"); export default { id: "server-only", version: "0.1.0" };`,
|
|
});
|
|
|
|
const aggregate = await discoverModuleSchemas(discoveryOptions(modulesDirectory));
|
|
|
|
expect(aggregate.discoveredModules).toHaveLength(1);
|
|
expect(aggregate.modules).toEqual([]);
|
|
expect(aggregate.tables).toEqual([]);
|
|
expect(aggregate.drizzleSchema).toEqual({});
|
|
expect(recorder.events).toEqual([]);
|
|
expect(Object.isFrozen(aggregate)).toBe(true);
|
|
expect(Object.isFrozen(aggregate.tables)).toBe(true);
|
|
});
|
|
|
|
it("completes all static module discovery before importing any schema code", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
const recorder = createGlobalEventRecorder();
|
|
await createSchemaModule(modulesDirectory, "valid", {
|
|
schemaSource: `${recorder.expression}.push("schema-imported"); export default {};`,
|
|
});
|
|
await createSchemaModule(modulesDirectory, "missing-schema");
|
|
|
|
let discoveryError: ModuleDiscoveryError | undefined;
|
|
try {
|
|
await discoverModuleSchemas(discoveryOptions(modulesDirectory));
|
|
} catch (error) {
|
|
if (error instanceof ModuleDiscoveryError) {
|
|
discoveryError = error;
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
expect(discoveryError?.issues).toContainEqual(
|
|
expect.objectContaining({
|
|
code: "MODULE_ENTRYPOINT_MISSING",
|
|
moduleId: "missing-schema",
|
|
path: "entrypoints.schema",
|
|
}),
|
|
);
|
|
expect(recorder.events).toEqual([]);
|
|
});
|
|
|
|
it("loads and aggregates module tables deterministically", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
const recorder = createGlobalEventRecorder();
|
|
await createSchemaModule(modulesDirectory, "calendar", {
|
|
manifest: manifestFor("calendar", {
|
|
dependencies: { modules: { required: { people: "^0.1.0" } } },
|
|
}),
|
|
schemaSource: `
|
|
import { integer, pgTable, text } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
${recorder.expression}.push("calendar:schema");
|
|
const events = pgTable("calendar_events", { id: integer("id").primaryKey() });
|
|
const locations = pgTable("calendar_locations", {
|
|
id: integer("id").primaryKey(),
|
|
eventId: integer("event_id").references(() => events.id)
|
|
});
|
|
export default { moduleId: "calendar", tables: { locations, events } };
|
|
`,
|
|
serverSource: 'throw new Error("server entrypoint must remain unloaded");',
|
|
});
|
|
await createSchemaModule(modulesDirectory, "people", {
|
|
schemaSource: `
|
|
import { integer, pgTable, text } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
${recorder.expression}.push("people:schema");
|
|
const records = pgTable("people_records", { id: integer("id").primaryKey() });
|
|
const notes = pgTable("people_notes", {
|
|
id: integer("id").primaryKey(),
|
|
recordId: integer("record_id").references(() => records.id),
|
|
body: text("body")
|
|
});
|
|
export default { moduleId: "people", tables: { records, notes } };
|
|
`,
|
|
});
|
|
|
|
const aggregate = await discoverModuleSchemas(discoveryOptions(modulesDirectory));
|
|
|
|
expect(recorder.events).toEqual(["people:schema", "calendar:schema"]);
|
|
expect(aggregate.modules.map((module) => module.moduleId)).toEqual(["people", "calendar"]);
|
|
expect(aggregate.modules[0]?.tables.map((table) => table.exportName)).toEqual([
|
|
"notes",
|
|
"records",
|
|
]);
|
|
expect(aggregate.modules[1]?.tables.map((table) => table.exportName)).toEqual([
|
|
"events",
|
|
"locations",
|
|
]);
|
|
expect(aggregate.tables.map((table) => table.name)).toEqual([
|
|
"calendar_events",
|
|
"calendar_locations",
|
|
"people_notes",
|
|
"people_records",
|
|
]);
|
|
expect(Object.keys(aggregate.drizzleSchema)).toEqual(
|
|
aggregate.tables.map((table) => table.name),
|
|
);
|
|
expect(Object.isFrozen(aggregate.modules)).toBe(true);
|
|
expect(Object.isFrozen(aggregate.drizzleSchema)).toBe(true);
|
|
expect(Object.isFrozen(aggregate.modules[0])).toBe(true);
|
|
expect(Object.isFrozen(aggregate.modules[0]?.tables)).toBe(true);
|
|
expect(Object.isFrozen(aggregate.tables[0])).toBe(true);
|
|
});
|
|
|
|
it("aggregates import, export, and ownership-marker failures without leaking details", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
await createSchemaModule(modulesDirectory, "alpha", {
|
|
schemaSource: 'throw new Error("database-password at /machine/private/path");',
|
|
});
|
|
await createSchemaModule(modulesDirectory, "beta", {
|
|
schemaSource: 'export default { moduleId: "beta", tables: { rows: {} } };',
|
|
});
|
|
await createSchemaModule(modulesDirectory, "gamma", {
|
|
schemaSource: `
|
|
import { integer, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
const rows = pgTable("gamma_rows", { id: integer("id") });
|
|
export default { moduleId: "wrong", tables: { rows } };
|
|
`,
|
|
});
|
|
|
|
const error = await captureSchemaError(modulesDirectory);
|
|
|
|
expect(issueCodes(error)).toEqual([
|
|
"MODULE_SCHEMA_IMPORT_FAILED",
|
|
"MODULE_SCHEMA_EXPORT_INVALID",
|
|
"MODULE_SCHEMA_MODULE_ID_MISMATCH",
|
|
]);
|
|
const report = `${error.message}\n${error.stack}\n${JSON.stringify(error.issues)}`;
|
|
expect(report).not.toContain("database-password");
|
|
expect(report).not.toContain("/machine/private/path");
|
|
expect(report).not.toContain(modulesDirectory);
|
|
});
|
|
|
|
it("rejects malformed, unnamespaced, duplicate, and custom-schema tables", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
await createSchemaModule(modulesDirectory, "example", {
|
|
schemaSource: `
|
|
import { integer, pgSchema, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
const duplicateA = pgTable("example_records", { id: integer("id") });
|
|
const duplicateB = pgTable("example_records", { id: integer("id") });
|
|
const invalid = pgTable("secret at /machine/path", { id: integer("id") });
|
|
const invalidLocal = pgTable("example_1records", { id: integer("id") });
|
|
const other = pgTable("other_records", { id: integer("id") });
|
|
const privateTable = pgSchema("private").table("example_private", { id: integer("id") });
|
|
export default {
|
|
moduleId: "example",
|
|
tables: {
|
|
repeated: duplicateA,
|
|
privateTable,
|
|
other,
|
|
invalid,
|
|
invalidLocal,
|
|
duplicateB,
|
|
duplicateA
|
|
}
|
|
};
|
|
`,
|
|
});
|
|
|
|
const error = await captureSchemaError(modulesDirectory);
|
|
|
|
expect(issueCodes(error)).toEqual(
|
|
expect.arrayContaining([
|
|
"MODULE_SCHEMA_POSTGRES_SCHEMA_UNSUPPORTED",
|
|
"MODULE_SCHEMA_TABLE_DUPLICATE",
|
|
"MODULE_SCHEMA_TABLE_NAME_INVALID",
|
|
"MODULE_SCHEMA_TABLE_NAMESPACE_INVALID",
|
|
]),
|
|
);
|
|
expect(
|
|
issueCodes(error).filter((code) => code === "MODULE_SCHEMA_TABLE_DUPLICATE"),
|
|
).toHaveLength(2);
|
|
expect(
|
|
issueCodes(error).filter((code) => code === "MODULE_SCHEMA_TABLE_NAME_INVALID"),
|
|
).toHaveLength(2);
|
|
expect(JSON.stringify(error.issues)).not.toContain("secret at /machine/path");
|
|
});
|
|
|
|
it("rejects duplicate physical names across module namespaces", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
for (const [moduleId, exportName] of [
|
|
["foo", "barRows"],
|
|
["foo-bar", "rows"],
|
|
] as const) {
|
|
await createSchemaModule(modulesDirectory, moduleId, {
|
|
schemaSource: `
|
|
import { integer, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
const table = pgTable("foo_bar_rows", { id: integer("id") });
|
|
export default { moduleId: ${JSON.stringify(moduleId)}, tables: { ${exportName}: table } };
|
|
`,
|
|
});
|
|
}
|
|
|
|
const error = await captureSchemaError(modulesDirectory);
|
|
expect(error.issues).toEqual([
|
|
expect.objectContaining({
|
|
code: "MODULE_SCHEMA_TABLE_DUPLICATE",
|
|
moduleId: "foo-bar",
|
|
tableName: "foo_bar_rows",
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("rejects cross-module and omitted local foreign-key targets", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
await createSchemaModule(modulesDirectory, "people", {
|
|
schemaSource: `
|
|
import { integer, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
export const records = pgTable("people_records", { id: integer("id").primaryKey() });
|
|
export default { moduleId: "people", tables: { records } };
|
|
`,
|
|
});
|
|
await createSchemaModule(modulesDirectory, "calendar", {
|
|
manifest: manifestFor("calendar", {
|
|
dependencies: { modules: { required: { people: "^0.1.0" } } },
|
|
}),
|
|
schemaSource: `
|
|
import { integer, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
import { records } from "../../../people/dist/schema/index.js";
|
|
const privateTable = pgTable("calendar_private", { id: integer("id").primaryKey() });
|
|
const events = pgTable("calendar_events", {
|
|
id: integer("id"),
|
|
personId: integer("person_id").references(() => records.id)
|
|
});
|
|
const attendees = pgTable("calendar_attendees", {
|
|
id: integer("id"),
|
|
privateId: integer("private_id").references(() => privateTable.id)
|
|
});
|
|
export default { moduleId: "calendar", tables: { events, attendees } };
|
|
`,
|
|
});
|
|
|
|
const error = await captureSchemaError(modulesDirectory);
|
|
|
|
expect(issueCodes(error)).toEqual([
|
|
"MODULE_SCHEMA_FOREIGN_KEY_TARGET_MISSING",
|
|
"MODULE_SCHEMA_CROSS_MODULE_FOREIGN_KEY",
|
|
]);
|
|
});
|
|
|
|
it("rejects compound foreign keys that mix target-table ownership", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
await createSchemaModule(modulesDirectory, "people", {
|
|
schemaSource: `
|
|
import { integer, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
export const records = pgTable("people_records", {
|
|
id: integer("id").notNull(),
|
|
alternateId: integer("alternate_id").notNull()
|
|
});
|
|
export default { moduleId: "people", tables: { records } };
|
|
`,
|
|
});
|
|
await createSchemaModule(modulesDirectory, "calendar", {
|
|
manifest: manifestFor("calendar", {
|
|
dependencies: { modules: { required: { people: "^0.1.0" } } },
|
|
}),
|
|
schemaSource: `
|
|
import { foreignKey, integer, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
import { records } from "../../../people/dist/schema/index.js";
|
|
const targets = pgTable("calendar_targets", {
|
|
id: integer("id").notNull(),
|
|
alternateId: integer("alternate_id").notNull()
|
|
});
|
|
const links = pgTable("calendar_links", {
|
|
targetId: integer("target_id").notNull(),
|
|
alternateId: integer("alternate_id").notNull()
|
|
}, (table) => [
|
|
foreignKey({
|
|
columns: [table.targetId, table.alternateId],
|
|
foreignColumns: [targets.id, records.alternateId]
|
|
})
|
|
]);
|
|
export default { moduleId: "calendar", tables: { links, targets } };
|
|
`,
|
|
});
|
|
|
|
const error = await captureSchemaError(modulesDirectory);
|
|
expect(issueCodes(error)).toEqual(["MODULE_SCHEMA_FOREIGN_KEY_INVALID"]);
|
|
});
|
|
|
|
it("rejects table aliases and sanitizes hostile metadata proxies", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
await createSchemaModule(modulesDirectory, "alias-table", {
|
|
schemaSource: `
|
|
import { alias, integer, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
const base = pgTable("private_records", { id: integer("id") });
|
|
const records = alias(base, "alias_table_records");
|
|
export default { moduleId: "alias-table", tables: { records } };
|
|
`,
|
|
});
|
|
await createSchemaModule(modulesDirectory, "proxy-table", {
|
|
schemaSource: `
|
|
import { integer, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
const base = pgTable("proxy_table_records", { id: integer("id") });
|
|
const aliasSymbol = Symbol.for("drizzle:IsAlias");
|
|
let aliasReads = 0;
|
|
const records = new Proxy(base, {
|
|
get(target, key, receiver) {
|
|
if (key === aliasSymbol && ++aliasReads > 1) {
|
|
throw new Error("proxy-secret at /machine/private/schema");
|
|
}
|
|
return Reflect.get(target, key, receiver);
|
|
}
|
|
});
|
|
export default { moduleId: "proxy-table", tables: { records } };
|
|
`,
|
|
});
|
|
|
|
const error = await captureSchemaError(modulesDirectory);
|
|
expect(issueCodes(error)).toEqual([
|
|
"MODULE_SCHEMA_EXPORT_INVALID",
|
|
"MODULE_SCHEMA_TABLE_INVALID",
|
|
]);
|
|
const report = `${error.stack}\n${JSON.stringify(error.issues)}`;
|
|
expect(report).not.toContain("proxy-secret");
|
|
expect(report).not.toContain("/machine/private/schema");
|
|
});
|
|
|
|
it("sanitizes errors thrown while resolving lazy foreign-key metadata", async () => {
|
|
const modulesDirectory = await createModulesDirectory();
|
|
await createSchemaModule(modulesDirectory, "unsafe", {
|
|
schemaSource: `
|
|
import { integer, pgTable } from ${JSON.stringify(drizzlePgCoreUrl)};
|
|
const rows = pgTable("unsafe_rows", {
|
|
id: integer("id"),
|
|
ownerId: integer("owner_id").references(() => {
|
|
throw new Error("foreign-key-secret at /machine/schema/path");
|
|
})
|
|
});
|
|
export default { moduleId: "unsafe", tables: { rows } };
|
|
`,
|
|
});
|
|
|
|
const error = await captureSchemaError(modulesDirectory);
|
|
expect(issueCodes(error)).toEqual(["MODULE_SCHEMA_FOREIGN_KEY_INVALID"]);
|
|
expect(JSON.stringify(error.issues)).not.toContain("foreign-key-secret");
|
|
expect(JSON.stringify(error.issues)).not.toContain("/machine/schema/path");
|
|
});
|
|
|
|
it("produces schema diagnostics independent of the absolute modules path", async () => {
|
|
const firstModulesDirectory = await createModulesDirectory();
|
|
const secondModulesDirectory = await createModulesDirectory();
|
|
for (const modulesDirectory of [firstModulesDirectory, secondModulesDirectory]) {
|
|
await createSchemaModule(modulesDirectory, "broken", {
|
|
schemaSource: 'throw new Error("secret at /machine/schema/path");',
|
|
});
|
|
}
|
|
|
|
const [firstError, secondError] = await Promise.all([
|
|
captureSchemaError(firstModulesDirectory),
|
|
captureSchemaError(secondModulesDirectory),
|
|
]);
|
|
|
|
expect(firstError.issues).toEqual(secondError.issues);
|
|
expect(firstError.stack).toBe(secondError.stack);
|
|
});
|
|
});
|