@better-openclaw/core 1.0.30 → 1.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/deployers/coolify.test.cjs +156 -0
- package/dist/deployers/coolify.test.cjs.map +1 -0
- package/dist/deployers/coolify.test.d.cts +1 -0
- package/dist/deployers/coolify.test.d.mts +1 -0
- package/dist/deployers/coolify.test.mjs +157 -0
- package/dist/deployers/coolify.test.mjs.map +1 -0
- package/dist/deployers/dokploy.test.cjs +108 -0
- package/dist/deployers/dokploy.test.cjs.map +1 -0
- package/dist/deployers/dokploy.test.d.cts +1 -0
- package/dist/deployers/dokploy.test.d.mts +1 -0
- package/dist/deployers/dokploy.test.mjs +109 -0
- package/dist/deployers/dokploy.test.mjs.map +1 -0
- package/dist/frameworks/frameworks.test.cjs +94 -0
- package/dist/frameworks/frameworks.test.cjs.map +1 -0
- package/dist/frameworks/frameworks.test.d.cts +1 -0
- package/dist/frameworks/frameworks.test.d.mts +1 -0
- package/dist/frameworks/frameworks.test.mjs +94 -0
- package/dist/frameworks/frameworks.test.mjs.map +1 -0
- package/dist/generators/cloud-init.test.cjs +58 -0
- package/dist/generators/cloud-init.test.cjs.map +1 -0
- package/dist/generators/cloud-init.test.d.cts +1 -0
- package/dist/generators/cloud-init.test.d.mts +1 -0
- package/dist/generators/cloud-init.test.mjs +59 -0
- package/dist/generators/cloud-init.test.mjs.map +1 -0
- package/dist/generators/get-shit-done.test.cjs +48 -0
- package/dist/generators/get-shit-done.test.cjs.map +1 -0
- package/dist/generators/get-shit-done.test.d.cts +1 -0
- package/dist/generators/get-shit-done.test.d.mts +1 -0
- package/dist/generators/get-shit-done.test.mjs +49 -0
- package/dist/generators/get-shit-done.test.mjs.map +1 -0
- package/dist/generators/grafana.test.cjs +74 -0
- package/dist/generators/grafana.test.cjs.map +1 -0
- package/dist/generators/grafana.test.d.cts +1 -0
- package/dist/generators/grafana.test.d.mts +1 -0
- package/dist/generators/grafana.test.mjs +74 -0
- package/dist/generators/grafana.test.mjs.map +1 -0
- package/dist/generators/n8n-workflows.test.cjs +75 -0
- package/dist/generators/n8n-workflows.test.cjs.map +1 -0
- package/dist/generators/n8n-workflows.test.d.cts +1 -0
- package/dist/generators/n8n-workflows.test.d.mts +1 -0
- package/dist/generators/n8n-workflows.test.mjs +76 -0
- package/dist/generators/n8n-workflows.test.mjs.map +1 -0
- package/dist/generators/openclaw-install-script.test.cjs +35 -0
- package/dist/generators/openclaw-install-script.test.cjs.map +1 -0
- package/dist/generators/openclaw-install-script.test.d.cts +1 -0
- package/dist/generators/openclaw-install-script.test.d.mts +1 -0
- package/dist/generators/openclaw-install-script.test.mjs +36 -0
- package/dist/generators/openclaw-install-script.test.mjs.map +1 -0
- package/dist/generators/postgres-init.test.cjs +111 -0
- package/dist/generators/postgres-init.test.cjs.map +1 -0
- package/dist/generators/postgres-init.test.d.cts +1 -0
- package/dist/generators/postgres-init.test.d.mts +1 -0
- package/dist/generators/postgres-init.test.mjs +112 -0
- package/dist/generators/postgres-init.test.mjs.map +1 -0
- package/dist/generators/prometheus.test.cjs +99 -0
- package/dist/generators/prometheus.test.cjs.map +1 -0
- package/dist/generators/prometheus.test.d.cts +1 -0
- package/dist/generators/prometheus.test.d.mts +1 -0
- package/dist/generators/prometheus.test.mjs +99 -0
- package/dist/generators/prometheus.test.mjs.map +1 -0
- package/dist/generators/stack-manifest.test.cjs +97 -0
- package/dist/generators/stack-manifest.test.cjs.map +1 -0
- package/dist/generators/stack-manifest.test.d.cts +1 -0
- package/dist/generators/stack-manifest.test.d.mts +1 -0
- package/dist/generators/stack-manifest.test.mjs +98 -0
- package/dist/generators/stack-manifest.test.mjs.map +1 -0
- package/dist/index.cjs +0 -2
- package/dist/index.d.cts +1 -2
- package/dist/index.d.mts +1 -2
- package/dist/index.mjs +1 -2
- package/dist/logger/index.cjs +0 -2
- package/dist/logger/index.d.cts +1 -2
- package/dist/logger/index.d.mts +1 -2
- package/dist/logger/index.mjs +1 -2
- package/dist/port-scanner.test.cjs +155 -0
- package/dist/port-scanner.test.cjs.map +1 -0
- package/dist/port-scanner.test.d.cts +1 -0
- package/dist/port-scanner.test.d.mts +1 -0
- package/dist/port-scanner.test.mjs +156 -0
- package/dist/port-scanner.test.mjs.map +1 -0
- package/package.json +1 -1
- package/src/deployers/coolify.test.ts +180 -0
- package/src/deployers/dokploy.test.ts +120 -0
- package/src/frameworks/frameworks.test.ts +119 -0
- package/src/generators/cloud-init.test.ts +70 -0
- package/src/generators/get-shit-done.test.ts +54 -0
- package/src/generators/grafana.test.ts +90 -0
- package/src/generators/n8n-workflows.test.ts +80 -0
- package/src/generators/openclaw-install-script.test.ts +42 -0
- package/src/generators/postgres-init.test.ts +116 -0
- package/src/generators/prometheus.test.ts +108 -0
- package/src/generators/stack-manifest.test.ts +104 -0
- package/src/index.ts +3 -2
- package/src/logger/index.ts +2 -1
- package/src/port-scanner.test.ts +167 -0
package/README.md
CHANGED
|
@@ -98,7 +98,8 @@ The core ships a centralized `OperationsLogger` used by all packages (CLI, API,
|
|
|
98
98
|
### Programmatic Usage
|
|
99
99
|
|
|
100
100
|
```typescript
|
|
101
|
-
import { OperationsLogger,
|
|
101
|
+
import { OperationsLogger, ConsoleSink, CallbackSink } from "@better-openclaw/core";
|
|
102
|
+
import { FileSink } from "@better-openclaw/core/logger/sinks/file-sink";
|
|
102
103
|
|
|
103
104
|
const logger = new OperationsLogger({
|
|
104
105
|
source: "cli", // "cli" | "api" | "mcp" | "web" | "core"
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const require_test_CTcmp4Su = require("../test.CTcmp4Su-BWSPM8ZQ.cjs");
|
|
2
|
+
//#region src/deployers/coolify.test.ts
|
|
3
|
+
/**
|
|
4
|
+
* Tests for Coolify deployer utility functions and deploy orchestration.
|
|
5
|
+
*
|
|
6
|
+
* We mock `fetch` to simulate Coolify API responses and test the
|
|
7
|
+
* multi-step deployment flow without making real HTTP requests.
|
|
8
|
+
*/
|
|
9
|
+
function hashString(str) {
|
|
10
|
+
let hash = 0;
|
|
11
|
+
for (let i = 0; i < str.length; i++) {
|
|
12
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
13
|
+
hash |= 0;
|
|
14
|
+
}
|
|
15
|
+
return hash;
|
|
16
|
+
}
|
|
17
|
+
function parseEnvContent(envContent) {
|
|
18
|
+
if (!envContent) return [];
|
|
19
|
+
const result = [];
|
|
20
|
+
for (const line of envContent.split("\n")) {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
23
|
+
const idx = trimmed.indexOf("=");
|
|
24
|
+
if (idx <= 0) continue;
|
|
25
|
+
const key = trimmed.slice(0, idx);
|
|
26
|
+
const value = trimmed.slice(idx + 1);
|
|
27
|
+
result.push({
|
|
28
|
+
key,
|
|
29
|
+
value,
|
|
30
|
+
is_preview: false,
|
|
31
|
+
is_build_time: false,
|
|
32
|
+
is_literal: true
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
require_test_CTcmp4Su.describe("hashString", () => {
|
|
38
|
+
require_test_CTcmp4Su.it("returns 0 for empty string", () => {
|
|
39
|
+
require_test_CTcmp4Su.globalExpect(hashString("")).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
require_test_CTcmp4Su.it("returns same hash for same input", () => {
|
|
42
|
+
require_test_CTcmp4Su.globalExpect(hashString("hello")).toBe(hashString("hello"));
|
|
43
|
+
});
|
|
44
|
+
require_test_CTcmp4Su.it("returns different hash for different input", () => {
|
|
45
|
+
require_test_CTcmp4Su.globalExpect(hashString("hello")).not.toBe(hashString("world"));
|
|
46
|
+
});
|
|
47
|
+
require_test_CTcmp4Su.it("detects compose changes", () => {
|
|
48
|
+
require_test_CTcmp4Su.globalExpect(hashString("version: '3'\nservices:\n redis:\n image: redis:7")).not.toBe(hashString("version: '3'\nservices:\n redis:\n image: redis:8"));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
require_test_CTcmp4Su.describe("parseEnvContent", () => {
|
|
52
|
+
require_test_CTcmp4Su.it("returns empty array for undefined input", () => {
|
|
53
|
+
require_test_CTcmp4Su.globalExpect(parseEnvContent(void 0)).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
require_test_CTcmp4Su.it("returns empty array for empty string", () => {
|
|
56
|
+
require_test_CTcmp4Su.globalExpect(parseEnvContent("")).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
require_test_CTcmp4Su.it("parses simple key=value pairs", () => {
|
|
59
|
+
const result = parseEnvContent("FOO=bar\nBAZ=qux");
|
|
60
|
+
require_test_CTcmp4Su.globalExpect(result).toHaveLength(2);
|
|
61
|
+
require_test_CTcmp4Su.globalExpect(result[0]).toEqual({
|
|
62
|
+
key: "FOO",
|
|
63
|
+
value: "bar",
|
|
64
|
+
is_preview: false,
|
|
65
|
+
is_build_time: false,
|
|
66
|
+
is_literal: true
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
require_test_CTcmp4Su.it("skips comments", () => {
|
|
70
|
+
const result = parseEnvContent("# This is a comment\nFOO=bar");
|
|
71
|
+
require_test_CTcmp4Su.globalExpect(result).toHaveLength(1);
|
|
72
|
+
require_test_CTcmp4Su.globalExpect(result[0].key).toBe("FOO");
|
|
73
|
+
});
|
|
74
|
+
require_test_CTcmp4Su.it("skips blank lines", () => {
|
|
75
|
+
require_test_CTcmp4Su.globalExpect(parseEnvContent("FOO=bar\n\n\nBAZ=qux")).toHaveLength(2);
|
|
76
|
+
});
|
|
77
|
+
require_test_CTcmp4Su.it("handles values containing equals signs", () => {
|
|
78
|
+
const result = parseEnvContent("DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=require");
|
|
79
|
+
require_test_CTcmp4Su.globalExpect(result).toHaveLength(1);
|
|
80
|
+
require_test_CTcmp4Su.globalExpect(result[0].key).toBe("DATABASE_URL");
|
|
81
|
+
require_test_CTcmp4Su.globalExpect(result[0].value).toBe("postgres://user:pass@host:5432/db?sslmode=require");
|
|
82
|
+
});
|
|
83
|
+
require_test_CTcmp4Su.it("handles empty values", () => {
|
|
84
|
+
const result = parseEnvContent("EMPTY_VAR=");
|
|
85
|
+
require_test_CTcmp4Su.globalExpect(result).toHaveLength(1);
|
|
86
|
+
require_test_CTcmp4Su.globalExpect(result[0].key).toBe("EMPTY_VAR");
|
|
87
|
+
require_test_CTcmp4Su.globalExpect(result[0].value).toBe("");
|
|
88
|
+
});
|
|
89
|
+
require_test_CTcmp4Su.it("skips lines without = sign", () => {
|
|
90
|
+
const result = parseEnvContent("INVALID_LINE\nVALID=yes");
|
|
91
|
+
require_test_CTcmp4Su.globalExpect(result).toHaveLength(1);
|
|
92
|
+
require_test_CTcmp4Su.globalExpect(result[0].key).toBe("VALID");
|
|
93
|
+
});
|
|
94
|
+
require_test_CTcmp4Su.it("handles quoted values", () => {
|
|
95
|
+
const result = parseEnvContent("PASSWORD=\"my secret\"");
|
|
96
|
+
require_test_CTcmp4Su.globalExpect(result[0].key).toBe("PASSWORD");
|
|
97
|
+
require_test_CTcmp4Su.globalExpect(result[0].value).toBe("\"my secret\"");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
require_test_CTcmp4Su.describe("CoolifyDeployer", () => {
|
|
101
|
+
require_test_CTcmp4Su.beforeEach(() => {
|
|
102
|
+
require_test_CTcmp4Su.vi.restoreAllMocks();
|
|
103
|
+
});
|
|
104
|
+
require_test_CTcmp4Su.it("can be imported and instantiated", async () => {
|
|
105
|
+
const { CoolifyDeployer } = await Promise.resolve().then(() => require("./coolify.cjs"));
|
|
106
|
+
const deployer = new CoolifyDeployer();
|
|
107
|
+
require_test_CTcmp4Su.globalExpect(deployer.name).toBe("Coolify");
|
|
108
|
+
require_test_CTcmp4Su.globalExpect(deployer.id).toBe("coolify");
|
|
109
|
+
});
|
|
110
|
+
require_test_CTcmp4Su.it("testConnection returns error on network failure", async () => {
|
|
111
|
+
require_test_CTcmp4Su.vi.stubGlobal("fetch", require_test_CTcmp4Su.vi.fn().mockRejectedValue(/* @__PURE__ */ new Error("Connection refused")));
|
|
112
|
+
const { CoolifyDeployer } = await Promise.resolve().then(() => require("./coolify.cjs"));
|
|
113
|
+
const result = await new CoolifyDeployer().testConnection({
|
|
114
|
+
instanceUrl: "https://coolify.example.com",
|
|
115
|
+
apiKey: "test-key"
|
|
116
|
+
});
|
|
117
|
+
require_test_CTcmp4Su.globalExpect(result.ok).toBe(false);
|
|
118
|
+
require_test_CTcmp4Su.globalExpect(result.error).toContain("Connection refused");
|
|
119
|
+
require_test_CTcmp4Su.vi.unstubAllGlobals();
|
|
120
|
+
});
|
|
121
|
+
require_test_CTcmp4Su.it("testConnection returns ok on successful version response", async () => {
|
|
122
|
+
require_test_CTcmp4Su.vi.stubGlobal("fetch", require_test_CTcmp4Su.vi.fn().mockResolvedValue({
|
|
123
|
+
ok: true,
|
|
124
|
+
text: () => Promise.resolve("\"v4.0.0\"")
|
|
125
|
+
}));
|
|
126
|
+
const { CoolifyDeployer } = await Promise.resolve().then(() => require("./coolify.cjs"));
|
|
127
|
+
require_test_CTcmp4Su.globalExpect((await new CoolifyDeployer().testConnection({
|
|
128
|
+
instanceUrl: "https://coolify.example.com",
|
|
129
|
+
apiKey: "test-key"
|
|
130
|
+
})).ok).toBe(true);
|
|
131
|
+
require_test_CTcmp4Su.vi.unstubAllGlobals();
|
|
132
|
+
});
|
|
133
|
+
require_test_CTcmp4Su.it("deploy returns error when no servers available", async () => {
|
|
134
|
+
require_test_CTcmp4Su.vi.stubGlobal("fetch", require_test_CTcmp4Su.vi.fn().mockResolvedValue({
|
|
135
|
+
ok: true,
|
|
136
|
+
text: () => Promise.resolve("[]")
|
|
137
|
+
}));
|
|
138
|
+
const { CoolifyDeployer } = await Promise.resolve().then(() => require("./coolify.cjs"));
|
|
139
|
+
const result = await new CoolifyDeployer().deploy({
|
|
140
|
+
target: {
|
|
141
|
+
instanceUrl: "https://coolify.example.com",
|
|
142
|
+
apiKey: "test-key"
|
|
143
|
+
},
|
|
144
|
+
projectName: "test-project",
|
|
145
|
+
composeYaml: "version: '3'",
|
|
146
|
+
envContent: ""
|
|
147
|
+
});
|
|
148
|
+
require_test_CTcmp4Su.globalExpect(result.success).toBe(false);
|
|
149
|
+
require_test_CTcmp4Su.globalExpect(result.error).toContain("No Coolify servers");
|
|
150
|
+
require_test_CTcmp4Su.globalExpect(result.steps[0].status).toBe("error");
|
|
151
|
+
require_test_CTcmp4Su.vi.unstubAllGlobals();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
//#endregion
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=coolify.test.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coolify.test.cjs","names":["describe","vi"],"sources":["../../src/deployers/coolify.test.ts"],"sourcesContent":["import { beforeEach, describe, expect, it, vi } from \"vitest\";\n\n/**\n * Tests for Coolify deployer utility functions and deploy orchestration.\n *\n * We mock `fetch` to simulate Coolify API responses and test the\n * multi-step deployment flow without making real HTTP requests.\n */\n\n// Inline the utility functions for direct testing\nfunction hashString(str: string) {\n\tlet hash = 0;\n\tfor (let i = 0; i < str.length; i++) {\n\t\thash = (hash << 5) - hash + str.charCodeAt(i);\n\t\thash |= 0;\n\t}\n\treturn hash;\n}\n\nfunction parseEnvContent(envContent?: string) {\n\tif (!envContent) return [];\n\tconst result = [];\n\tfor (const line of envContent.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed || trimmed.startsWith(\"#\")) continue;\n\t\tconst idx = trimmed.indexOf(\"=\");\n\t\tif (idx <= 0) continue;\n\t\tconst key = trimmed.slice(0, idx);\n\t\tconst value = trimmed.slice(idx + 1);\n\t\tresult.push({ key, value, is_preview: false, is_build_time: false, is_literal: true });\n\t}\n\treturn result;\n}\n\ndescribe(\"hashString\", () => {\n\tit(\"returns 0 for empty string\", () => {\n\t\texpect(hashString(\"\")).toBe(0);\n\t});\n\n\tit(\"returns same hash for same input\", () => {\n\t\texpect(hashString(\"hello\")).toBe(hashString(\"hello\"));\n\t});\n\n\tit(\"returns different hash for different input\", () => {\n\t\texpect(hashString(\"hello\")).not.toBe(hashString(\"world\"));\n\t});\n\n\tit(\"detects compose changes\", () => {\n\t\tconst v1 = \"version: '3'\\nservices:\\n redis:\\n image: redis:7\";\n\t\tconst v2 = \"version: '3'\\nservices:\\n redis:\\n image: redis:8\";\n\t\texpect(hashString(v1)).not.toBe(hashString(v2));\n\t});\n});\n\ndescribe(\"parseEnvContent\", () => {\n\tit(\"returns empty array for undefined input\", () => {\n\t\texpect(parseEnvContent(undefined)).toEqual([]);\n\t});\n\n\tit(\"returns empty array for empty string\", () => {\n\t\texpect(parseEnvContent(\"\")).toEqual([]);\n\t});\n\n\tit(\"parses simple key=value pairs\", () => {\n\t\tconst result = parseEnvContent(\"FOO=bar\\nBAZ=qux\");\n\t\texpect(result).toHaveLength(2);\n\t\texpect(result[0]).toEqual({\n\t\t\tkey: \"FOO\",\n\t\t\tvalue: \"bar\",\n\t\t\tis_preview: false,\n\t\t\tis_build_time: false,\n\t\t\tis_literal: true,\n\t\t});\n\t});\n\n\tit(\"skips comments\", () => {\n\t\tconst result = parseEnvContent(\"# This is a comment\\nFOO=bar\");\n\t\texpect(result).toHaveLength(1);\n\t\texpect(result[0]!.key).toBe(\"FOO\");\n\t});\n\n\tit(\"skips blank lines\", () => {\n\t\tconst result = parseEnvContent(\"FOO=bar\\n\\n\\nBAZ=qux\");\n\t\texpect(result).toHaveLength(2);\n\t});\n\n\tit(\"handles values containing equals signs\", () => {\n\t\tconst result = parseEnvContent(\n\t\t\t\"DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=require\",\n\t\t);\n\t\texpect(result).toHaveLength(1);\n\t\texpect(result[0]!.key).toBe(\"DATABASE_URL\");\n\t\texpect(result[0]!.value).toBe(\"postgres://user:pass@host:5432/db?sslmode=require\");\n\t});\n\n\tit(\"handles empty values\", () => {\n\t\tconst result = parseEnvContent(\"EMPTY_VAR=\");\n\t\texpect(result).toHaveLength(1);\n\t\texpect(result[0]!.key).toBe(\"EMPTY_VAR\");\n\t\texpect(result[0]!.value).toBe(\"\");\n\t});\n\n\tit(\"skips lines without = sign\", () => {\n\t\tconst result = parseEnvContent(\"INVALID_LINE\\nVALID=yes\");\n\t\texpect(result).toHaveLength(1);\n\t\texpect(result[0]!.key).toBe(\"VALID\");\n\t});\n\n\tit(\"handles quoted values\", () => {\n\t\tconst result = parseEnvContent('PASSWORD=\"my secret\"');\n\t\texpect(result[0]!.key).toBe(\"PASSWORD\");\n\t\texpect(result[0]!.value).toBe('\"my secret\"');\n\t});\n});\n\ndescribe(\"CoolifyDeployer\", () => {\n\tbeforeEach(() => {\n\t\tvi.restoreAllMocks();\n\t});\n\n\tit(\"can be imported and instantiated\", async () => {\n\t\tconst { CoolifyDeployer } = await import(\"./coolify.js\");\n\t\tconst deployer = new CoolifyDeployer();\n\t\texpect(deployer.name).toBe(\"Coolify\");\n\t\texpect(deployer.id).toBe(\"coolify\");\n\t});\n\n\tit(\"testConnection returns error on network failure\", async () => {\n\t\tvi.stubGlobal(\"fetch\", vi.fn().mockRejectedValue(new Error(\"Connection refused\")));\n\t\tconst { CoolifyDeployer } = await import(\"./coolify.js\");\n\t\tconst deployer = new CoolifyDeployer();\n\t\tconst result = await deployer.testConnection({\n\t\t\tinstanceUrl: \"https://coolify.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(result.ok).toBe(false);\n\t\texpect(result.error).toContain(\"Connection refused\");\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"testConnection returns ok on successful version response\", async () => {\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockResolvedValue({\n\t\t\t\tok: true,\n\t\t\t\ttext: () => Promise.resolve('\"v4.0.0\"'),\n\t\t\t}),\n\t\t);\n\t\tconst { CoolifyDeployer } = await import(\"./coolify.js\");\n\t\tconst deployer = new CoolifyDeployer();\n\t\tconst result = await deployer.testConnection({\n\t\t\tinstanceUrl: \"https://coolify.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(result.ok).toBe(true);\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"deploy returns error when no servers available\", async () => {\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockResolvedValue({\n\t\t\t\tok: true,\n\t\t\t\ttext: () => Promise.resolve(\"[]\"),\n\t\t\t}),\n\t\t);\n\t\tconst { CoolifyDeployer } = await import(\"./coolify.js\");\n\t\tconst deployer = new CoolifyDeployer();\n\t\tconst result = await deployer.deploy({\n\t\t\ttarget: { instanceUrl: \"https://coolify.example.com\", apiKey: \"test-key\" },\n\t\t\tprojectName: \"test-project\",\n\t\t\tcomposeYaml: \"version: '3'\",\n\t\t\tenvContent: \"\",\n\t\t});\n\t\texpect(result.success).toBe(false);\n\t\texpect(result.error).toContain(\"No Coolify servers\");\n\t\texpect(result.steps[0]!.status).toBe(\"error\");\n\t\tvi.unstubAllGlobals();\n\t});\n});\n"],"mappings":";;;;;;;;AAUA,SAAS,WAAW,KAAa;CAChC,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACpC,UAAQ,QAAQ,KAAK,OAAO,IAAI,WAAW,EAAE;AAC7C,UAAQ;;AAET,QAAO;;AAGR,SAAS,gBAAgB,YAAqB;AAC7C,KAAI,CAAC,WAAY,QAAO,EAAE;CAC1B,MAAM,SAAS,EAAE;AACjB,MAAK,MAAM,QAAQ,WAAW,MAAM,KAAK,EAAE;EAC1C,MAAM,UAAU,KAAK,MAAM;AAC3B,MAAI,CAAC,WAAW,QAAQ,WAAW,IAAI,CAAE;EACzC,MAAM,MAAM,QAAQ,QAAQ,IAAI;AAChC,MAAI,OAAO,EAAG;EACd,MAAM,MAAM,QAAQ,MAAM,GAAG,IAAI;EACjC,MAAM,QAAQ,QAAQ,MAAM,MAAM,EAAE;AACpC,SAAO,KAAK;GAAE;GAAK;GAAO,YAAY;GAAO,eAAe;GAAO,YAAY;GAAM,CAAC;;AAEvF,QAAO;;AAGRA,sBAAAA,SAAS,oBAAoB;AAC5B,uBAAA,GAAG,oCAAoC;AACtC,wBAAA,aAAO,WAAW,GAAG,CAAC,CAAC,KAAK,EAAE;GAC7B;AAEF,uBAAA,GAAG,0CAA0C;AAC5C,wBAAA,aAAO,WAAW,QAAQ,CAAC,CAAC,KAAK,WAAW,QAAQ,CAAC;GACpD;AAEF,uBAAA,GAAG,oDAAoD;AACtD,wBAAA,aAAO,WAAW,QAAQ,CAAC,CAAC,IAAI,KAAK,WAAW,QAAQ,CAAC;GACxD;AAEF,uBAAA,GAAG,iCAAiC;AAGnC,wBAAA,aAAO,WAFI,wDAEU,CAAC,CAAC,IAAI,KAAK,WADrB,wDACmC,CAAC;GAC9C;EACD;AAEFA,sBAAAA,SAAS,yBAAyB;AACjC,uBAAA,GAAG,iDAAiD;AACnD,wBAAA,aAAO,gBAAgB,KAAA,EAAU,CAAC,CAAC,QAAQ,EAAE,CAAC;GAC7C;AAEF,uBAAA,GAAG,8CAA8C;AAChD,wBAAA,aAAO,gBAAgB,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;GACtC;AAEF,uBAAA,GAAG,uCAAuC;EACzC,MAAM,SAAS,gBAAgB,mBAAmB;AAClD,wBAAA,aAAO,OAAO,CAAC,aAAa,EAAE;AAC9B,wBAAA,aAAO,OAAO,GAAG,CAAC,QAAQ;GACzB,KAAK;GACL,OAAO;GACP,YAAY;GACZ,eAAe;GACf,YAAY;GACZ,CAAC;GACD;AAEF,uBAAA,GAAG,wBAAwB;EAC1B,MAAM,SAAS,gBAAgB,+BAA+B;AAC9D,wBAAA,aAAO,OAAO,CAAC,aAAa,EAAE;AAC9B,wBAAA,aAAO,OAAO,GAAI,IAAI,CAAC,KAAK,MAAM;GACjC;AAEF,uBAAA,GAAG,2BAA2B;AAE7B,wBAAA,aADe,gBAAgB,uBAAuB,CACxC,CAAC,aAAa,EAAE;GAC7B;AAEF,uBAAA,GAAG,gDAAgD;EAClD,MAAM,SAAS,gBACd,iEACA;AACD,wBAAA,aAAO,OAAO,CAAC,aAAa,EAAE;AAC9B,wBAAA,aAAO,OAAO,GAAI,IAAI,CAAC,KAAK,eAAe;AAC3C,wBAAA,aAAO,OAAO,GAAI,MAAM,CAAC,KAAK,oDAAoD;GACjF;AAEF,uBAAA,GAAG,8BAA8B;EAChC,MAAM,SAAS,gBAAgB,aAAa;AAC5C,wBAAA,aAAO,OAAO,CAAC,aAAa,EAAE;AAC9B,wBAAA,aAAO,OAAO,GAAI,IAAI,CAAC,KAAK,YAAY;AACxC,wBAAA,aAAO,OAAO,GAAI,MAAM,CAAC,KAAK,GAAG;GAChC;AAEF,uBAAA,GAAG,oCAAoC;EACtC,MAAM,SAAS,gBAAgB,0BAA0B;AACzD,wBAAA,aAAO,OAAO,CAAC,aAAa,EAAE;AAC9B,wBAAA,aAAO,OAAO,GAAI,IAAI,CAAC,KAAK,QAAQ;GACnC;AAEF,uBAAA,GAAG,+BAA+B;EACjC,MAAM,SAAS,gBAAgB,yBAAuB;AACtD,wBAAA,aAAO,OAAO,GAAI,IAAI,CAAC,KAAK,WAAW;AACvC,wBAAA,aAAO,OAAO,GAAI,MAAM,CAAC,KAAK,gBAAc;GAC3C;EACD;AAEFA,sBAAAA,SAAS,yBAAyB;AACjC,uBAAA,iBAAiB;AAChB,wBAAA,GAAG,iBAAiB;GACnB;AAEF,uBAAA,GAAG,oCAAoC,YAAY;EAClD,MAAM,EAAE,oBAAoB,MAAA,QAAA,SAAA,CAAA,WAAA,QAAM,gBAAA,CAAA;EAClC,MAAM,WAAW,IAAI,iBAAiB;AACtC,wBAAA,aAAO,SAAS,KAAK,CAAC,KAAK,UAAU;AACrC,wBAAA,aAAO,SAAS,GAAG,CAAC,KAAK,UAAU;GAClC;AAEF,uBAAA,GAAG,mDAAmD,YAAY;AACjE,wBAAA,GAAG,WAAW,SAASC,sBAAAA,GAAG,IAAI,CAAC,kCAAkB,IAAI,MAAM,qBAAqB,CAAC,CAAC;EAClF,MAAM,EAAE,oBAAoB,MAAA,QAAA,SAAA,CAAA,WAAA,QAAM,gBAAA,CAAA;EAElC,MAAM,SAAS,MADE,IAAI,iBAAiB,CACR,eAAe;GAC5C,aAAa;GACb,QAAQ;GACR,CAAC;AACF,wBAAA,aAAO,OAAO,GAAG,CAAC,KAAK,MAAM;AAC7B,wBAAA,aAAO,OAAO,MAAM,CAAC,UAAU,qBAAqB;AACpD,wBAAA,GAAG,kBAAkB;GACpB;AAEF,uBAAA,GAAG,4DAA4D,YAAY;AAC1E,wBAAA,GAAG,WACF,SACAA,sBAAAA,GAAG,IAAI,CAAC,kBAAkB;GACzB,IAAI;GACJ,YAAY,QAAQ,QAAQ,aAAW;GACvC,CAAC,CACF;EACD,MAAM,EAAE,oBAAoB,MAAA,QAAA,SAAA,CAAA,WAAA,QAAM,gBAAA,CAAA;AAMlC,wBAAA,cAJe,MADE,IAAI,iBAAiB,CACR,eAAe;GAC5C,aAAa;GACb,QAAQ;GACR,CAAC,EACY,GAAG,CAAC,KAAK,KAAK;AAC5B,wBAAA,GAAG,kBAAkB;GACpB;AAEF,uBAAA,GAAG,kDAAkD,YAAY;AAChE,wBAAA,GAAG,WACF,SACAA,sBAAAA,GAAG,IAAI,CAAC,kBAAkB;GACzB,IAAI;GACJ,YAAY,QAAQ,QAAQ,KAAK;GACjC,CAAC,CACF;EACD,MAAM,EAAE,oBAAoB,MAAA,QAAA,SAAA,CAAA,WAAA,QAAM,gBAAA,CAAA;EAElC,MAAM,SAAS,MADE,IAAI,iBAAiB,CACR,OAAO;GACpC,QAAQ;IAAE,aAAa;IAA+B,QAAQ;IAAY;GAC1E,aAAa;GACb,aAAa;GACb,YAAY;GACZ,CAAC;AACF,wBAAA,aAAO,OAAO,QAAQ,CAAC,KAAK,MAAM;AAClC,wBAAA,aAAO,OAAO,MAAM,CAAC,UAAU,qBAAqB;AACpD,wBAAA,aAAO,OAAO,MAAM,GAAI,OAAO,CAAC,KAAK,QAAQ;AAC7C,wBAAA,GAAG,kBAAkB;GACpB;EACD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { a as describe, i as beforeEach, n as vi, o as it, t as globalExpect } from "../test.CTcmp4Su-BRa7-bTj.mjs";
|
|
2
|
+
//#region src/deployers/coolify.test.ts
|
|
3
|
+
/**
|
|
4
|
+
* Tests for Coolify deployer utility functions and deploy orchestration.
|
|
5
|
+
*
|
|
6
|
+
* We mock `fetch` to simulate Coolify API responses and test the
|
|
7
|
+
* multi-step deployment flow without making real HTTP requests.
|
|
8
|
+
*/
|
|
9
|
+
function hashString(str) {
|
|
10
|
+
let hash = 0;
|
|
11
|
+
for (let i = 0; i < str.length; i++) {
|
|
12
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
13
|
+
hash |= 0;
|
|
14
|
+
}
|
|
15
|
+
return hash;
|
|
16
|
+
}
|
|
17
|
+
function parseEnvContent(envContent) {
|
|
18
|
+
if (!envContent) return [];
|
|
19
|
+
const result = [];
|
|
20
|
+
for (const line of envContent.split("\n")) {
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
23
|
+
const idx = trimmed.indexOf("=");
|
|
24
|
+
if (idx <= 0) continue;
|
|
25
|
+
const key = trimmed.slice(0, idx);
|
|
26
|
+
const value = trimmed.slice(idx + 1);
|
|
27
|
+
result.push({
|
|
28
|
+
key,
|
|
29
|
+
value,
|
|
30
|
+
is_preview: false,
|
|
31
|
+
is_build_time: false,
|
|
32
|
+
is_literal: true
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
describe("hashString", () => {
|
|
38
|
+
it("returns 0 for empty string", () => {
|
|
39
|
+
globalExpect(hashString("")).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
it("returns same hash for same input", () => {
|
|
42
|
+
globalExpect(hashString("hello")).toBe(hashString("hello"));
|
|
43
|
+
});
|
|
44
|
+
it("returns different hash for different input", () => {
|
|
45
|
+
globalExpect(hashString("hello")).not.toBe(hashString("world"));
|
|
46
|
+
});
|
|
47
|
+
it("detects compose changes", () => {
|
|
48
|
+
globalExpect(hashString("version: '3'\nservices:\n redis:\n image: redis:7")).not.toBe(hashString("version: '3'\nservices:\n redis:\n image: redis:8"));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe("parseEnvContent", () => {
|
|
52
|
+
it("returns empty array for undefined input", () => {
|
|
53
|
+
globalExpect(parseEnvContent(void 0)).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
it("returns empty array for empty string", () => {
|
|
56
|
+
globalExpect(parseEnvContent("")).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
it("parses simple key=value pairs", () => {
|
|
59
|
+
const result = parseEnvContent("FOO=bar\nBAZ=qux");
|
|
60
|
+
globalExpect(result).toHaveLength(2);
|
|
61
|
+
globalExpect(result[0]).toEqual({
|
|
62
|
+
key: "FOO",
|
|
63
|
+
value: "bar",
|
|
64
|
+
is_preview: false,
|
|
65
|
+
is_build_time: false,
|
|
66
|
+
is_literal: true
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
it("skips comments", () => {
|
|
70
|
+
const result = parseEnvContent("# This is a comment\nFOO=bar");
|
|
71
|
+
globalExpect(result).toHaveLength(1);
|
|
72
|
+
globalExpect(result[0].key).toBe("FOO");
|
|
73
|
+
});
|
|
74
|
+
it("skips blank lines", () => {
|
|
75
|
+
globalExpect(parseEnvContent("FOO=bar\n\n\nBAZ=qux")).toHaveLength(2);
|
|
76
|
+
});
|
|
77
|
+
it("handles values containing equals signs", () => {
|
|
78
|
+
const result = parseEnvContent("DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=require");
|
|
79
|
+
globalExpect(result).toHaveLength(1);
|
|
80
|
+
globalExpect(result[0].key).toBe("DATABASE_URL");
|
|
81
|
+
globalExpect(result[0].value).toBe("postgres://user:pass@host:5432/db?sslmode=require");
|
|
82
|
+
});
|
|
83
|
+
it("handles empty values", () => {
|
|
84
|
+
const result = parseEnvContent("EMPTY_VAR=");
|
|
85
|
+
globalExpect(result).toHaveLength(1);
|
|
86
|
+
globalExpect(result[0].key).toBe("EMPTY_VAR");
|
|
87
|
+
globalExpect(result[0].value).toBe("");
|
|
88
|
+
});
|
|
89
|
+
it("skips lines without = sign", () => {
|
|
90
|
+
const result = parseEnvContent("INVALID_LINE\nVALID=yes");
|
|
91
|
+
globalExpect(result).toHaveLength(1);
|
|
92
|
+
globalExpect(result[0].key).toBe("VALID");
|
|
93
|
+
});
|
|
94
|
+
it("handles quoted values", () => {
|
|
95
|
+
const result = parseEnvContent("PASSWORD=\"my secret\"");
|
|
96
|
+
globalExpect(result[0].key).toBe("PASSWORD");
|
|
97
|
+
globalExpect(result[0].value).toBe("\"my secret\"");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe("CoolifyDeployer", () => {
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
vi.restoreAllMocks();
|
|
103
|
+
});
|
|
104
|
+
it("can be imported and instantiated", async () => {
|
|
105
|
+
const { CoolifyDeployer } = await import("./coolify.mjs");
|
|
106
|
+
const deployer = new CoolifyDeployer();
|
|
107
|
+
globalExpect(deployer.name).toBe("Coolify");
|
|
108
|
+
globalExpect(deployer.id).toBe("coolify");
|
|
109
|
+
});
|
|
110
|
+
it("testConnection returns error on network failure", async () => {
|
|
111
|
+
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(/* @__PURE__ */ new Error("Connection refused")));
|
|
112
|
+
const { CoolifyDeployer } = await import("./coolify.mjs");
|
|
113
|
+
const result = await new CoolifyDeployer().testConnection({
|
|
114
|
+
instanceUrl: "https://coolify.example.com",
|
|
115
|
+
apiKey: "test-key"
|
|
116
|
+
});
|
|
117
|
+
globalExpect(result.ok).toBe(false);
|
|
118
|
+
globalExpect(result.error).toContain("Connection refused");
|
|
119
|
+
vi.unstubAllGlobals();
|
|
120
|
+
});
|
|
121
|
+
it("testConnection returns ok on successful version response", async () => {
|
|
122
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
123
|
+
ok: true,
|
|
124
|
+
text: () => Promise.resolve("\"v4.0.0\"")
|
|
125
|
+
}));
|
|
126
|
+
const { CoolifyDeployer } = await import("./coolify.mjs");
|
|
127
|
+
globalExpect((await new CoolifyDeployer().testConnection({
|
|
128
|
+
instanceUrl: "https://coolify.example.com",
|
|
129
|
+
apiKey: "test-key"
|
|
130
|
+
})).ok).toBe(true);
|
|
131
|
+
vi.unstubAllGlobals();
|
|
132
|
+
});
|
|
133
|
+
it("deploy returns error when no servers available", async () => {
|
|
134
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
135
|
+
ok: true,
|
|
136
|
+
text: () => Promise.resolve("[]")
|
|
137
|
+
}));
|
|
138
|
+
const { CoolifyDeployer } = await import("./coolify.mjs");
|
|
139
|
+
const result = await new CoolifyDeployer().deploy({
|
|
140
|
+
target: {
|
|
141
|
+
instanceUrl: "https://coolify.example.com",
|
|
142
|
+
apiKey: "test-key"
|
|
143
|
+
},
|
|
144
|
+
projectName: "test-project",
|
|
145
|
+
composeYaml: "version: '3'",
|
|
146
|
+
envContent: ""
|
|
147
|
+
});
|
|
148
|
+
globalExpect(result.success).toBe(false);
|
|
149
|
+
globalExpect(result.error).toContain("No Coolify servers");
|
|
150
|
+
globalExpect(result.steps[0].status).toBe("error");
|
|
151
|
+
vi.unstubAllGlobals();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
//#endregion
|
|
155
|
+
export {};
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=coolify.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coolify.test.mjs","names":[],"sources":["../../src/deployers/coolify.test.ts"],"sourcesContent":["import { beforeEach, describe, expect, it, vi } from \"vitest\";\n\n/**\n * Tests for Coolify deployer utility functions and deploy orchestration.\n *\n * We mock `fetch` to simulate Coolify API responses and test the\n * multi-step deployment flow without making real HTTP requests.\n */\n\n// Inline the utility functions for direct testing\nfunction hashString(str: string) {\n\tlet hash = 0;\n\tfor (let i = 0; i < str.length; i++) {\n\t\thash = (hash << 5) - hash + str.charCodeAt(i);\n\t\thash |= 0;\n\t}\n\treturn hash;\n}\n\nfunction parseEnvContent(envContent?: string) {\n\tif (!envContent) return [];\n\tconst result = [];\n\tfor (const line of envContent.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed || trimmed.startsWith(\"#\")) continue;\n\t\tconst idx = trimmed.indexOf(\"=\");\n\t\tif (idx <= 0) continue;\n\t\tconst key = trimmed.slice(0, idx);\n\t\tconst value = trimmed.slice(idx + 1);\n\t\tresult.push({ key, value, is_preview: false, is_build_time: false, is_literal: true });\n\t}\n\treturn result;\n}\n\ndescribe(\"hashString\", () => {\n\tit(\"returns 0 for empty string\", () => {\n\t\texpect(hashString(\"\")).toBe(0);\n\t});\n\n\tit(\"returns same hash for same input\", () => {\n\t\texpect(hashString(\"hello\")).toBe(hashString(\"hello\"));\n\t});\n\n\tit(\"returns different hash for different input\", () => {\n\t\texpect(hashString(\"hello\")).not.toBe(hashString(\"world\"));\n\t});\n\n\tit(\"detects compose changes\", () => {\n\t\tconst v1 = \"version: '3'\\nservices:\\n redis:\\n image: redis:7\";\n\t\tconst v2 = \"version: '3'\\nservices:\\n redis:\\n image: redis:8\";\n\t\texpect(hashString(v1)).not.toBe(hashString(v2));\n\t});\n});\n\ndescribe(\"parseEnvContent\", () => {\n\tit(\"returns empty array for undefined input\", () => {\n\t\texpect(parseEnvContent(undefined)).toEqual([]);\n\t});\n\n\tit(\"returns empty array for empty string\", () => {\n\t\texpect(parseEnvContent(\"\")).toEqual([]);\n\t});\n\n\tit(\"parses simple key=value pairs\", () => {\n\t\tconst result = parseEnvContent(\"FOO=bar\\nBAZ=qux\");\n\t\texpect(result).toHaveLength(2);\n\t\texpect(result[0]).toEqual({\n\t\t\tkey: \"FOO\",\n\t\t\tvalue: \"bar\",\n\t\t\tis_preview: false,\n\t\t\tis_build_time: false,\n\t\t\tis_literal: true,\n\t\t});\n\t});\n\n\tit(\"skips comments\", () => {\n\t\tconst result = parseEnvContent(\"# This is a comment\\nFOO=bar\");\n\t\texpect(result).toHaveLength(1);\n\t\texpect(result[0]!.key).toBe(\"FOO\");\n\t});\n\n\tit(\"skips blank lines\", () => {\n\t\tconst result = parseEnvContent(\"FOO=bar\\n\\n\\nBAZ=qux\");\n\t\texpect(result).toHaveLength(2);\n\t});\n\n\tit(\"handles values containing equals signs\", () => {\n\t\tconst result = parseEnvContent(\n\t\t\t\"DATABASE_URL=postgres://user:pass@host:5432/db?sslmode=require\",\n\t\t);\n\t\texpect(result).toHaveLength(1);\n\t\texpect(result[0]!.key).toBe(\"DATABASE_URL\");\n\t\texpect(result[0]!.value).toBe(\"postgres://user:pass@host:5432/db?sslmode=require\");\n\t});\n\n\tit(\"handles empty values\", () => {\n\t\tconst result = parseEnvContent(\"EMPTY_VAR=\");\n\t\texpect(result).toHaveLength(1);\n\t\texpect(result[0]!.key).toBe(\"EMPTY_VAR\");\n\t\texpect(result[0]!.value).toBe(\"\");\n\t});\n\n\tit(\"skips lines without = sign\", () => {\n\t\tconst result = parseEnvContent(\"INVALID_LINE\\nVALID=yes\");\n\t\texpect(result).toHaveLength(1);\n\t\texpect(result[0]!.key).toBe(\"VALID\");\n\t});\n\n\tit(\"handles quoted values\", () => {\n\t\tconst result = parseEnvContent('PASSWORD=\"my secret\"');\n\t\texpect(result[0]!.key).toBe(\"PASSWORD\");\n\t\texpect(result[0]!.value).toBe('\"my secret\"');\n\t});\n});\n\ndescribe(\"CoolifyDeployer\", () => {\n\tbeforeEach(() => {\n\t\tvi.restoreAllMocks();\n\t});\n\n\tit(\"can be imported and instantiated\", async () => {\n\t\tconst { CoolifyDeployer } = await import(\"./coolify.js\");\n\t\tconst deployer = new CoolifyDeployer();\n\t\texpect(deployer.name).toBe(\"Coolify\");\n\t\texpect(deployer.id).toBe(\"coolify\");\n\t});\n\n\tit(\"testConnection returns error on network failure\", async () => {\n\t\tvi.stubGlobal(\"fetch\", vi.fn().mockRejectedValue(new Error(\"Connection refused\")));\n\t\tconst { CoolifyDeployer } = await import(\"./coolify.js\");\n\t\tconst deployer = new CoolifyDeployer();\n\t\tconst result = await deployer.testConnection({\n\t\t\tinstanceUrl: \"https://coolify.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(result.ok).toBe(false);\n\t\texpect(result.error).toContain(\"Connection refused\");\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"testConnection returns ok on successful version response\", async () => {\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockResolvedValue({\n\t\t\t\tok: true,\n\t\t\t\ttext: () => Promise.resolve('\"v4.0.0\"'),\n\t\t\t}),\n\t\t);\n\t\tconst { CoolifyDeployer } = await import(\"./coolify.js\");\n\t\tconst deployer = new CoolifyDeployer();\n\t\tconst result = await deployer.testConnection({\n\t\t\tinstanceUrl: \"https://coolify.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(result.ok).toBe(true);\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"deploy returns error when no servers available\", async () => {\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockResolvedValue({\n\t\t\t\tok: true,\n\t\t\t\ttext: () => Promise.resolve(\"[]\"),\n\t\t\t}),\n\t\t);\n\t\tconst { CoolifyDeployer } = await import(\"./coolify.js\");\n\t\tconst deployer = new CoolifyDeployer();\n\t\tconst result = await deployer.deploy({\n\t\t\ttarget: { instanceUrl: \"https://coolify.example.com\", apiKey: \"test-key\" },\n\t\t\tprojectName: \"test-project\",\n\t\t\tcomposeYaml: \"version: '3'\",\n\t\t\tenvContent: \"\",\n\t\t});\n\t\texpect(result.success).toBe(false);\n\t\texpect(result.error).toContain(\"No Coolify servers\");\n\t\texpect(result.steps[0]!.status).toBe(\"error\");\n\t\tvi.unstubAllGlobals();\n\t});\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const require_test_CTcmp4Su = require("../test.CTcmp4Su-BWSPM8ZQ.cjs");
|
|
2
|
+
//#region src/deployers/dokploy.test.ts
|
|
3
|
+
require_test_CTcmp4Su.describe("DokployDeployer", () => {
|
|
4
|
+
require_test_CTcmp4Su.beforeEach(() => {
|
|
5
|
+
require_test_CTcmp4Su.vi.restoreAllMocks();
|
|
6
|
+
});
|
|
7
|
+
require_test_CTcmp4Su.it("can be imported and instantiated", async () => {
|
|
8
|
+
const { DokployDeployer } = await Promise.resolve().then(() => require("./dokploy.cjs"));
|
|
9
|
+
const deployer = new DokployDeployer();
|
|
10
|
+
require_test_CTcmp4Su.globalExpect(deployer.name).toBe("Dokploy");
|
|
11
|
+
require_test_CTcmp4Su.globalExpect(deployer.id).toBe("dokploy");
|
|
12
|
+
});
|
|
13
|
+
require_test_CTcmp4Su.it("testConnection returns error on network failure", async () => {
|
|
14
|
+
require_test_CTcmp4Su.vi.stubGlobal("fetch", require_test_CTcmp4Su.vi.fn().mockRejectedValue(/* @__PURE__ */ new Error("ECONNREFUSED")));
|
|
15
|
+
const { DokployDeployer } = await Promise.resolve().then(() => require("./dokploy.cjs"));
|
|
16
|
+
const result = await new DokployDeployer().testConnection({
|
|
17
|
+
instanceUrl: "https://dokploy.example.com",
|
|
18
|
+
apiKey: "test-key"
|
|
19
|
+
});
|
|
20
|
+
require_test_CTcmp4Su.globalExpect(result.ok).toBe(false);
|
|
21
|
+
require_test_CTcmp4Su.globalExpect(result.error).toContain("ECONNREFUSED");
|
|
22
|
+
require_test_CTcmp4Su.vi.unstubAllGlobals();
|
|
23
|
+
});
|
|
24
|
+
require_test_CTcmp4Su.it("testConnection returns ok when project list succeeds", async () => {
|
|
25
|
+
require_test_CTcmp4Su.vi.stubGlobal("fetch", require_test_CTcmp4Su.vi.fn().mockResolvedValue({
|
|
26
|
+
ok: true,
|
|
27
|
+
text: () => Promise.resolve("[]")
|
|
28
|
+
}));
|
|
29
|
+
const { DokployDeployer } = await Promise.resolve().then(() => require("./dokploy.cjs"));
|
|
30
|
+
require_test_CTcmp4Su.globalExpect((await new DokployDeployer().testConnection({
|
|
31
|
+
instanceUrl: "https://dokploy.example.com",
|
|
32
|
+
apiKey: "test-key"
|
|
33
|
+
})).ok).toBe(true);
|
|
34
|
+
require_test_CTcmp4Su.vi.unstubAllGlobals();
|
|
35
|
+
});
|
|
36
|
+
require_test_CTcmp4Su.it("listServers returns empty array on API failure", async () => {
|
|
37
|
+
require_test_CTcmp4Su.vi.stubGlobal("fetch", require_test_CTcmp4Su.vi.fn().mockRejectedValue(/* @__PURE__ */ new Error("Not found")));
|
|
38
|
+
const { DokployDeployer } = await Promise.resolve().then(() => require("./dokploy.cjs"));
|
|
39
|
+
require_test_CTcmp4Su.globalExpect(await new DokployDeployer().listServers({
|
|
40
|
+
instanceUrl: "https://dokploy.example.com",
|
|
41
|
+
apiKey: "test-key"
|
|
42
|
+
})).toEqual([]);
|
|
43
|
+
require_test_CTcmp4Su.vi.unstubAllGlobals();
|
|
44
|
+
});
|
|
45
|
+
require_test_CTcmp4Su.it("listServers maps server fields correctly", async () => {
|
|
46
|
+
require_test_CTcmp4Su.vi.stubGlobal("fetch", require_test_CTcmp4Su.vi.fn().mockResolvedValue({
|
|
47
|
+
ok: true,
|
|
48
|
+
text: () => Promise.resolve(JSON.stringify([{
|
|
49
|
+
serverId: "s1",
|
|
50
|
+
name: "Production",
|
|
51
|
+
ipAddress: "1.2.3.4"
|
|
52
|
+
}, {
|
|
53
|
+
serverId: "s2",
|
|
54
|
+
name: "Staging",
|
|
55
|
+
ipAddress: "5.6.7.8"
|
|
56
|
+
}]))
|
|
57
|
+
}));
|
|
58
|
+
const { DokployDeployer } = await Promise.resolve().then(() => require("./dokploy.cjs"));
|
|
59
|
+
const servers = await new DokployDeployer().listServers({
|
|
60
|
+
instanceUrl: "https://dokploy.example.com",
|
|
61
|
+
apiKey: "test-key"
|
|
62
|
+
});
|
|
63
|
+
require_test_CTcmp4Su.globalExpect(servers).toHaveLength(2);
|
|
64
|
+
require_test_CTcmp4Su.globalExpect(servers[0]).toEqual({
|
|
65
|
+
id: "s1",
|
|
66
|
+
name: "Production",
|
|
67
|
+
ip: "1.2.3.4"
|
|
68
|
+
});
|
|
69
|
+
require_test_CTcmp4Su.globalExpect(servers[1]).toEqual({
|
|
70
|
+
id: "s2",
|
|
71
|
+
name: "Staging",
|
|
72
|
+
ip: "5.6.7.8"
|
|
73
|
+
});
|
|
74
|
+
require_test_CTcmp4Su.vi.unstubAllGlobals();
|
|
75
|
+
});
|
|
76
|
+
require_test_CTcmp4Su.it("deploy sets error on running step when an API call fails", async () => {
|
|
77
|
+
let callCount = 0;
|
|
78
|
+
require_test_CTcmp4Su.vi.stubGlobal("fetch", require_test_CTcmp4Su.vi.fn().mockImplementation(() => {
|
|
79
|
+
callCount++;
|
|
80
|
+
if (callCount === 1) return Promise.resolve({
|
|
81
|
+
ok: true,
|
|
82
|
+
text: () => Promise.resolve("[]")
|
|
83
|
+
});
|
|
84
|
+
return Promise.resolve({
|
|
85
|
+
ok: false,
|
|
86
|
+
status: 500,
|
|
87
|
+
text: () => Promise.resolve("{\"message\":\"Internal error\"}")
|
|
88
|
+
});
|
|
89
|
+
}));
|
|
90
|
+
const { DokployDeployer } = await Promise.resolve().then(() => require("./dokploy.cjs"));
|
|
91
|
+
const result = await new DokployDeployer().deploy({
|
|
92
|
+
target: {
|
|
93
|
+
instanceUrl: "https://dokploy.example.com",
|
|
94
|
+
apiKey: "key"
|
|
95
|
+
},
|
|
96
|
+
projectName: "test",
|
|
97
|
+
composeYaml: "version: '3'",
|
|
98
|
+
envContent: ""
|
|
99
|
+
});
|
|
100
|
+
require_test_CTcmp4Su.globalExpect(result.success).toBe(false);
|
|
101
|
+
require_test_CTcmp4Su.globalExpect(result.error).toBeDefined();
|
|
102
|
+
require_test_CTcmp4Su.globalExpect(result.steps.find((s) => s.status === "error")).toBeDefined();
|
|
103
|
+
require_test_CTcmp4Su.vi.unstubAllGlobals();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
//#endregion
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=dokploy.test.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dokploy.test.cjs","names":["describe","vi"],"sources":["../../src/deployers/dokploy.test.ts"],"sourcesContent":["import { beforeEach, describe, expect, it, vi } from \"vitest\";\n\ndescribe(\"DokployDeployer\", () => {\n\tbeforeEach(() => {\n\t\tvi.restoreAllMocks();\n\t});\n\n\tit(\"can be imported and instantiated\", async () => {\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\texpect(deployer.name).toBe(\"Dokploy\");\n\t\texpect(deployer.id).toBe(\"dokploy\");\n\t});\n\n\tit(\"testConnection returns error on network failure\", async () => {\n\t\tvi.stubGlobal(\"fetch\", vi.fn().mockRejectedValue(new Error(\"ECONNREFUSED\")));\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst result = await deployer.testConnection({\n\t\t\tinstanceUrl: \"https://dokploy.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(result.ok).toBe(false);\n\t\texpect(result.error).toContain(\"ECONNREFUSED\");\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"testConnection returns ok when project list succeeds\", async () => {\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockResolvedValue({\n\t\t\t\tok: true,\n\t\t\t\ttext: () => Promise.resolve(\"[]\"),\n\t\t\t}),\n\t\t);\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst result = await deployer.testConnection({\n\t\t\tinstanceUrl: \"https://dokploy.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(result.ok).toBe(true);\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"listServers returns empty array on API failure\", async () => {\n\t\tvi.stubGlobal(\"fetch\", vi.fn().mockRejectedValue(new Error(\"Not found\")));\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst servers = await deployer.listServers({\n\t\t\tinstanceUrl: \"https://dokploy.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(servers).toEqual([]);\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"listServers maps server fields correctly\", async () => {\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockResolvedValue({\n\t\t\t\tok: true,\n\t\t\t\ttext: () =>\n\t\t\t\t\tPromise.resolve(\n\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t{ serverId: \"s1\", name: \"Production\", ipAddress: \"1.2.3.4\" },\n\t\t\t\t\t\t\t{ serverId: \"s2\", name: \"Staging\", ipAddress: \"5.6.7.8\" },\n\t\t\t\t\t\t]),\n\t\t\t\t\t),\n\t\t\t}),\n\t\t);\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst servers = await deployer.listServers({\n\t\t\tinstanceUrl: \"https://dokploy.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(servers).toHaveLength(2);\n\t\texpect(servers[0]).toEqual({ id: \"s1\", name: \"Production\", ip: \"1.2.3.4\" });\n\t\texpect(servers[1]).toEqual({ id: \"s2\", name: \"Staging\", ip: \"5.6.7.8\" });\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"deploy sets error on running step when an API call fails\", async () => {\n\t\tlet callCount = 0;\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockImplementation(() => {\n\t\t\t\tcallCount++;\n\t\t\t\tif (callCount === 1) {\n\t\t\t\t\t// project.all succeeds\n\t\t\t\t\treturn Promise.resolve({\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\ttext: () => Promise.resolve(\"[]\"),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// project.create fails\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tok: false,\n\t\t\t\t\tstatus: 500,\n\t\t\t\t\ttext: () => Promise.resolve('{\"message\":\"Internal error\"}'),\n\t\t\t\t});\n\t\t\t}),\n\t\t);\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst result = await deployer.deploy({\n\t\t\ttarget: { instanceUrl: \"https://dokploy.example.com\", apiKey: \"key\" },\n\t\t\tprojectName: \"test\",\n\t\t\tcomposeYaml: \"version: '3'\",\n\t\t\tenvContent: \"\",\n\t\t});\n\t\texpect(result.success).toBe(false);\n\t\texpect(result.error).toBeDefined();\n\t\t// At least one step should be in error state\n\t\tconst errorStep = result.steps.find((s) => s.status === \"error\");\n\t\texpect(errorStep).toBeDefined();\n\t\tvi.unstubAllGlobals();\n\t});\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { a as describe, i as beforeEach, n as vi, o as it, t as globalExpect } from "../test.CTcmp4Su-BRa7-bTj.mjs";
|
|
2
|
+
//#region src/deployers/dokploy.test.ts
|
|
3
|
+
describe("DokployDeployer", () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.restoreAllMocks();
|
|
6
|
+
});
|
|
7
|
+
it("can be imported and instantiated", async () => {
|
|
8
|
+
const { DokployDeployer } = await import("./dokploy.mjs");
|
|
9
|
+
const deployer = new DokployDeployer();
|
|
10
|
+
globalExpect(deployer.name).toBe("Dokploy");
|
|
11
|
+
globalExpect(deployer.id).toBe("dokploy");
|
|
12
|
+
});
|
|
13
|
+
it("testConnection returns error on network failure", async () => {
|
|
14
|
+
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(/* @__PURE__ */ new Error("ECONNREFUSED")));
|
|
15
|
+
const { DokployDeployer } = await import("./dokploy.mjs");
|
|
16
|
+
const result = await new DokployDeployer().testConnection({
|
|
17
|
+
instanceUrl: "https://dokploy.example.com",
|
|
18
|
+
apiKey: "test-key"
|
|
19
|
+
});
|
|
20
|
+
globalExpect(result.ok).toBe(false);
|
|
21
|
+
globalExpect(result.error).toContain("ECONNREFUSED");
|
|
22
|
+
vi.unstubAllGlobals();
|
|
23
|
+
});
|
|
24
|
+
it("testConnection returns ok when project list succeeds", async () => {
|
|
25
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
26
|
+
ok: true,
|
|
27
|
+
text: () => Promise.resolve("[]")
|
|
28
|
+
}));
|
|
29
|
+
const { DokployDeployer } = await import("./dokploy.mjs");
|
|
30
|
+
globalExpect((await new DokployDeployer().testConnection({
|
|
31
|
+
instanceUrl: "https://dokploy.example.com",
|
|
32
|
+
apiKey: "test-key"
|
|
33
|
+
})).ok).toBe(true);
|
|
34
|
+
vi.unstubAllGlobals();
|
|
35
|
+
});
|
|
36
|
+
it("listServers returns empty array on API failure", async () => {
|
|
37
|
+
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(/* @__PURE__ */ new Error("Not found")));
|
|
38
|
+
const { DokployDeployer } = await import("./dokploy.mjs");
|
|
39
|
+
globalExpect(await new DokployDeployer().listServers({
|
|
40
|
+
instanceUrl: "https://dokploy.example.com",
|
|
41
|
+
apiKey: "test-key"
|
|
42
|
+
})).toEqual([]);
|
|
43
|
+
vi.unstubAllGlobals();
|
|
44
|
+
});
|
|
45
|
+
it("listServers maps server fields correctly", async () => {
|
|
46
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
|
|
47
|
+
ok: true,
|
|
48
|
+
text: () => Promise.resolve(JSON.stringify([{
|
|
49
|
+
serverId: "s1",
|
|
50
|
+
name: "Production",
|
|
51
|
+
ipAddress: "1.2.3.4"
|
|
52
|
+
}, {
|
|
53
|
+
serverId: "s2",
|
|
54
|
+
name: "Staging",
|
|
55
|
+
ipAddress: "5.6.7.8"
|
|
56
|
+
}]))
|
|
57
|
+
}));
|
|
58
|
+
const { DokployDeployer } = await import("./dokploy.mjs");
|
|
59
|
+
const servers = await new DokployDeployer().listServers({
|
|
60
|
+
instanceUrl: "https://dokploy.example.com",
|
|
61
|
+
apiKey: "test-key"
|
|
62
|
+
});
|
|
63
|
+
globalExpect(servers).toHaveLength(2);
|
|
64
|
+
globalExpect(servers[0]).toEqual({
|
|
65
|
+
id: "s1",
|
|
66
|
+
name: "Production",
|
|
67
|
+
ip: "1.2.3.4"
|
|
68
|
+
});
|
|
69
|
+
globalExpect(servers[1]).toEqual({
|
|
70
|
+
id: "s2",
|
|
71
|
+
name: "Staging",
|
|
72
|
+
ip: "5.6.7.8"
|
|
73
|
+
});
|
|
74
|
+
vi.unstubAllGlobals();
|
|
75
|
+
});
|
|
76
|
+
it("deploy sets error on running step when an API call fails", async () => {
|
|
77
|
+
let callCount = 0;
|
|
78
|
+
vi.stubGlobal("fetch", vi.fn().mockImplementation(() => {
|
|
79
|
+
callCount++;
|
|
80
|
+
if (callCount === 1) return Promise.resolve({
|
|
81
|
+
ok: true,
|
|
82
|
+
text: () => Promise.resolve("[]")
|
|
83
|
+
});
|
|
84
|
+
return Promise.resolve({
|
|
85
|
+
ok: false,
|
|
86
|
+
status: 500,
|
|
87
|
+
text: () => Promise.resolve("{\"message\":\"Internal error\"}")
|
|
88
|
+
});
|
|
89
|
+
}));
|
|
90
|
+
const { DokployDeployer } = await import("./dokploy.mjs");
|
|
91
|
+
const result = await new DokployDeployer().deploy({
|
|
92
|
+
target: {
|
|
93
|
+
instanceUrl: "https://dokploy.example.com",
|
|
94
|
+
apiKey: "key"
|
|
95
|
+
},
|
|
96
|
+
projectName: "test",
|
|
97
|
+
composeYaml: "version: '3'",
|
|
98
|
+
envContent: ""
|
|
99
|
+
});
|
|
100
|
+
globalExpect(result.success).toBe(false);
|
|
101
|
+
globalExpect(result.error).toBeDefined();
|
|
102
|
+
globalExpect(result.steps.find((s) => s.status === "error")).toBeDefined();
|
|
103
|
+
vi.unstubAllGlobals();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
//#endregion
|
|
107
|
+
export {};
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=dokploy.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dokploy.test.mjs","names":[],"sources":["../../src/deployers/dokploy.test.ts"],"sourcesContent":["import { beforeEach, describe, expect, it, vi } from \"vitest\";\n\ndescribe(\"DokployDeployer\", () => {\n\tbeforeEach(() => {\n\t\tvi.restoreAllMocks();\n\t});\n\n\tit(\"can be imported and instantiated\", async () => {\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\texpect(deployer.name).toBe(\"Dokploy\");\n\t\texpect(deployer.id).toBe(\"dokploy\");\n\t});\n\n\tit(\"testConnection returns error on network failure\", async () => {\n\t\tvi.stubGlobal(\"fetch\", vi.fn().mockRejectedValue(new Error(\"ECONNREFUSED\")));\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst result = await deployer.testConnection({\n\t\t\tinstanceUrl: \"https://dokploy.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(result.ok).toBe(false);\n\t\texpect(result.error).toContain(\"ECONNREFUSED\");\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"testConnection returns ok when project list succeeds\", async () => {\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockResolvedValue({\n\t\t\t\tok: true,\n\t\t\t\ttext: () => Promise.resolve(\"[]\"),\n\t\t\t}),\n\t\t);\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst result = await deployer.testConnection({\n\t\t\tinstanceUrl: \"https://dokploy.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(result.ok).toBe(true);\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"listServers returns empty array on API failure\", async () => {\n\t\tvi.stubGlobal(\"fetch\", vi.fn().mockRejectedValue(new Error(\"Not found\")));\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst servers = await deployer.listServers({\n\t\t\tinstanceUrl: \"https://dokploy.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(servers).toEqual([]);\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"listServers maps server fields correctly\", async () => {\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockResolvedValue({\n\t\t\t\tok: true,\n\t\t\t\ttext: () =>\n\t\t\t\t\tPromise.resolve(\n\t\t\t\t\t\tJSON.stringify([\n\t\t\t\t\t\t\t{ serverId: \"s1\", name: \"Production\", ipAddress: \"1.2.3.4\" },\n\t\t\t\t\t\t\t{ serverId: \"s2\", name: \"Staging\", ipAddress: \"5.6.7.8\" },\n\t\t\t\t\t\t]),\n\t\t\t\t\t),\n\t\t\t}),\n\t\t);\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst servers = await deployer.listServers({\n\t\t\tinstanceUrl: \"https://dokploy.example.com\",\n\t\t\tapiKey: \"test-key\",\n\t\t});\n\t\texpect(servers).toHaveLength(2);\n\t\texpect(servers[0]).toEqual({ id: \"s1\", name: \"Production\", ip: \"1.2.3.4\" });\n\t\texpect(servers[1]).toEqual({ id: \"s2\", name: \"Staging\", ip: \"5.6.7.8\" });\n\t\tvi.unstubAllGlobals();\n\t});\n\n\tit(\"deploy sets error on running step when an API call fails\", async () => {\n\t\tlet callCount = 0;\n\t\tvi.stubGlobal(\n\t\t\t\"fetch\",\n\t\t\tvi.fn().mockImplementation(() => {\n\t\t\t\tcallCount++;\n\t\t\t\tif (callCount === 1) {\n\t\t\t\t\t// project.all succeeds\n\t\t\t\t\treturn Promise.resolve({\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\ttext: () => Promise.resolve(\"[]\"),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// project.create fails\n\t\t\t\treturn Promise.resolve({\n\t\t\t\t\tok: false,\n\t\t\t\t\tstatus: 500,\n\t\t\t\t\ttext: () => Promise.resolve('{\"message\":\"Internal error\"}'),\n\t\t\t\t});\n\t\t\t}),\n\t\t);\n\t\tconst { DokployDeployer } = await import(\"./dokploy.js\");\n\t\tconst deployer = new DokployDeployer();\n\t\tconst result = await deployer.deploy({\n\t\t\ttarget: { instanceUrl: \"https://dokploy.example.com\", apiKey: \"key\" },\n\t\t\tprojectName: \"test\",\n\t\t\tcomposeYaml: \"version: '3'\",\n\t\t\tenvContent: \"\",\n\t\t});\n\t\texpect(result.success).toBe(false);\n\t\texpect(result.error).toBeDefined();\n\t\t// At least one step should be in error state\n\t\tconst errorStep = result.steps.find((s) => s.status === \"error\");\n\t\texpect(errorStep).toBeDefined();\n\t\tvi.unstubAllGlobals();\n\t});\n});\n"],"mappings":"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"}
|