feat: harden release signing and coverage gate
ForgeFlow quality gate / quality (push) Canceled after 0s

This commit is contained in:
NuklearRabbit
2026-07-29 22:54:51 +02:00
parent aa4895912a
commit 18f42621c2
13 changed files with 496 additions and 39 deletions
+88
View File
@@ -165,3 +165,91 @@ test("profile, review and operation lookups return safe empty values", async (t)
await store.deleteInventoryReviewDecision("missing", "workload");
await store.deleteDeploymentProfile("missing/repo", "profile");
});
test("session credentials preserve, replace and clear safely when OS encryption is unavailable", async (t) => {
const { store } = await storeFixture(t);
assert.deepEqual(store.setToken(" session-token "), { persistent: false, preserved: false });
assert.equal(store.getToken(), "session-token");
assert.deepEqual(store.setToken("", { preserveExisting: true }), { persistent: false, preserved: true });
assert.equal(store.getToken(), "session-token");
assert.deepEqual(store.setToken("replacement"), { persistent: false, preserved: false });
assert.equal(store.getToken(), "replacement");
assert.deepEqual(store.setToken(""), { persistent: true, preserved: false });
assert.equal(store.getToken(), "");
assert.throws(() => store.encryptSecret("password"), (error) => error.code === "SECURE_STORAGE_UNAVAILABLE");
assert.equal(store.encryptSecret(""), null);
assert.equal(store.decryptSecret(null), "");
assert.equal(store.decryptSecret("not-base64-encrypted-data"), "");
});
test("setup, Gitea updates and generic patches retain normalized public state", async (t) => {
const { store } = await storeFixture(t);
const completed = await store.completeSetup({
baseUrl: "https://gitea.test", token: "token", user: { login: "jens" },
workspaceRoots: [" C:/Projects ", "C:/Projects", ""]
});
assert.equal(completed.state.setupComplete, true);
assert.equal(completed.state.gitea.hasToken, true);
assert.deepEqual(completed.state.workspaceRoots, ["C:/Projects"]);
const update = await store.updateGitea({ baseUrl: "https://new.test", token: "", user: null });
assert.equal(update.preserved, true);
assert.equal(store.data.gitea.user.login, "jens");
const patched = await store.patch({ appearance: "light", workspaceRoots: ["D:/Code", "D:/Code"] });
assert.equal(patched.appearance, "light");
assert.deepEqual(patched.workspaceRoots, ["D:/Code"]);
assert.equal("encryptedToken" in patched.gitea, false);
});
test("server saves reject absent credentials before mutating configuration", async (t) => {
const { store } = await storeFixture(t);
await assert.rejects(
store.saveServer({ host: "unraid", username: "root", authType: "password", basePath: "/mnt/apps" }),
/password is required/i
);
await assert.rejects(
store.saveServer({ host: "unraid", username: "root", authType: "privateKey", basePath: "/mnt/apps", privateKeyPath: "" }),
/select a private key/i
);
assert.deepEqual(store.data.servers, []);
});
test("deployment profile normalization covers safe defaults and every optional Unraid control", async (t) => {
const { store } = await storeFixture(t);
const actions = store.normalizeDeploymentProfile({ environment: "qa", statusUrl: "https://app.test/status" });
assert.equal(actions.provider, "gitea-actions");
assert.equal(actions.name, "qa");
assert.equal(actions.branch, "main");
assert.equal(actions.workflowFile, "deploy.yml");
assert.equal(actions.rollbackWorkflowFile, "");
assert.equal(actions.confirmationRequired, true);
const unraid = store.normalizeDeploymentProfile({
id: "all-options", name: " Server ", environment: "production", provider: "ssh-unraid", branch: "release",
serverId: " server ", remoteFolder: "apps/App", deploymentMode: "monitor-only", generatedCompose: true,
composeFiles: [], composeServices: ["WEB", "Worker"], composeProject: "App.prod", composeWorkingDir: "/mnt/apps/App",
containerName: "Visible.App", cloneUrl: "https://gitea.test/Owner/App.git", alignRemote: true,
hostPort: -2, containerPort: 70000, webUiUrl: "http://[IP]:[PORT:3000]/", iconMode: "upload",
iconFilePath: "C:/icon.png", dockerShell: "/bin/bash", preservePaths: [], adoptedFromServer: true,
serverSourceOfTruth: true, manageDockerMan: true, forceRecreate: true, removeOrphans: true,
workloadIdentity: { workloadId: "one" }, serverGitAccess: { configured: true, deployKeyId: "invalid", keyFingerprint: "", hostFingerprint: "", configuredAt: "now" },
provenance: { remoteFolder: "server" }, detectedMetadata: { source: "docker" }, serverIconReference: " icon ",
deploymentPolicy: { frozen: true, freezeReason: " maintenance ", requireNote: true, maintenanceWindows: [{ days: [0, 0, 6, 7, "bad"], start: "01:00", end: "02:00" }] }
});
assert.equal(unraid.name, "Server");
assert.equal(unraid.serverId, "server");
assert.equal(unraid.composeFile, "docker-compose.yml");
assert.deepEqual(unraid.composeServices, ["web", "worker"]);
assert.equal(unraid.hostPort, 1);
assert.equal(unraid.containerPort, 65535);
assert.equal(unraid.iconMode, "upload");
assert.equal(unraid.dockerShell, "/bin/bash");
assert.equal(unraid.serverGitAccess.deployKeyId, null);
assert.equal(unraid.serverGitAccess.keyFingerprint, null);
assert.deepEqual(unraid.deploymentPolicy.maintenanceWindows[0].days, [0, 6]);
assert.equal(unraid.serverIconReference, "icon");
assert.throws(() => store.normalizeDeploymentProfile({ provider: "ssh-unraid", remoteFolder: "app", environment: "prod", composeService: "app", composeServices: ["bad service"] }), /Compose services/);
assert.throws(() => store.normalizeDeploymentProfile({ provider: "ssh-unraid", remoteFolder: "app", environment: "prod", composeProject: "bad project!" }), /Compose project/);
assert.throws(() => store.normalizeDeploymentProfile({ provider: "ssh-unraid", remoteFolder: "app", environment: "prod", composeWorkingDir: "relative" }), /working directory/);
assert.throws(() => store.normalizeDeploymentProfile({ provider: "ssh-unraid", remoteFolder: "app", environment: "prod", containerName: "bad name" }), /Container name/);
});
+112
View File
@@ -63,3 +63,115 @@ test('deployment preflight verifies exact Git, workflow, Actions and server prer
assert.equal(result.checks.filter((item) => item.status === 'fail').length, 0);
assert.equal(result.head, sha);
});
test('system preflight reports unavailable Git, storage, roots and rejected Gitea credentials', async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), 'forgeflow-preflight-failures-'));
t.after(() => rm(root, { recursive: true, force: true }));
const ordinaryFile = path.join(root, 'not-a-directory');
await writeFile(ordinaryFile, 'file');
const events = [];
const service = new PreflightService({
store: { data: { gitea: { baseUrl: 'https://stored.test' } }, getToken: () => 'stored-token' },
git: { isAvailable: async () => ({ available: false, error: 'git missing' }) },
gitea: { validateConnection: async () => { throw new Error('token rejected'); } },
deployments: {}, diagnostics: { logDirectory: path.join(root, 'logs'), info: async (...args) => events.push(args) },
userDataPath: path.join(root, 'data'), secureStorageAvailable: () => false
});
service.writableDirectory = async (directory) => {
if (directory.endsWith('data')) throw new Error('read only');
return true;
};
const result = await service.runSystem({ roots: [ordinaryFile, path.join(root, 'missing'), ordinaryFile, ''] });
assert.equal(result.checks.find((item) => item.id === 'git.available').status, 'fail');
assert.equal(result.checks.find((item) => item.id === 'storage.userdata').status, 'fail');
assert.equal(result.checks.find((item) => item.id === 'storage.diagnostics').status, 'pass');
assert.equal(result.checks.find((item) => item.id === 'storage.credentials').status, 'warning');
assert.equal(result.checks.find((item) => item.id === 'workspace.root.0').status, 'fail');
assert.equal(result.checks.find((item) => item.id === 'workspace.root.1').status, 'fail');
assert.equal(result.checks.find((item) => item.id === 'gitea.connection').status, 'fail');
assert.equal(result.summary.ready, false);
assert.equal(events[0][0], 'preflight.system.completed');
});
test('system preflight warns on incomplete Git identity and accepts unknown Gitea version', async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), 'forgeflow-preflight-identity-'));
t.after(() => rm(root, { recursive: true, force: true }));
const service = new PreflightService({
store: { data: { gitea: { baseUrl: '' } }, getToken: () => '' },
git: { isAvailable: async () => ({ available: true, version: 'git' }) },
gitea: { validateConnection: async () => ({ version: null, user: null, repositoryCount: 0 }) }, deployments: {},
diagnostics: { logDirectory: path.join(root, 'logs'), info: async () => {} }, userDataPath: path.join(root, 'data')
});
service.gitIdentity = async () => ({ name: '', email: '' });
const result = await service.runSystem({ baseUrl: 'https://gitea.test', token: 'token', roots: [] });
assert.equal(result.checks.find((item) => item.id === 'git.identity').status, 'warning');
assert.match(result.checks.find((item) => item.id === 'gitea.connection').detail, /unknown version.*user/i);
assert.equal(result.checks.find((item) => item.id === 'gitea.repositories').status, 'pass');
assert.equal(result.checks.find((item) => item.id === 'workspace.roots').status, 'warning');
service.gitIdentity = async () => { throw new Error('identity lookup failed'); };
const second = await service.runSystem();
assert.match(second.checks.find((item) => item.id === 'git.identity').detail, /lookup failed/i);
});
test('deployment preflight fails fast for invalid identity, profile and missing local link', async () => {
const diagnostics = [];
const service = new PreflightService({
store: { getDeploymentProfile: (_name, id) => id === 'known' ? { id: 'known', name: 'Production' } : null },
git: {}, gitea: {}, deployments: {}, diagnostics: { info: async (...args) => diagnostics.push(args) }, userDataPath: ''
});
await assert.rejects(service.runDeployment({ repository: null, profileId: 'known' }), /identity is required/i);
await assert.rejects(service.runDeployment({ repository: { fullName: 'owner/app' }, profileId: 'missing' }), /profile not found/i);
const result = await service.runDeployment({ repository: { fullName: 'owner/app', localPath: '' }, profileId: 'known' });
assert.deepEqual(result.summary.blocking, ['repository.linked']);
assert.equal(diagnostics[0][0], 'preflight.deployment.completed');
});
test('deployment preflight preserves actionable evidence across Git, workflow and endpoint failures', async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), 'forgeflow-preflight-degraded-'));
t.after(() => rm(root, { recursive: true, force: true }));
const profile = { id: 'production', name: 'Production', environment: 'production', branch: 'main', workflowFile: 'deploy.yml', rollbackWorkflowFile: 'rollback.yml' };
const service = new PreflightService({
store: { getDeploymentProfile: () => profile },
git: {
status: async () => ({ root, head: 'b'.repeat(40), clean: false, counts: { changed: 4 }, branch: { head: '', upstream: '', ahead: 2, behind: 3 } }),
verifyCommitOnRemoteBranch: async () => { throw new Error('commit not published'); }
},
gitea: { repositoryFileExists: async () => false, listWorkflowRuns: async () => { throw new Error('Actions disabled'); } },
deployments: {}, diagnostics: { info: async () => {} }, userDataPath: root
});
const result = await service.runDeployment({ repository: { fullName: 'owner/app', localPath: root }, profileId: profile.id });
for (const id of ['git.branch', 'git.clean', 'git.upstream', 'git.sync', 'git.remote-sha', 'workflow.deploy.local', 'workflow.deploy.remote', 'gitea.actions', 'server.status.configured']) {
assert.equal(result.checks.find((item) => item.id === id).status, 'fail', id);
}
assert.equal(result.checks.find((item) => item.id === 'workflow.rollback.local').status, 'warning');
assert.equal(result.checks.find((item) => item.id === 'server.health').status, 'warning');
assert.equal(result.head, 'b'.repeat(40));
});
test('deployment preflight distinguishes unreachable and mismatched status evidence', async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), 'forgeflow-preflight-status-'));
t.after(() => rm(root, { recursive: true, force: true }));
await mkdir(path.join(root, '.gitea', 'workflows'), { recursive: true });
await writeFile(path.join(root, '.gitea', 'workflows', 'deploy.yml'), 'name: deploy\n');
const profile = { id: 'production', name: 'Production', environment: 'production', branch: 'main', workflowFile: 'deploy.yml', statusUrl: 'https://app/status', healthcheckUrl: 'https://app/health' };
let status = { reachable: false, ok: false, status: 503, error: '' };
const service = new PreflightService({
store: { getDeploymentProfile: () => profile },
git: { status: async () => { throw new Error('checkout corrupt'); } },
gitea: { repositoryFileExists: async () => { throw new Error('Gitea offline'); } },
deployments: { readStatusEndpoint: async () => status, checkHealth: async () => ({ healthy: false, status: 500, error: '' }) },
diagnostics: { info: async () => {} }, userDataPath: root
});
const unreachable = await service.runDeployment({ repository: { fullName: 'owner/app', localPath: root }, profileId: profile.id });
assert.match(unreachable.checks.find((item) => item.id === 'server.status.reachable').detail, /HTTP 503/i);
assert.match(unreachable.checks.find((item) => item.id === 'server.health').detail, /HTTP 500/i);
assert.match(unreachable.checks.find((item) => item.id === 'git.repository').detail, /checkout corrupt/i);
status = { reachable: true, ok: true, repository: 'other/app', environment: 'staging', liveSha: null };
const mismatch = await service.runDeployment({ repository: { fullName: 'owner/app', localPath: root }, profileId: profile.id });
const identity = mismatch.checks.find((item) => item.id === 'server.status.identity');
assert.equal(identity.status, 'fail');
assert.equal(identity.required, true);
assert.match(mismatch.checks.find((item) => item.id === 'server.status.reachable').detail, /no live SHA/i);
});
+144
View File
@@ -0,0 +1,144 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const {
parseServerInventory, buildWorkloadInventory, inventoryContainerMatch, remoteIdentity,
stableWorkloadId, profileMatchesWorkload, sanitizeLegacyContainer, safeRelativeToBase,
canonicalServerAppdataPath, deploymentRootCandidate,
} = require('../src/main/server-inventory.cjs');
const b64 = (value) => Buffer.from(String(value)).toString('base64');
test('inventory parser handles every evidence record and ignores malformed payloads', () => {
const legacy = { Id: 'legacy', Name: '/App', Config: { Image: 'app:1', Labels: { 'com.docker.compose.project': 'app' } }, State: { Running: true, Status: 'running', Health: { Status: 'healthy' } }, Mounts: null };
const safe = { id: 'safe', name: '/Safe', running: false, labels: null, mounts: null, ports: null, networks: null };
const projects = [{ Name: 'app', Status: 'running(1)', ConfigFiles: '/mnt/user/appdata/App/compose.yml,/mnt/user/appdata/App/extra.yml' }, { name: '', configFiles: [] }];
const output = [
'noise', '__FORGEFLOW_INVENTORY__',
`H\ttrue\tfalse\ttrue\ttrue\tfalse\ttrue\t${b64('Compose v2')}\t${b64('Linux')}`,
`R\t${b64('/mnt/user/appdata/App')}\t${b64('git@gitea.test:Owner/App.git')}\t${'a'.repeat(40)}\t${b64('main')}`,
`C\t${b64(JSON.stringify([legacy, null]))}`,
`C\t${b64(JSON.stringify(safe))}`,
`C\t${b64('{bad json')}`,
`D\t${b64('App')}\t${b64('/templates/App.xml')}\t${b64('http://app')}\t${b64('/icon.png')}\t${b64('/bin/bash')}\t${b64('app:1')}\t${b64('bridge')}`,
`P\t${b64(JSON.stringify(projects))}`,
`P\t${b64(JSON.stringify({ name: 'single', status: 'exited', config_files: ['single.yml'] }))}`,
`Y\t${b64('/mnt/user/appdata/App/')}\t${b64('compose.yml\ncompose.prod.yml\n')}\t${b64('app')}\t${b64('web\nworker')}\t${b64('app:1')}\ttrue\t${b64('')}`,
`W\t${b64('partial docker inspect failure')}`,
'UNKNOWN\tignored',
].join('\n');
const parsed = parseServerInventory(output);
assert.deepEqual(parsed.capabilities, { docker: true, compose: false, git: true, tar: true, checksum: false, baseWritable: true, composeVersion: 'Compose v2', platform: 'Linux' });
assert.equal(parsed.checkouts.length, 1);
assert.equal(parsed.containers.length, 2);
assert.equal(parsed.containers[0].health, 'healthy');
assert.deepEqual(parsed.containers[1].labels, {});
assert.equal(parsed.dockerMan[0].templatePath, '/templates/App.xml');
assert.equal(parsed.composeProjects.length, 2);
assert.deepEqual(parsed.composeProjects[0].configFiles, ['/mnt/user/appdata/App/compose.yml', '/mnt/user/appdata/App/extra.yml']);
assert.deepEqual(parsed.composeDefinitions[0].services, ['web', 'worker']);
assert.deepEqual(parsed.warnings, ['partial docker inspect failure']);
assert.throws(() => parseServerInventory('ordinary output'), /did not return/i);
});
test('server path normalization keeps deployments inside canonical appdata', () => {
assert.equal(safeRelativeToBase('/mnt/user/appdata/', '/mnt/user/appdata/App/'), 'App');
for (const value of ['', '/mnt/user/appdata', '/mnt/user/appdata/../etc', '/other/App']) assert.equal(safeRelativeToBase('/mnt/user/appdata', value), '');
assert.equal(canonicalServerAppdataPath('/mnt/user/appdata', '/mnt/cache/appdata/App'), '/mnt/user/appdata/App');
assert.equal(canonicalServerAppdataPath('/mnt/user/appdata', '/mnt/disk2/appdata/App/data'), '/mnt/user/appdata/App/data');
assert.equal(canonicalServerAppdataPath('', '/mnt/user/appdata/App'), '/mnt/user/appdata/App');
assert.equal(canonicalServerAppdataPath('/custom', '/outside/path'), '/outside/path');
assert.equal(canonicalServerAppdataPath('/custom', ''), '');
assert.equal(deploymentRootCandidate('App/source-pre-abcdef1/source'), 'App');
assert.equal(deploymentRootCandidate('App/.forgeflow/incoming'), 'App');
});
test('profile matching requires the same server and accepts each stable identity form', () => {
const workload = { serverId: 'server', workloadId: 'workload', selector: { kind: 'compose', composeProject: 'app' }, compose: { project: 'app', workingDir: '/apps/App' }, remoteFolderCandidate: 'App', containers: [{ name: 'app-web' }] };
assert.equal(profileMatchesWorkload(null, workload), false);
assert.equal(profileMatchesWorkload({ provider: 'gitea-actions', serverId: 'server' }, workload), false);
assert.equal(profileMatchesWorkload({ provider: 'ssh-unraid', serverId: 'other' }, workload), false);
assert.equal(profileMatchesWorkload({ provider: 'ssh-unraid', serverId: 'server', workloadIdentity: { workloadId: 'workload' } }, workload), true);
assert.equal(profileMatchesWorkload({ provider: 'ssh-unraid', serverId: 'server', workloadIdentity: { selector: workload.selector } }, workload), true);
assert.equal(profileMatchesWorkload({ provider: 'ssh-unraid', serverId: 'server', composeProject: 'app' }, workload), true);
assert.equal(profileMatchesWorkload({ provider: 'ssh-unraid', serverId: 'server', composeProject: 'app', composeWorkingDir: '/other' }, workload), false);
assert.equal(profileMatchesWorkload({ provider: 'ssh-unraid', serverId: 'server', remoteFolder: 'App' }, workload), true);
assert.equal(profileMatchesWorkload({ provider: 'ssh-unraid', serverId: 'server', containerName: 'app-web' }, workload), true);
assert.equal(stableWorkloadId('server', workload.selector), stableWorkloadId('server', workload.selector));
});
test('container matching prioritizes working directory, mounts, provenance and stable names', () => {
const checkout = { root: '/apps/App', remote: 'git@gitea.test:Owner/App.git' };
const repository = { name: 'App' };
const base = { running: true, labels: {}, mounts: [], name: 'different' };
assert.equal(inventoryContainerMatch(checkout, repository, { ...base, labels: { 'com.docker.compose.project.working_dir': '/apps/App/' } }), 100);
assert.equal(inventoryContainerMatch(checkout, repository, { ...base, mounts: [{ Source: '/apps/App/data' }] }), 90);
assert.equal(inventoryContainerMatch(checkout, repository, { ...base, labels: { 'org.opencontainers.image.source': 'https://gitea.test/Owner/App' } }), 85);
assert.equal(inventoryContainerMatch(checkout, repository, { ...base, labels: { 'com.docker.compose.project': 'app' } }), 70);
assert.equal(inventoryContainerMatch(checkout, repository, { ...base, name: '/APP' }), 60);
assert.equal(inventoryContainerMatch(checkout, repository, { ...base, running: false }), 0);
assert.equal(inventoryContainerMatch(checkout, repository, base), 0);
assert.equal(remoteIdentity(''), '');
});
test('workload builder merges runtime, Compose file and DockerMan evidence without backups', () => {
const labels = {
'com.docker.compose.project': 'app',
'com.docker.compose.project.working_dir': '/mnt/cache/appdata/App',
'com.docker.compose.project.config_files': '/mnt/cache/appdata/App/compose.yml',
'com.docker.compose.service': 'web',
'tech.itworx.forgeflow.repository': 'git@gitea.test:Owner/App.git',
'tech.itworx.forgeflow.commit': 'b'.repeat(40),
'tech.itworx.forgeflow.branch': 'main',
};
const inventory = {
containers: [
{ id: 'web', name: 'app-web', image: 'registry/app:1', imageId: 'image', running: true, status: 'running', health: 'unhealthy', labels, ports: { '8080/tcp': null, '3000/udp': [{ HostIp: '0.0.0.0', HostPort: '3000' }] }, mounts: [{ Type: 'bind', Source: '/mnt/disk1/appdata/App/data', Destination: '/data', RW: false }], networks: { frontend: {} }, restartPolicy: 'always' },
{ id: 'worker', name: 'app-worker', image: 'registry/worker:1', imageId: 'worker', running: false, status: 'exited', health: null, labels: { ...labels, 'com.docker.compose.service': 'worker' }, ports: {}, mounts: [], networks: {}, restartPolicy: '' },
],
checkouts: [{ root: '/mnt/user/appdata/App', remote: 'git@gitea.test:Owner/App.git', liveSha: 'c'.repeat(40), branch: 'release' }],
composeProjects: [{ name: 'app', status: 'running', configFiles: [] }, { name: 'headless', status: 'exited', configFiles: ['/mnt/user/appdata/Headless/compose.yml'] }],
composeDefinitions: [
{ workingDir: '/mnt/user/appdata/App', configFiles: ['/mnt/user/appdata/App/compose.yml'], projectName: 'app', services: ['web', 'worker'], images: ['registry/app:1'], valid: true, error: '' },
{ workingDir: '/mnt/user/appdata/Backup/.forgeflow/releases/one', configFiles: ['compose.yml'], projectName: 'backup', services: [], images: [], valid: true },
{ workingDir: '/mnt/user/appdata/Standalone', configFiles: ['/mnt/user/appdata/Standalone/compose.yml'], projectName: '', services: ['api'], images: ['standalone:1'], valid: false, error: 'invalid compose' },
],
dockerMan: [
{ name: 'app-web', templatePath: '/templates/app.xml', webUiUrl: 'http://app', iconUrl: '/app.png', shell: '/bin/bash', repository: 'registry/app:1', network: 'frontend' },
{ name: 'template-only', templatePath: '/templates/template.xml', webUiUrl: '', iconUrl: '', shell: '', repository: 'template:1', network: 'bridge' },
], warnings: [], capabilities: {},
};
const repository = { fullName: 'Owner/App', name: 'App', cloneUrl: 'https://gitea.test/Owner/App.git' };
const workloads = buildWorkloadInventory({ inventory, server: { id: 'server', name: 'Unraid', basePath: '/mnt/user/appdata' }, repositories: [repository], profiles: [{ id: 'profile', provider: 'ssh-unraid', serverId: 'server', composeProject: 'app', repositoryFullName: 'Owner/App', adoptedFromServer: true }] });
assert.equal(workloads.some((workload) => workload.displayName === 'backup'), false);
const app = workloads.find((workload) => workload.displayName === 'app');
assert.equal(app.status, 'linked');
assert.equal(app.runtime.running, true);
assert.equal(app.runtime.allRunning, false);
assert.equal(app.runtime.health, 'unhealthy');
assert.equal(app.runtime.ports.length, 2);
assert.equal(app.containers[0].mounts[0].readOnly, true);
assert.equal(app.remoteFolderCandidate, 'App');
assert.equal(app.candidates[0].exact, true);
assert.equal(app.link.source, 'automatic');
const standalone = workloads.find((workload) => workload.displayName === 'Standalone');
assert.equal(standalone.metadata.composeDefinitionValid, false);
assert.equal(standalone.metadata.composeDefinitionError, 'invalid compose');
const template = workloads.find((workload) => workload.displayName === 'template-only');
assert.equal(template.kind, 'dockerman-container');
assert.equal(template.runtime.running, false);
assert.equal(template.metadata.shell, '/bin/sh');
});
test('legacy container sanitizer applies safe defaults to partial Docker inspect data', () => {
assert.deepEqual(sanitizeLegacyContainer(null), {
id: '', name: '', image: '', imageId: '', running: false, status: '', health: null,
labels: {
'com.docker.compose.project': '', 'com.docker.compose.project.working_dir': '', 'com.docker.compose.project.config_files': '', 'com.docker.compose.service': '',
'org.opencontainers.image.source': '', 'org.opencontainers.image.revision': '', 'tech.itworx.forgeflow.repository': '', 'tech.itworx.forgeflow.commit': '',
'tech.itworx.forgeflow.branch': '', 'net.unraid.docker.webui': '', 'net.unraid.docker.icon': '', 'net.unraid.docker.shell': '', 'net.unraid.docker.managed': '',
}, ports: {}, mounts: [], networks: {}, restartPolicy: '',
});
});
+16
View File
@@ -528,6 +528,22 @@ test("Windows release pipeline fails closed on signatures and emits provenance p
assert.match(publisher, /sbom\.cdx\.json/);
});
test("production signing build supports classic and Azure identities but always fails closed", async () => {
const [pkg, validator, signedConfig] = await Promise.all([
readFile(new URL("../package.json", import.meta.url), "utf8"),
readFile(new URL("../scripts/validate-signing-environment.mjs", import.meta.url), "utf8"),
readFile(new URL("../scripts/signed-electron-builder-config.cjs", import.meta.url), "utf8"),
]);
assert.match(pkg, /dist:win:signed/);
assert.match(validator, /FORGEFLOW_SIGNED_RELEASE/);
assert.match(validator, /WIN_CSC_LINK/);
assert.match(validator, /FORGEFLOW_AZURE_CERTIFICATE_PROFILE/);
assert.match(validator, /exact certificate subject/);
assert.match(signedConfig, /forceCodeSigning:\s*true/);
assert.match(signedConfig, /azureSignOptions/);
assert.match(signedConfig, /timestamp\.acs\.microsoft\.com/);
});
test("binary update helper verifies, waits, applies and records restart state", async () => {
const helper = await readFile(
new URL("../scripts/apply-binary-update.ps1", import.meta.url),