fix: harden deployment discovery and preflight
ForgeFlow quality gate / quality (push) Canceled after 0s

This commit is contained in:
NuklearRabbit
2026-08-08 23:59:27 +02:00
parent f866b12fbf
commit 4c21616e72
18 changed files with 166 additions and 35 deletions
+6 -2
View File
@@ -115,8 +115,12 @@ test("repository changes, Git tools and Git Validator complete their primary flo
test("every long application surface retains a working vertical scroll owner", async ({ page }) => {
for (const view of ["overview", "deployments", "diagnostics", "settings"]) {
await page.locator(`.nav-button[data-view="${view}"]`).click();
await assertScrollableWhenOverflowing(page, ".main-canvas");
await test.step(`${view} view scrolls`, async () => {
const navigation = page.locator(`.nav-button[data-view="${view}"]`);
await navigation.click();
await expect(navigation).toHaveClass(/active/);
await assertScrollableWhenOverflowing(page, ".main-canvas");
});
}
await page.locator('[data-action="select-repo"]').first().click();
for (const tab of ["history", "deployments", "gittools", "validator", "settings"]) {
+7
View File
@@ -10,6 +10,13 @@ async function ipcSource() {
return (await Promise.all(["ipc.cjs", "ipc/repository-handlers.cjs", "ipc/deployment-handlers.cjs", "ipc/operations-handlers.cjs"].map((file) => readFile(new URL(`../src/main/${file}`, import.meta.url), "utf8")))).join("\n");
}
test("desktop shell serializes ForgeFlow to one configuration writer", async () => {
const main = await readFile(new URL("../main.cjs", import.meta.url), "utf8");
assert.match(main, /requestSingleInstanceLock\(\)/);
assert.match(main, /second-instance/);
assert.match(main, /showMainWindow\(\)/);
});
test("changed file list has an independently scrollable bounded layout", async () => {
const css = await readFile(
new URL("../src/renderer/styles.css", import.meta.url),
+6
View File
@@ -141,4 +141,10 @@ test('legacy container sanitizer applies safe defaults to partial Docker inspect
'tech.itworx.forgeflow.branch': '', 'net.unraid.docker.webui': '', 'net.unraid.docker.icon': '', 'net.unraid.docker.shell': '', 'net.unraid.docker.managed': '',
}, ports: {}, mounts: [], networks: {}, restartPolicy: '',
});
assert.equal(sanitizeLegacyContainer({
Id: 'no-healthcheck',
Name: '/NoHealthcheck',
State: { Running: true, Status: 'running' },
Config: { Image: 'example/no-healthcheck:latest', Labels: null },
}).health, null);
});
+35 -1
View File
@@ -112,6 +112,33 @@ test("server pull verification proves a repository-scoped read-only key and exac
assert.equal(report.checks.find((check) => check.id === "deploy-key-scope").status, "pass");
});
test("server pull remains deploy-ready when only live runtime evidence is incomplete", async () => {
const sha = "c".repeat(40);
const profile = {
id: "profile-runtime-incomplete", provider: "ssh-unraid", serverId: "unraid", remoteFolder: "portfolio",
environment: "production", branch: "main", deploymentMode: "server-git", composeFiles: ["compose.yml"],
serverGitAccess: { deployKeyId: 17, keyFingerprint: "SHA256:key", hostFingerprint: "SHA256:host" },
};
const service = new UnraidDeploymentService({
store: {
getDeploymentProfile: () => profile,
getServer: () => ({ id: "unraid", name: "Unraid", basePath: "/mnt/user/appdata" }),
getDeploymentState: () => ({ containerRunning: true, healthy: null }),
},
ssh: { exec: async () => ({ stdout: `__FORGEFLOW_SERVER_GIT_PROBE__\nremoteSha=${sha}\nkeyFingerprint=SHA256:key\nhostFingerprint=SHA256:host\n` }) },
gitea: {
getBranch: async () => ({ commit: { id: sha } }),
listDeployKeys: async () => [{ id: 17, read_only: true }],
},
});
service.inspect = async () => ({ exists: true, composeFiles: ["compose.yml"], head: null });
const report = await service.verifyServerGitProfile({ repository: { fullName: "Jens/Portfolio", sshUrl: "git@gitea.test:Jens/Portfolio.git" }, profileId: profile.id });
assert.equal(report.deployReady, true);
assert.equal(report.ready, true);
assert.equal(report.readiness, "Deploy-ready; runtime verification incomplete");
assert.deepEqual(report.deploymentBlockers, []);
});
test("server pull verification blocks a writable Gitea deploy key", async () => {
const sha = "d".repeat(40);
const profile = {
@@ -1071,6 +1098,8 @@ test("inventory scan uses only configured roots and reports partial find failure
assert.match(script, /-name 'scratch'/);
assert.match(script, /Inventory scan partially failed/);
assert.match(script, /2>"\$scan_error" \|\| true/);
assert.match(script, /docker inspect "\$container_id"/);
assert.doesNotMatch(script, /docker inspect --format/);
assert.doesNotMatch(script, /add_scan_root \/mnt\/cache\/appdata/);
});
@@ -1294,7 +1323,10 @@ test("server-pull preflight resolves Gitea SHA and reports every degraded capabi
},
git: { status: async () => ({ root: "/local", clean: false, counts: { changed: 3 }, branch: { head: "main" } }) },
ssh: { test: async () => ({ capabilities: { docker: true, dockerReady: false, compose: false, git: false, tar: true, checksum: false, baseWritable: false } }) },
gitea: { getBranch: async () => ({ commit: { sha } }) },
gitea: {
getBranch: async () => ({ commit: { sha } }),
repositoryFileExists: async ({ filePath, ref }) => ref === sha && filePath === "compose.yml",
},
sourcePath: process.cwd()
});
service.probeServerGitAccess = async () => ({ ready: false, error: "deploy key missing", remoteSha: null });
@@ -1308,6 +1340,8 @@ test("server-pull preflight resolves Gitea SHA and reports every degraded capabi
assert.equal(result.sha, sha);
assert.equal(byId("local-branch").status, "warning");
assert.equal(byId("local-clean").status, "warning");
assert.equal(byId("gitea-deployment-files").status, "fail");
assert.match(byId("gitea-deployment-files").detail, /compose\.prod\.yml/);
assert.equal(byId("docker-runtime").status, "fail");
assert.match(byId("docker-runtime").detail, /cannot query/i);
assert.equal(byId("compose-command").status, "fail");