test: generate the external module fixture instead of copying ignored build output
CI / verify (pull_request) Successful in 52s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m48s
CI / image-build (pull_request) Successful in 28s
CI / deploy-smoke (same-origin) (pull_request) Successful in 41s
CI / deploy-smoke (split) (pull_request) Successful in 1m37s
CI / verify (pull_request) Successful in 52s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m48s
CI / image-build (pull_request) Successful in 28s
CI / deploy-smoke (same-origin) (pull_request) Successful in 41s
CI / deploy-smoke (split) (pull_request) Successful in 1m37s
The test proving that migrations of a module installed outside the repository are discovered, while Core's come from the platform, copied templates/module including dist/. dist/ is gitignored and never committed. CI runs `pnpm test` before any build of the template, where only the template's own UI test may have written dist/ui, dist/display, and dist/contributions, so discovery failed with MODULE_ENTRYPOINT_MISSING for ./dist/server/index.js and ./dist/schema/index.js (and, had the UI test not run yet, the copy itself would have failed). Locally a previous `pnpm build` had left dist/ complete. The test now generates a minimal valid installed module in a temporary directory with createMigrationModule, moved unchanged from the migration discovery test into core/tests/support so both share it. It depends on no build output, ignored file, or node_modules. It also asserts more precisely: the module directory is outside the repository, Core's migrations match the platform's own core/drizzle inventory by identity and checksum, and the module's migration matches the SQL that was installed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e36b70d8d4
commit
f122e910fc
@@ -1,14 +1,16 @@
|
||||
import { cp, mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdtemp, readdir, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { discoverRepositoryMigrations } from "../src/cli/migration-command-support.js";
|
||||
import { resolveCommandModulesDirectory } from "../src/cli/modules-directory-environment.js";
|
||||
import { discoverCoreMigrations } from "../src/migrations/index.js";
|
||||
import { ModuleDiscoveryError } from "../src/modules/index.js";
|
||||
import { createMigrationModule } from "./support/migration-module-fixture.js";
|
||||
|
||||
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const templateModule = path.join(repositoryRoot, "templates", "module");
|
||||
const temporaryDirectories: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -23,16 +25,6 @@ async function externalModulesDirectory(): Promise<string> {
|
||||
return directory;
|
||||
}
|
||||
|
||||
/** Installs the module template's migration-bearing package files as module `example`. Migration
|
||||
* discovery imports nothing, so the template's manifest, package metadata, and migrations are all
|
||||
* it reads; the compiled artifacts discovery checks for are copied too when they exist. */
|
||||
async function installTemplate(modulesDirectory: string): Promise<void> {
|
||||
const target = path.join(modulesDirectory, "example");
|
||||
for (const entry of ["package.json", "ekklesia.module.json", "drizzle", "dist"]) {
|
||||
await cp(path.join(templateModule, entry), path.join(target, entry), { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe("operator commands select the modules directory through host configuration", () => {
|
||||
it("defaults to the repository's own modules directory", () => {
|
||||
expect(resolveCommandModulesDirectory({}, "/opt/ekklesia")).toEqual({
|
||||
@@ -62,23 +54,34 @@ describe("operator commands select the modules directory through host configurat
|
||||
});
|
||||
|
||||
it("plans migrations for modules installed outside the repository, with Core's from the platform", async () => {
|
||||
// A minimal installed module generated from nothing in the repository, so this test never
|
||||
// depends on build output or ignored files: a clean checkout runs it before anything is built.
|
||||
const modulesDirectory = await externalModulesDirectory();
|
||||
await installTemplate(modulesDirectory);
|
||||
const installed = await createMigrationModule(modulesDirectory, "example");
|
||||
expect(path.relative(repositoryRoot, modulesDirectory).startsWith("..")).toBe(true);
|
||||
|
||||
const aggregate = await discoverRepositoryMigrations(repositoryRoot, {
|
||||
EKKLESIA_MODULES_DIR: modulesDirectory,
|
||||
});
|
||||
|
||||
const sources = aggregate.modules.map((module) => module.moduleId);
|
||||
expect(sources[0]).toBe("core");
|
||||
expect(sources).toContain("example");
|
||||
expect(
|
||||
aggregate.migrations.some((migration) => migration.identity === "example:0000_initial"),
|
||||
).toBe(true);
|
||||
const coreTags = (await readdir(path.join(repositoryRoot, "core", "drizzle"))).filter((name) =>
|
||||
name.endsWith(".sql"),
|
||||
expect(aggregate.modules.map((module) => module.moduleId)).toEqual(["core", "example"]);
|
||||
|
||||
// Core's migrations are exactly the platform's own, byte for byte, from `core/drizzle`.
|
||||
const platformCore = await discoverCoreMigrations({
|
||||
coreMigrationsDirectory: path.join(repositoryRoot, "core", "drizzle"),
|
||||
});
|
||||
const fingerprint = (migrations: readonly { identity: string; checksum: string }[]) =>
|
||||
migrations.map((migration) => `${migration.identity} ${migration.checksum}`);
|
||||
expect(platformCore.length).toBeGreaterThan(0);
|
||||
expect(fingerprint(aggregate.modules[0]?.migrations ?? [])).toEqual(fingerprint(platformCore));
|
||||
|
||||
// The module's migration is the one installed outside the repository, byte for byte.
|
||||
const installedSql = await readFile(
|
||||
path.join(installed.migrationDirectory ?? "", "0000_initial.sql"),
|
||||
);
|
||||
expect(aggregate.modules[0]?.migrations).toHaveLength(coreTags.length);
|
||||
expect(fingerprint(aggregate.modules[1]?.migrations ?? [])).toEqual([
|
||||
`example:0000_initial ${createHash("sha256").update(installedSql).digest("hex")}`,
|
||||
]);
|
||||
});
|
||||
|
||||
it("fails closed when the configured directory does not exist", async () => {
|
||||
|
||||
Reference in New Issue
Block a user