572 lines
20 KiB
TypeScript
572 lines
20 KiB
TypeScript
import { spawnSync } from "node:child_process";
|
|
import { chmodSync, existsSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { describe, expect, test } from "vitest";
|
|
|
|
const root = existsSync(join(process.cwd(), "turbo.json"))
|
|
? process.cwd()
|
|
: join(process.cwd(), "..", "..");
|
|
|
|
describe("workspace boundaries", () => {
|
|
test("declares the root as a turbo-managed bun workspace", () => {
|
|
const packageJson = JSON.parse(
|
|
readFileSync(join(root, "package.json"), "utf8"),
|
|
) as {
|
|
private?: boolean;
|
|
scripts?: Record<string, string>;
|
|
workspaces?: string[];
|
|
};
|
|
|
|
expect(packageJson.private).toBe(true);
|
|
expect(packageJson.workspaces).toEqual(["apps/*", "packages/*"]);
|
|
expect(packageJson.scripts?.build).toBe("turbo run build");
|
|
expect(existsSync(join(root, "turbo.json"))).toBe(true);
|
|
});
|
|
|
|
test("keeps release orchestration in the root turbo task graph", () => {
|
|
const rootPackage = JSON.parse(
|
|
readFileSync(join(root, "package.json"), "utf8"),
|
|
) as {
|
|
scripts?: Record<string, string>;
|
|
};
|
|
const webPackage = JSON.parse(
|
|
readFileSync(join(root, "apps/web/package.json"), "utf8"),
|
|
) as { scripts?: Record<string, string> };
|
|
const turboConfig = JSON.parse(
|
|
readFileSync(join(root, "turbo.json"), "utf8"),
|
|
) as {
|
|
globalDependencies?: string[];
|
|
tasks?: Record<string, { env?: string[] }>;
|
|
};
|
|
|
|
expect(rootPackage.scripts?.["test:qa"]).toBe(
|
|
"turbo run check test:unit build @dimensionlab/ui#build-storybook @dimensionlab/web#test:e2e",
|
|
);
|
|
expect(webPackage.scripts).not.toHaveProperty("test:qa");
|
|
expect(webPackage.scripts?.test).toBe("bun --bun vitest run");
|
|
expect(webPackage.scripts?.["test:unit"]).toBe("bun --bun vitest run");
|
|
expect(turboConfig.tasks).not.toHaveProperty("test:qa");
|
|
expect(turboConfig.globalDependencies).toEqual(
|
|
expect.arrayContaining(["bun.lock", "tsconfig.base.json"]),
|
|
);
|
|
expect(turboConfig.tasks?.["test:e2e"]?.env).toEqual(
|
|
expect.arrayContaining([
|
|
"CI",
|
|
"PLAYWRIGHT_DATABASE_URL",
|
|
"PLAYWRIGHT_PORT",
|
|
"PLAYWRIGHT_STORYBOOK_PORT",
|
|
]),
|
|
);
|
|
});
|
|
|
|
test("lets turbo build app and Storybook artifacts before e2e serves them", () => {
|
|
const playwrightConfig = readFileSync(
|
|
join(root, "apps/web/playwright.config.ts"),
|
|
"utf8",
|
|
);
|
|
const turboConfig = JSON.parse(
|
|
readFileSync(join(root, "turbo.json"), "utf8"),
|
|
) as {
|
|
tasks?: Record<string, { dependsOn?: string[] }>;
|
|
};
|
|
|
|
expect(playwrightConfig).not.toContain("bun run build &&");
|
|
expect(playwrightConfig).not.toContain("bun run build-storybook");
|
|
expect(turboConfig.tasks?.["test:e2e"]?.dependsOn).toEqual(
|
|
expect.arrayContaining(["@dimensionlab/ui#build-storybook"]),
|
|
);
|
|
});
|
|
|
|
test("accounts for local env files in cacheable Vite and Storybook task hashes", () => {
|
|
const turboConfig = JSON.parse(
|
|
readFileSync(join(root, "turbo.json"), "utf8"),
|
|
) as {
|
|
tasks?: Record<string, { inputs?: string[] }>;
|
|
};
|
|
|
|
for (const taskName of ["build", "build-storybook"]) {
|
|
expect(turboConfig.tasks?.[taskName]?.inputs).toEqual([
|
|
"$TURBO_DEFAULT$",
|
|
".env*",
|
|
]);
|
|
}
|
|
});
|
|
|
|
test("keeps the website app and reusable UI library as separate packages", () => {
|
|
const webPackage = JSON.parse(
|
|
readFileSync(join(root, "apps/web/package.json"), "utf8"),
|
|
) as { dependencies?: Record<string, string>; name?: string };
|
|
const uiPackage = JSON.parse(
|
|
readFileSync(join(root, "packages/ui/package.json"), "utf8"),
|
|
) as { exports?: Record<string, unknown>; name?: string };
|
|
|
|
expect(webPackage.name).toBe("@dimensionlab/web");
|
|
expect(webPackage.dependencies?.["@dimensionlab/ui"]).toBe("workspace:*");
|
|
expect(uiPackage.name).toBe("@dimensionlab/ui");
|
|
expect(uiPackage.exports).toHaveProperty(".");
|
|
expect(uiPackage.exports).toHaveProperty("./styles.css");
|
|
});
|
|
|
|
test("keeps the dashboard model in a reusable internal package", () => {
|
|
const webPackage = JSON.parse(
|
|
readFileSync(join(root, "apps/web/package.json"), "utf8"),
|
|
) as { dependencies?: Record<string, string> };
|
|
const modelPackage = JSON.parse(
|
|
readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"),
|
|
) as { exports?: Record<string, unknown>; name?: string };
|
|
const tsconfig = JSON.parse(
|
|
readFileSync(join(root, "tsconfig.json"), "utf8"),
|
|
) as { references?: Array<{ path: string }> };
|
|
|
|
expect(webPackage.dependencies?.["@dimensionlab/dashboard-model"]).toBe(
|
|
"workspace:*",
|
|
);
|
|
expect(modelPackage.name).toBe("@dimensionlab/dashboard-model");
|
|
expect(modelPackage.exports).toHaveProperty(".");
|
|
expect(modelPackage.exports).toHaveProperty("./fixtures");
|
|
expect(tsconfig.references).toEqual(
|
|
expect.arrayContaining([{ path: "./packages/dashboard-model" }]),
|
|
);
|
|
expect(existsSync(join(root, "apps/web/src/lib/model/index.ts"))).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
test("consumes workspace packages through package exports instead of source aliases", () => {
|
|
const webTsconfig = JSON.parse(
|
|
readFileSync(join(root, "apps/web/tsconfig.json"), "utf8"),
|
|
) as { compilerOptions?: { paths?: Record<string, string[]> } };
|
|
const modelPackage = JSON.parse(
|
|
readFileSync(join(root, "packages/dashboard-model/package.json"), "utf8"),
|
|
) as { exports?: Record<string, WorkspacePackageExport> };
|
|
const uiPackage = JSON.parse(
|
|
readFileSync(join(root, "packages/ui/package.json"), "utf8"),
|
|
) as { exports?: Record<string, WorkspacePackageExport> };
|
|
const viteConfig = readFileSync(join(root, "apps/web/vite.config.ts"), "utf8");
|
|
const turboConfig = JSON.parse(
|
|
readFileSync(join(root, "turbo.json"), "utf8"),
|
|
) as { tasks?: Record<string, { dependsOn?: string[] }> };
|
|
|
|
expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty(
|
|
"@dimensionlab/dashboard-model",
|
|
);
|
|
expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty(
|
|
"@dimensionlab/dashboard-model/fixtures",
|
|
);
|
|
expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty("@dimensionlab/ui");
|
|
expect(webTsconfig.compilerOptions?.paths).not.toHaveProperty(
|
|
"@dimensionlab/ui/styles.css",
|
|
);
|
|
expect(webTsconfig.compilerOptions?.paths).toEqual({
|
|
"$lib/*": ["src/lib/*"],
|
|
});
|
|
expect(viteConfig).not.toContain("../../packages/dashboard-model/src");
|
|
expect(viteConfig).not.toContain("../../packages/ui/src");
|
|
expect(turboConfig.tasks?.dev?.dependsOn).toEqual(["^build"]);
|
|
expectPackageExport(modelPackage.exports?.["."], {
|
|
types: "./dist/index.d.ts",
|
|
development: "./src/index.ts",
|
|
default: "./dist/index.js",
|
|
});
|
|
expectPackageExport(modelPackage.exports?.["./fixtures"], {
|
|
types: "./dist/fixtures/index.d.ts",
|
|
development: "./src/fixtures/index.ts",
|
|
default: "./dist/fixtures/index.js",
|
|
});
|
|
expectPackageExport(uiPackage.exports?.["."], {
|
|
types: "./dist/index.d.ts",
|
|
development: "./src/index.ts",
|
|
default: "./dist/index.js",
|
|
});
|
|
expectPackageExport(uiPackage.exports?.["./styles.css"], {
|
|
development: "./src/styles.css",
|
|
default: "./dist/styles.css",
|
|
});
|
|
expectPackageExport(uiPackage.exports?.["./tokens.css"], {
|
|
development: "./src/tokens.css",
|
|
default: "./dist/tokens.css",
|
|
});
|
|
});
|
|
|
|
test("builds the internal container from a turbo-pruned web workspace", () => {
|
|
const containerfile = readFileSync(
|
|
join(root, "apps/web/Containerfile"),
|
|
"utf8",
|
|
);
|
|
const pruneCommand = "turbo prune @dimensionlab/web --docker";
|
|
const jsonCopy = "COPY --from=pruner /repo/out/json/ ./";
|
|
const sourceCopy = "COPY --from=pruner /repo/out/full/ ./";
|
|
const tsconfigCopy =
|
|
"COPY --from=pruner /repo/tsconfig.base.json /repo/tsconfig.json ./";
|
|
const installCommand = "RUN bun install --frozen-lockfile --ignore-scripts";
|
|
|
|
expect(containerfile).toContain("AS pruner");
|
|
expect(containerfile).toContain(pruneCommand);
|
|
expect(containerfile).toContain(jsonCopy);
|
|
expect(containerfile).toContain(sourceCopy);
|
|
expect(containerfile).toContain(tsconfigCopy);
|
|
expect(containerfile).not.toContain(
|
|
"COPY packages/dashboard-model/package.json",
|
|
);
|
|
expect(containerfile).not.toContain("COPY packages/ui/package.json");
|
|
|
|
const jsonCopyIndex = containerfile.indexOf(jsonCopy);
|
|
const installIndex = containerfile.indexOf(installCommand);
|
|
const sourceCopyIndex = containerfile.indexOf(sourceCopy);
|
|
const tsconfigCopyIndex = containerfile.indexOf(tsconfigCopy);
|
|
|
|
expect(jsonCopyIndex).toBeGreaterThan(-1);
|
|
expect(installIndex).toBeGreaterThan(jsonCopyIndex);
|
|
expect(sourceCopyIndex).toBeGreaterThan(installIndex);
|
|
expect(tsconfigCopyIndex).toBeGreaterThan(sourceCopyIndex);
|
|
expect(containerfile.indexOf("RUN bun run build")).toBeGreaterThan(
|
|
tsconfigCopyIndex,
|
|
);
|
|
});
|
|
|
|
test("keeps local turbo prune output out of git and container contexts", () => {
|
|
const webPackage = JSON.parse(
|
|
readFileSync(join(root, "apps/web/package.json"), "utf8"),
|
|
) as { scripts?: Record<string, string> };
|
|
const gitignore = readFileSync(join(root, ".gitignore"), "utf8");
|
|
const containerignore = readFileSync(join(root, ".containerignore"), "utf8");
|
|
const containerIgnoreRules = new Set(
|
|
containerignore
|
|
.split(/\r?\n/)
|
|
.map((line) => line.trim())
|
|
.filter(Boolean),
|
|
);
|
|
|
|
expect(gitignore).toContain("out/");
|
|
expect(containerignore).toContain("out");
|
|
expect([...containerIgnoreRules]).toEqual(
|
|
expect.arrayContaining([
|
|
"apps/*/.turbo",
|
|
"apps/*/build",
|
|
"apps/*/data",
|
|
"apps/*/dist",
|
|
"apps/*/playwright-report",
|
|
"apps/*/test-results",
|
|
"packages/*/.turbo",
|
|
"packages/*/dist",
|
|
"packages/*/storybook-static",
|
|
]),
|
|
);
|
|
expect(webPackage.scripts?.build).toBe(
|
|
"rm -rf build && vite build && bun build src/server/index.ts --target bun --outdir build",
|
|
);
|
|
});
|
|
|
|
test("defines Forgejo CI and main-branch deploy automation", () => {
|
|
const workflow = readFileSync(
|
|
join(root, ".forgejo/workflows/dimensionlab-website.yml"),
|
|
"utf8",
|
|
);
|
|
|
|
expect(workflow).toContain("name: Dimension Lab website");
|
|
expect(workflow).toContain("pull_request:");
|
|
expect(workflow).toContain("push:");
|
|
expect(workflow).toContain("branches:");
|
|
expect(workflow).toContain("- main");
|
|
expect(workflow).toContain("runs-on: docker");
|
|
expect(workflow).toContain("bun install --frozen-lockfile");
|
|
expect(workflow).toContain("bun run check");
|
|
expect(workflow).toContain("bun run test");
|
|
expect(workflow).toContain("bun run build");
|
|
expect(workflow).toContain("needs: ci");
|
|
expect(workflow).toContain("github.event_name == 'push'");
|
|
expect(workflow).toContain("github.ref == 'refs/heads/main'");
|
|
expect(workflow).toContain("docker inspect dimensionlab-website");
|
|
expect(workflow).toContain("PODMAN_SYSTEMD_UNIT");
|
|
expect(workflow).toContain("scripts/deploy-dimensionlab-website.sh");
|
|
});
|
|
|
|
test("keeps production deployment behind a guarded script", () => {
|
|
const deployScript = readFileSync(
|
|
join(root, "scripts/deploy-dimensionlab-website.sh"),
|
|
"utf8",
|
|
);
|
|
|
|
expect(deployScript).toContain("refs/heads/main");
|
|
expect(deployScript).toContain("dimensionlab-website.service");
|
|
expect(deployScript).toContain("localhost/dimensionlab-website");
|
|
expect(deployScript).toContain("apps/web/Containerfile");
|
|
expect(deployScript).toContain("rollback-");
|
|
expect(deployScript).toContain("https://dimensionlab.net");
|
|
expect(deployScript).toContain("/api/dashboard/tiles");
|
|
expect(deployScript).toContain("--dry-run");
|
|
expect(deployScript).toContain("DEPLOY_RESTART_STRATEGY");
|
|
});
|
|
|
|
test("rolls back the latest image when production smoke checks fail", () => {
|
|
const result = runDeployScriptWithFakes(
|
|
{
|
|
curl: failingCurlCommand,
|
|
git: fakeGitCommand,
|
|
podman: fakePodmanCommand("dimensionlab-website.service"),
|
|
},
|
|
{
|
|
DEPLOY_CONTAINER_CLI: "podman",
|
|
DEPLOY_EVENT_NAME: "push",
|
|
DEPLOY_REF: "refs/heads/main",
|
|
DEPLOY_RESTART_STRATEGY: "quadlet-container",
|
|
DEPLOY_SHA: "1234567890abcdef",
|
|
DEPLOY_SMOKE_TIMEOUT_SECONDS: "0",
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain("smoke checks failed");
|
|
expect(result.stderr).toContain("rolling back to localhost/dimensionlab-website:rollback-");
|
|
expect(result.log).toMatch(
|
|
/tag localhost\/dimensionlab-website:latest localhost\/dimensionlab-website:rollback-\d{14}/,
|
|
);
|
|
expect(result.log).toMatch(
|
|
/tag localhost\/dimensionlab-website:rollback-\d{14} localhost\/dimensionlab-website:latest/,
|
|
);
|
|
expect(result.log.match(/^stop dimensionlab-website$/gm)).toHaveLength(2);
|
|
});
|
|
|
|
test("rolls back the latest image when the container fails to restart", () => {
|
|
const result = runDeployScriptWithFakes(
|
|
{
|
|
curl: passingCurlCommand,
|
|
git: fakeGitCommand,
|
|
podman: fakePodmanCommand("dimensionlab-website.service"),
|
|
},
|
|
{
|
|
DEPLOY_CONTAINER_CLI: "podman",
|
|
DEPLOY_CONTAINER_START_TIMEOUT_SECONDS: "1",
|
|
DEPLOY_TEST_CONTAINER_IMAGE: "wrong",
|
|
DEPLOY_EVENT_NAME: "push",
|
|
DEPLOY_REF: "refs/heads/main",
|
|
DEPLOY_RESTART_STRATEGY: "quadlet-container",
|
|
DEPLOY_SHA: "1234567890abcdef",
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain("did not restart on localhost/dimensionlab-website:latest");
|
|
expect(result.stderr).toContain("rolling back to localhost/dimensionlab-website:rollback-");
|
|
expect(result.stderr).toContain("rollback image is running");
|
|
expect(result.log).toMatch(
|
|
/tag localhost\/dimensionlab-website:rollback-\d{14} localhost\/dimensionlab-website:latest/,
|
|
);
|
|
expect(result.log.match(/^stop dimensionlab-website$/gm)).toHaveLength(2);
|
|
});
|
|
|
|
test.each([
|
|
{
|
|
env: { DEPLOY_EVENT_NAME: "pull_request", DEPLOY_REF: "refs/heads/main" },
|
|
message: "production deploys only run for push",
|
|
},
|
|
{
|
|
env: { DEPLOY_EVENT_NAME: "push", DEPLOY_REF: "refs/heads/codex/test" },
|
|
message: "expected refs/heads/main",
|
|
},
|
|
])("refuses guarded deploy contexts before host mutations", ({ env, message }) => {
|
|
const result = runDeployScriptWithFakes(
|
|
{
|
|
curl: passingCurlCommand,
|
|
git: fakeGitCommand,
|
|
podman: fakePodmanCommand("dimensionlab-website.service"),
|
|
},
|
|
{
|
|
DEPLOY_CONTAINER_CLI: "podman",
|
|
DEPLOY_SHA: "1234567890abcdef",
|
|
...env,
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain(message);
|
|
expect(result.log).toBe("");
|
|
});
|
|
|
|
test("refuses stop-based deploys unless the container belongs to the expected unit", () => {
|
|
const result = runDeployScriptWithFakes(
|
|
{
|
|
curl: passingCurlCommand,
|
|
git: fakeGitCommand,
|
|
podman: fakePodmanCommand("other.service"),
|
|
},
|
|
{
|
|
DEPLOY_CONTAINER_CLI: "podman",
|
|
DEPLOY_EVENT_NAME: "push",
|
|
DEPLOY_REF: "refs/heads/main",
|
|
DEPLOY_RESTART_STRATEGY: "quadlet-container",
|
|
DEPLOY_SHA: "1234567890abcdef",
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain(
|
|
"refusing to stop dimensionlab-website; expected PODMAN_SYSTEMD_UNIT=dimensionlab-website.service",
|
|
);
|
|
expect(result.log).not.toContain("build ");
|
|
expect(result.log).not.toContain("stop dimensionlab-website");
|
|
});
|
|
|
|
test("checks the expected unit before auto falls back to stopping the container", () => {
|
|
const result = runDeployScriptWithFakes(
|
|
{
|
|
curl: passingCurlCommand,
|
|
git: fakeGitCommand,
|
|
podman: fakePodmanCommand("other.service"),
|
|
systemctl: fakeSystemctlCommand({ active: false, show: true }),
|
|
},
|
|
{
|
|
DEPLOY_CONTAINER_CLI: "podman",
|
|
DEPLOY_EVENT_NAME: "push",
|
|
DEPLOY_REF: "refs/heads/main",
|
|
DEPLOY_RESTART_STRATEGY: "auto",
|
|
DEPLOY_SHA: "1234567890abcdef",
|
|
},
|
|
);
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain(
|
|
"refusing to stop dimensionlab-website; expected PODMAN_SYSTEMD_UNIT=dimensionlab-website.service",
|
|
);
|
|
expect(result.log).not.toContain("stop dimensionlab-website");
|
|
});
|
|
});
|
|
|
|
type WorkspacePackageExport =
|
|
| string
|
|
| {
|
|
types?: string;
|
|
development?: string;
|
|
default?: string;
|
|
};
|
|
|
|
function expectPackageExport(
|
|
actual: WorkspacePackageExport | undefined,
|
|
expected: Exclude<WorkspacePackageExport, string>,
|
|
): void {
|
|
expect(actual).toMatchObject(expected);
|
|
}
|
|
|
|
function runDeployScriptWithFakes(
|
|
commands: Record<string, string>,
|
|
env: Record<string, string>,
|
|
): { log: string; status: number | null; stderr: string; stdout: string } {
|
|
const tempDir = mkdtempSync(join(tmpdir(), "dimensionlab-deploy-test-"));
|
|
const logPath = join(tempDir, "commands.log");
|
|
|
|
for (const [name, source] of Object.entries(commands)) {
|
|
const commandPath = join(tempDir, name);
|
|
writeFileSync(commandPath, source);
|
|
chmodSync(commandPath, 0o755);
|
|
}
|
|
|
|
const result = spawnSync("bash", [join(root, "scripts/deploy-dimensionlab-website.sh")], {
|
|
cwd: root,
|
|
encoding: "utf8",
|
|
env: {
|
|
...process.env,
|
|
...env,
|
|
DEPLOY_TEST_LOG: logPath,
|
|
PATH: `${tempDir}:${process.env.PATH ?? ""}`,
|
|
},
|
|
});
|
|
|
|
return {
|
|
log: existsSync(logPath) ? readFileSync(logPath, "utf8") : "",
|
|
status: result.status,
|
|
stderr: result.stderr,
|
|
stdout: result.stdout,
|
|
};
|
|
}
|
|
|
|
const fakeGitCommand = `#!/usr/bin/env bash
|
|
case "$1" in
|
|
branch)
|
|
echo main
|
|
;;
|
|
rev-parse)
|
|
echo 1234567890abcdef
|
|
;;
|
|
config|submodule)
|
|
exit 0
|
|
;;
|
|
esac
|
|
`;
|
|
|
|
function fakePodmanCommand(systemdUnit: string): string {
|
|
return `#!/usr/bin/env bash
|
|
state_file="$DEPLOY_TEST_LOG.state"
|
|
[ -f "$state_file" ] || printf 'initial' > "$state_file"
|
|
printf '%s\\n' "$*" >> "$DEPLOY_TEST_LOG"
|
|
if [ "$1" = "image" ] && [ "$2" = "inspect" ]; then
|
|
if [ "$4" = "--format" ]; then
|
|
case "$3" in
|
|
*:rollback-*)
|
|
echo sha256:old
|
|
;;
|
|
*)
|
|
if [ "$(cat "$state_file")" = "rollback" ]; then
|
|
echo sha256:old
|
|
else
|
|
echo sha256:new
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
exit 0
|
|
fi
|
|
if [ "$1" = "tag" ] && [ "$2" != "localhost/dimensionlab-website:latest" ]; then
|
|
printf 'rollback' > "$state_file"
|
|
fi
|
|
if [ "$1" = "inspect" ]; then
|
|
case "$*" in
|
|
*PODMAN_SYSTEMD_UNIT*)
|
|
echo ${systemdUnit}
|
|
;;
|
|
*State.Running*)
|
|
echo true
|
|
;;
|
|
*'.Image'*|*'{{.Image}}'*)
|
|
if [ "$(cat "$state_file")" = "rollback" ]; then
|
|
echo sha256:old
|
|
elif [ "\${DEPLOY_TEST_CONTAINER_IMAGE:-new}" = "wrong" ]; then
|
|
echo sha256:wrong
|
|
else
|
|
echo sha256:new
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
`;
|
|
}
|
|
|
|
const failingCurlCommand = `#!/usr/bin/env bash
|
|
printf 'curl %s\\n' "$*" >> "$DEPLOY_TEST_LOG"
|
|
exit 22
|
|
`;
|
|
|
|
const passingCurlCommand = `#!/usr/bin/env bash
|
|
printf 'curl %s\\n' "$*" >> "$DEPLOY_TEST_LOG"
|
|
if [ "$*" = *'/api/dashboard/tiles'* ]; then
|
|
printf '{"state":"ready","tiles":[]}'
|
|
fi
|
|
`;
|
|
|
|
function fakeSystemctlCommand(options: { active: boolean; show: boolean }): string {
|
|
const activeStatus = options.active ? 0 : 3;
|
|
const showStatus = options.show ? 0 : 1;
|
|
|
|
return `#!/usr/bin/env bash
|
|
printf 'systemctl %s\\n' "$*" >> "$DEPLOY_TEST_LOG"
|
|
if [ "$1" = "--user" ] && [ "$2" = "is-active" ]; then
|
|
exit ${activeStatus}
|
|
fi
|
|
if [ "$1" = "--user" ] && [ "$2" = "show" ]; then
|
|
exit ${showStatus}
|
|
fi
|
|
if [ "$1" = "--user" ] && [ "$2" = "restart" ]; then
|
|
exit 0
|
|
fi
|
|
`;
|
|
}
|