Files
MineGeo/tests/dokploy-diagnostics.test.ts
pavel444-byte 8dc8ee3bd7
Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 4m2s
Add detailed diagnostics to Dokploy deployment workflow
2026-10-17 13:38:05 +05:00

206 lines
6.8 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { spawnSync, type SpawnSyncReturns } from "node:child_process";
import { redact, responseDetails } from "../scripts/dokploy-diagnostics.mjs";
test("Dokploy diagnostics redact credentials and strip terminal controls", () => {
const key = "secret/key?value";
const message = redact(
`Key ${key} encoded ${encodeURIComponent(key)}\nx-api-key: ${key}\nAuthorization: Bearer token\nhttps://user:password@proxy.test\n\u001b[31m`,
[key],
);
for (const value of [
key,
encodeURIComponent(key),
"Bearer token",
"user:password",
"\u001b",
])
assert.ok(!message.includes(value));
assert.ok(message.includes("[REDACTED]"));
});
test("API failure details include useful error fields without dumping deployments, configuration or cookies", () => {
const result = responseDetails(
"HTTP/2 403\r\nserver: cloudflare\r\ncf-ray: abc\r\nset-cookie: private\r\n",
JSON.stringify({
error: {
message: "Permission denied",
code: "FORBIDDEN",
config: "private config",
},
source: "private source",
}),
).join("\n");
assert.ok(result.includes("HTTP/2 403"));
assert.ok(result.includes("Permission denied"));
assert.ok(result.includes("cf-ray: abc"));
for (const value of ["private config", "private source", "set-cookie"])
assert.ok(!result.includes(value));
const html = responseDetails(
"content-type: text/html",
"<title>Bad Gateway</title><body>Private proxy details</body>",
).join("\n");
assert.ok(html.includes("Bad Gateway"));
assert.ok(!html.includes("Private proxy details"));
});
test("HTTP 000 diagnostics preserve curl DNS/TLS errors, timings and exit codes while masking the key", () => {
const directory = fs.mkdtempSync(
path.join(os.tmpdir(), "minegeo-dokploy-diagnostics-"),
);
try {
const file = (name: string, value: string) => {
const target = path.join(directory, name);
fs.writeFileSync(target, value);
return target;
};
const key = "test-dokploy-secret-123";
const metrics = file(
"metrics",
"000\nremote_ip= dns=0.002s connect=0s tls=0s total=0.003s\nssl_verify_result=20\n",
);
const headers = file("headers", "");
const body = file("body", "");
const auth = file("auth", `x-api-key: ${key}\n`);
for (const [code, error, expected] of [
[
"6",
"curl: (6) Could not resolve host: dokploy.minegeo.site",
"Target DNS failed",
],
[
"60",
`curl: (60) SSL certificate problem: unable to get local issuer certificate ${key}`,
"TLS certificate verification failed",
],
]) {
const stderr = file("error", error);
const result = spawnSync(
process.execPath,
[
"scripts/dokploy-diagnostics.mjs",
"request",
"API preflight",
code,
metrics,
stderr,
headers,
body,
auth,
],
{ encoding: "utf8" },
);
assert.equal(result.status, 0, result.stderr);
assert.ok(result.stderr.includes(`curl exit=${code}`));
assert.ok(result.stderr.includes(expected));
assert.ok(result.stderr.includes("HTTP 000 means no HTTP response"));
assert.ok(result.stderr.includes("dns=0.002s"));
assert.ok(result.stderr.includes("Response body: empty"));
assert.ok(!result.stderr.includes(key));
assert.equal(result.stdout, "");
}
} finally {
fs.rmSync(directory, { recursive: true });
}
});
test("deployment workflow has valid Bash syntax and captures failures without disabling certificate checks", () => {
const workflow = fs
.readFileSync(".gitea/workflows/deploy-dokploy.yml", "utf8")
.replace(/\r\n/g, "\n");
const script = workflow
.split(" run: |\n")[1]
?.split("\n")
.map((line) => line.replace(/^ /, ""))
.join("\n");
assert.ok(script, "workflow script was not found");
assert.ok(
script.includes("readonly DOKPLOY_URL='https://dokploy.minegeo.site'"),
);
assert.ok(script.includes("curl_exit=$?"));
assert.ok(script.includes("--silent --show-error"));
assert.ok(!script.includes("2>/dev/null"));
assert.ok(!script.includes("--insecure"));
const bash =
process.platform === "win32"
? "C:/Program Files/Git/bin/bash.exe"
: "/bin/bash";
const result = spawnSync(bash, ["--noprofile", "--norc", "-n"], {
input: script,
encoding: "utf8",
});
assert.equal(result.status, 0, result.stderr || String(result.error));
});
test("the workflow request wrapper fails on HTTP 000 and reports the original curl exit code", () => {
const workflow = fs
.readFileSync(".gitea/workflows/deploy-dokploy.yml", "utf8")
.replace(/\r\n/g, "\n");
const script = workflow
.split(" run: |\n")[1]
.split("\n")
.map((line) => line.replace(/^ /, ""))
.join("\n");
const wrapper = script.match(/dokploy_request\(\) \{[\s\S]*?\n\}/)?.[0];
assert.ok(wrapper);
const directory = fs.mkdtempSync(
path.join(os.tmpdir(), "minegeo-dokploy-wrapper-"),
);
const bash =
process.platform === "win32"
? "C:/Program Files/Git/bin/bash.exe"
: "/bin/bash";
try {
fs.writeFileSync(
path.join(directory, "auth"),
"x-api-key: fake-secret-for-wrapper-test\n",
);
for (const [code, http, expected] of [
[6, "000", 1],
[60, "000", 1],
[0, "403", 1],
[0, "200", 0],
] as const) {
const result: SpawnSyncReturns<string> = spawnSync(
bash,
["--noprofile", "--norc", "-s"],
{
encoding: "utf8",
env: {
...process.env,
FIXTURE_DIR: directory.replace(/\\/g, "/"),
DOKPLOY_APPLICATION_ID: "test-app",
},
input: `set -eu
auth_file="$FIXTURE_DIR/auth"
metrics_file="$FIXTURE_DIR/metrics"
error_file="$FIXTURE_DIR/error"
headers_file="$FIXTURE_DIR/headers"
${wrapper}
curl() { printf '${http}\\ndns=0.001s total=0.002s\\n'; if [ ${code} -ne 0 ]; then printf 'curl: (${code}) simulated network failure\\n' >&2; fi; return ${code}; }
dokploy_request 'Mock API preflight' "$FIXTURE_DIR/body" --get https://dokploy.minegeo.site/api/deployment.all
`,
},
);
assert.equal(
result.status,
expected,
result.stderr || String(result.error),
);
assert.ok(result.stderr.includes(`curl exit=${code}`));
assert.ok(result.stderr.includes(http));
assert.ok(!result.stderr.includes("fake-secret-for-wrapper-test"));
if (code)
assert.ok(
result.stderr.includes(`curl: (${code}) simulated network failure`),
);
}
} finally {
fs.rmSync(directory, { recursive: true });
}
});