feat(FN-3986): remove PR lint pre-build and bun setup from pr-checks workfl
Removes the PR lint pre-build step from the GitHub Actions workflow and adds a new test suite (`ci-workflow.test.ts`) covering CI workflow behavior, with a small documentation update. Fusion-Task-Id: FN-3986
This commit is contained in:
6
.github/workflows/pr-checks.yml
vendored
6
.github/workflows/pr-checks.yml
vendored
@@ -29,12 +29,6 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Install Bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
|
|
||||||
- name: Build plugins and core packages
|
|
||||||
run: pnpm build
|
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: pnpm lint
|
run: pnpm lint
|
||||||
|
|
||||||
|
|||||||
@@ -30,3 +30,4 @@ why it broke, how it was fixed, and what command verified the fix.
|
|||||||
| TSH-012 | Warning filtering | The first SQLite warning filter also installed a `process.on("warning")` listener that could duplicate unrelated warnings. | Node still prints non-filtered warnings by default, so manually writing them again made future warning output noisier. | The shared setup now only wraps `process.emitWarning` for the known SQLite experimental warning, and the dashboard noisy-output marker list no longer suppresses generic trace-warning guidance. | `pnpm --filter @fusion/core typecheck`, `pnpm --filter @fusion/core exec vitest run src/__tests__/central-db.test.ts --silent=passed-only --reporter=dot`, and `pnpm test:full` |
|
| TSH-012 | Warning filtering | The first SQLite warning filter also installed a `process.on("warning")` listener that could duplicate unrelated warnings. | Node still prints non-filtered warnings by default, so manually writing them again made future warning output noisier. | The shared setup now only wraps `process.emitWarning` for the known SQLite experimental warning, and the dashboard noisy-output marker list no longer suppresses generic trace-warning guidance. | `pnpm --filter @fusion/core typecheck`, `pnpm --filter @fusion/core exec vitest run src/__tests__/central-db.test.ts --silent=passed-only --reporter=dot`, and `pnpm test:full` |
|
||||||
| TSH-013 | Dashboard default runtime | The default dashboard package test still took about 9 minutes after the noise cleanup. | `pnpm --filter @fusion/dashboard test` continued to run every app/jsdom and API/node file, including exhaustive modal/view permutations and broad route matrices intended for deeper sweeps. | Added curated `dashboard-app-quality` and `dashboard-api-quality` Vitest projects for the default package gate, kept exhaustive coverage behind `test:deep`, `test:app`, and `test:api`, and documented when to run each lane. | `/usr/bin/time -p pnpm --filter @fusion/dashboard test` (148 files, 3642 tests, `real 91.73`), `/usr/bin/time -p pnpm --filter @fusion/dashboard test:deep` (419 files, 10747 tests, `real 335.47`), and `/usr/bin/time -p pnpm test:full` (`real 308.34`) |
|
| TSH-013 | Dashboard default runtime | The default dashboard package test still took about 9 minutes after the noise cleanup. | `pnpm --filter @fusion/dashboard test` continued to run every app/jsdom and API/node file, including exhaustive modal/view permutations and broad route matrices intended for deeper sweeps. | Added curated `dashboard-app-quality` and `dashboard-api-quality` Vitest projects for the default package gate, kept exhaustive coverage behind `test:deep`, `test:app`, and `test:api`, and documented when to run each lane. | `/usr/bin/time -p pnpm --filter @fusion/dashboard test` (148 files, 3642 tests, `real 91.73`), `/usr/bin/time -p pnpm --filter @fusion/dashboard test:deep` (419 files, 10747 tests, `real 335.47`), and `/usr/bin/time -p pnpm test:full` (`real 308.34`) |
|
||||||
| TSH-014 | Core temp-dir cleanup | `kb-db-test-*` directories could leak after `db.test.ts` runs and trip isolation checks. | `afterEach(async)` and module-level `afterAll(async)` both cleared `createdTmpDirs` before async `rm(...)` completed, so hook timing races could hide leftovers from final teardown during worker shutdown. | Coordinated cleanup bookkeeping by deleting set entries only after per-dir removal, kept `afterEach` as async best-effort cleanup, and switched the final `afterAll` fallback to defensive synchronous `rmSync(..., { recursive: true, force: true })`. | `pnpm --filter @fusion/core test -- src/__tests__/db.test.ts` and `node scripts/check-test-isolation.mjs --before && pnpm --filter @fusion/core test && node scripts/check-test-isolation.mjs` |
|
| TSH-014 | Core temp-dir cleanup | `kb-db-test-*` directories could leak after `db.test.ts` runs and trip isolation checks. | `afterEach(async)` and module-level `afterAll(async)` both cleared `createdTmpDirs` before async `rm(...)` completed, so hook timing races could hide leftovers from final teardown during worker shutdown. | Coordinated cleanup bookkeeping by deleting set entries only after per-dir removal, kept `afterEach` as async best-effort cleanup, and switched the final `afterAll` fallback to defensive synchronous `rmSync(..., { recursive: true, force: true })`. | `pnpm --filter @fusion/core test -- src/__tests__/db.test.ts` and `node scripts/check-test-isolation.mjs --before && pnpm --filter @fusion/core test && node scripts/check-test-isolation.mjs` |
|
||||||
|
| TSH-015 | PR lint workflow contract | PR `lint` spent extra CI minutes setting up Bun and running `pnpm build` before lint. | `pr-checks.yml` drifted from the CI pattern where lint should be the fastest install+lint gate and build should be enforced by the separate `build` job. | Removed Bun setup and pre-lint build from the PR `lint` job, and strengthened workflow contract tests to require `pnpm install --frozen-lockfile` + `pnpm lint` in lint while asserting Bun/build remain in the PR `build` job. | `pnpm --filter @runfusion/fusion exec vitest run src/__tests__/ci-workflow.test.ts --silent=passed-only --reporter=dot`, `pnpm lint`, `pnpm test`, `pnpm typecheck`, and `pnpm build` |
|
||||||
|
|||||||
@@ -178,8 +178,38 @@ describe("PR checks workflow (.github/workflows/pr-checks.yml)", () => {
|
|||||||
expect(content).not.toContain("run: pnpm test\n");
|
expect(content).not.toContain("run: pnpm test\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps lint as install + lint only, without Bun/setup build coupling", () => {
|
||||||
|
const lintSteps = workflow.jobs?.lint?.steps ?? [];
|
||||||
|
expect(
|
||||||
|
lintSteps.some(
|
||||||
|
(step: any) =>
|
||||||
|
step.name === "Install dependencies" &&
|
||||||
|
typeof step.run === "string" &&
|
||||||
|
step.run.includes("pnpm install --frozen-lockfile"),
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
lintSteps.some((step: any) => step.name === "Lint" && typeof step.run === "string" && step.run.includes("pnpm lint")),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
lintSteps.some(
|
||||||
|
(step: any) =>
|
||||||
|
step.name === "Install Bun" ||
|
||||||
|
(typeof step.uses === "string" && step.uses.includes("oven-sh/setup-bun")) ||
|
||||||
|
(typeof step.run === "string" && step.run.includes("pnpm build")),
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps build coverage as an explicit PR gate", () => {
|
it("keeps build coverage as an explicit PR gate", () => {
|
||||||
const buildSteps = workflow.jobs?.build?.steps ?? [];
|
const buildSteps = workflow.jobs?.build?.steps ?? [];
|
||||||
|
expect(
|
||||||
|
buildSteps.some(
|
||||||
|
(step: any) =>
|
||||||
|
step.name === "Install Bun" ||
|
||||||
|
(typeof step.uses === "string" && step.uses.includes("oven-sh/setup-bun")),
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
expect(
|
expect(
|
||||||
buildSteps.some(
|
buildSteps.some(
|
||||||
(step: any) => step.name === "Build" && typeof step.run === "string" && step.run.includes("pnpm build"),
|
(step: any) => step.name === "Build" && typeof step.run === "string" && step.run.includes("pnpm build"),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { ManagedDockerNode, MeshConnectionConfig } from "../types.js";
|
import type { ManagedDockerNode, MeshConnectionConfig } from "../types.js";
|
||||||
|
|
||||||
// ── Mocks ──────────────────────────────────────────────────────────────────
|
// ── Mocks ──────────────────────────────────────────────────────────────────
|
||||||
@@ -224,6 +224,14 @@ describe("MeshConfigGenerator", () => {
|
|||||||
// ── applyConfig ────────────────────────────────────────────────────────
|
// ── applyConfig ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
describe("applyConfig", () => {
|
describe("applyConfig", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
const config: MeshConnectionConfig = {
|
const config: MeshConnectionConfig = {
|
||||||
nodeApiKey: "test-key",
|
nodeApiKey: "test-key",
|
||||||
reachableUrl: "http://localhost:4041",
|
reachableUrl: "http://localhost:4041",
|
||||||
@@ -249,7 +257,9 @@ describe("MeshConfigGenerator", () => {
|
|||||||
containerId: "new-container-id",
|
containerId: "new-container-id",
|
||||||
});
|
});
|
||||||
|
|
||||||
await generator.applyConfig("dn_test123", config, { host: undefined });
|
const applyPromise = generator.applyConfig("dn_test123", config, { host: undefined });
|
||||||
|
await vi.advanceTimersByTimeAsync(2_500);
|
||||||
|
await applyPromise;
|
||||||
|
|
||||||
// Status set to "recreating" first
|
// Status set to "recreating" first
|
||||||
expect(mockCentral.updateManagedDockerNode).toHaveBeenCalledWith(
|
expect(mockCentral.updateManagedDockerNode).toHaveBeenCalledWith(
|
||||||
@@ -377,17 +387,19 @@ describe("MeshConfigGenerator", () => {
|
|||||||
|
|
||||||
// Use fake timers to speed up the timeout test
|
// Use fake timers to speed up the timeout test
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
const resultPromise = generator.registerInMesh("dn_test123", config);
|
try {
|
||||||
|
const resultPromise = generator.registerInMesh("dn_test123", config);
|
||||||
|
|
||||||
// Fast-forward through the polling
|
// Fast-forward through the polling
|
||||||
await vi.advanceTimersByTimeAsync(35_000);
|
await vi.advanceTimersByTimeAsync(35_000);
|
||||||
|
|
||||||
const result = await resultPromise;
|
const result = await resultPromise;
|
||||||
|
|
||||||
expect(result.isHealthy).toBe(false);
|
expect(result.isHealthy).toBe(false);
|
||||||
expect(result.error).toContain("did not reach online status");
|
expect(result.error).toContain("did not reach online status");
|
||||||
|
} finally {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("re-throws when registration fails", async () => {
|
it("re-throws when registration fails", async () => {
|
||||||
@@ -415,6 +427,14 @@ describe("MeshConfigGenerator", () => {
|
|||||||
// ── provisionAndRegister ──────────────────────────────────────────────
|
// ── provisionAndRegister ──────────────────────────────────────────────
|
||||||
|
|
||||||
describe("provisionAndRegister", () => {
|
describe("provisionAndRegister", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
it("runs full end-to-end flow: generate → apply → register", async () => {
|
it("runs full end-to-end flow: generate → apply → register", async () => {
|
||||||
const generator = createGenerator();
|
const generator = createGenerator();
|
||||||
const node = createManagedNode();
|
const node = createManagedNode();
|
||||||
@@ -429,7 +449,7 @@ describe("MeshConfigGenerator", () => {
|
|||||||
mockCentral.linkManagedDockerNodeToNode.mockResolvedValue(node);
|
mockCentral.linkManagedDockerNodeToNode.mockResolvedValue(node);
|
||||||
mockCentral.checkNodeHealth.mockResolvedValue("online");
|
mockCentral.checkNodeHealth.mockResolvedValue("online");
|
||||||
|
|
||||||
const result = await generator.provisionAndRegister({
|
const resultPromise = generator.provisionAndRegister({
|
||||||
managedNode: node,
|
managedNode: node,
|
||||||
orchestratorUrl: "http://orchestrator:4040",
|
orchestratorUrl: "http://orchestrator:4040",
|
||||||
orchestratorApiKey: "orch-key",
|
orchestratorApiKey: "orch-key",
|
||||||
@@ -437,6 +457,9 @@ describe("MeshConfigGenerator", () => {
|
|||||||
containerPort: 4041,
|
containerPort: 4041,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await vi.advanceTimersByTimeAsync(2_500);
|
||||||
|
const result = await resultPromise;
|
||||||
|
|
||||||
expect(result.isHealthy).toBe(true);
|
expect(result.isHealthy).toBe(true);
|
||||||
expect(result.config.nodeApiKey).toBe("my-key");
|
expect(result.config.nodeApiKey).toBe("my-key");
|
||||||
expect(result.config.envVars.FUSION_DAEMON_TOKEN).toBe("my-key");
|
expect(result.config.envVars.FUSION_DAEMON_TOKEN).toBe("my-key");
|
||||||
@@ -482,13 +505,15 @@ describe("MeshConfigGenerator", () => {
|
|||||||
mockDockerClient.recreateContainer.mockResolvedValue("new-container-id");
|
mockDockerClient.recreateContainer.mockResolvedValue("new-container-id");
|
||||||
mockCentral.registerNode.mockRejectedValue(new Error("Registration failed"));
|
mockCentral.registerNode.mockRejectedValue(new Error("Registration failed"));
|
||||||
|
|
||||||
await expect(
|
const resultPromise = generator.provisionAndRegister({
|
||||||
generator.provisionAndRegister({
|
managedNode: node,
|
||||||
managedNode: node,
|
orchestratorUrl: "http://orchestrator:4040",
|
||||||
orchestratorUrl: "http://orchestrator:4040",
|
orchestratorApiKey: "orch-key",
|
||||||
orchestratorApiKey: "orch-key",
|
});
|
||||||
}),
|
const assertion = expect(resultPromise).rejects.toThrow("Registration failed");
|
||||||
).rejects.toThrow("Registration failed");
|
|
||||||
|
await vi.advanceTimersByTimeAsync(2_500);
|
||||||
|
await assertion;
|
||||||
|
|
||||||
expect(mockCentral.updateManagedDockerNode).toHaveBeenCalledWith(
|
expect(mockCentral.updateManagedDockerNode).toHaveBeenCalledWith(
|
||||||
"dn_test123",
|
"dn_test123",
|
||||||
|
|||||||
Reference in New Issue
Block a user