Skip to content

Commit cc1620b

Browse files
authored
Merge pull request #3133 from Dokploy/feat/add-test-for-deployments
test: add e2e tests for deployments (nixpacks, dockerfile, git)
2 parents b23ba17 + 27b605f commit cc1620b

File tree

10 files changed

+798
-7
lines changed

10 files changed

+798
-7
lines changed

.github/workflows/pull-request.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,32 @@ jobs:
2020
with:
2121
node-version: 20.16.0
2222
cache: "pnpm"
23+
24+
- name: Install Nixpacks
25+
if: matrix.job == 'test'
26+
run: |
27+
export NIXPACKS_VERSION=1.39.0
28+
curl -sSL https://nixpacks.com/install.sh | bash
29+
echo "Nixpacks installed $NIXPACKS_VERSION"
30+
31+
- name: Install Railpack
32+
if: matrix.job == 'test'
33+
run: |
34+
export RAILPACK_VERSION=0.15.0
35+
curl -sSL https://railpack.com/install.sh | bash
36+
echo "Railpack installed $RAILPACK_VERSION"
37+
38+
- name: Add build tools to PATH
39+
if: matrix.job == 'test'
40+
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
41+
42+
- name: Initialize Docker Swarm
43+
if: matrix.job == 'test'
44+
run: |
45+
docker swarm init
46+
docker network create --driver overlay dokploy-network || true
47+
echo "✅ Docker Swarm initialized"
48+
2349
- run: pnpm install --frozen-lockfile
2450
- run: pnpm server:build
2551
- run: pnpm ${{ matrix.job }}
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import * as adminService from "@dokploy/server/services/admin";
2+
import * as applicationService from "@dokploy/server/services/application";
3+
import { deployApplication } from "@dokploy/server/services/application";
4+
import * as deploymentService from "@dokploy/server/services/deployment";
5+
import * as builders from "@dokploy/server/utils/builders";
6+
import * as notifications from "@dokploy/server/utils/notifications/build-success";
7+
import * as execProcess from "@dokploy/server/utils/process/execAsync";
8+
import * as gitProvider from "@dokploy/server/utils/providers/git";
9+
import { beforeEach, describe, expect, it, vi } from "vitest";
10+
11+
vi.mock("@dokploy/server/db", () => {
12+
const createChainableMock = (): any => {
13+
const chain = {
14+
set: vi.fn(() => chain),
15+
where: vi.fn(() => chain),
16+
returning: vi.fn().mockResolvedValue([{}] as any),
17+
} as any;
18+
return chain;
19+
};
20+
21+
return {
22+
db: {
23+
select: vi.fn(),
24+
insert: vi.fn(),
25+
update: vi.fn(() => createChainableMock()),
26+
delete: vi.fn(),
27+
query: {
28+
applications: {
29+
findFirst: vi.fn(),
30+
},
31+
},
32+
},
33+
};
34+
});
35+
36+
vi.mock("@dokploy/server/services/application", async () => {
37+
const actual = await vi.importActual<
38+
typeof import("@dokploy/server/services/application")
39+
>("@dokploy/server/services/application");
40+
return {
41+
...actual,
42+
findApplicationById: vi.fn(),
43+
updateApplicationStatus: vi.fn(),
44+
};
45+
});
46+
47+
vi.mock("@dokploy/server/services/admin", () => ({
48+
getDokployUrl: vi.fn(),
49+
}));
50+
51+
vi.mock("@dokploy/server/services/deployment", () => ({
52+
createDeployment: vi.fn(),
53+
updateDeploymentStatus: vi.fn(),
54+
updateDeployment: vi.fn(),
55+
}));
56+
57+
vi.mock("@dokploy/server/utils/providers/git", async () => {
58+
const actual = await vi.importActual<
59+
typeof import("@dokploy/server/utils/providers/git")
60+
>("@dokploy/server/utils/providers/git");
61+
return {
62+
...actual,
63+
getGitCommitInfo: vi.fn(),
64+
};
65+
});
66+
67+
vi.mock("@dokploy/server/utils/process/execAsync", () => ({
68+
execAsync: vi.fn(),
69+
ExecError: class ExecError extends Error {},
70+
}));
71+
72+
vi.mock("@dokploy/server/utils/builders", async () => {
73+
const actual = await vi.importActual<
74+
typeof import("@dokploy/server/utils/builders")
75+
>("@dokploy/server/utils/builders");
76+
return {
77+
...actual,
78+
mechanizeDockerContainer: vi.fn(),
79+
getBuildCommand: vi.fn(),
80+
};
81+
});
82+
83+
vi.mock("@dokploy/server/utils/notifications/build-success", () => ({
84+
sendBuildSuccessNotifications: vi.fn(),
85+
}));
86+
87+
vi.mock("@dokploy/server/utils/notifications/build-error", () => ({
88+
sendBuildErrorNotifications: vi.fn(),
89+
}));
90+
91+
vi.mock("@dokploy/server/services/rollbacks", () => ({
92+
createRollback: vi.fn(),
93+
}));
94+
95+
import { db } from "@dokploy/server/db";
96+
import { cloneGitRepository } from "@dokploy/server/utils/providers/git";
97+
98+
const createMockApplication = (overrides = {}) => ({
99+
applicationId: "test-app-id",
100+
name: "Test App",
101+
appName: "test-app",
102+
sourceType: "git" as const,
103+
customGitUrl: "https://github.com/Dokploy/examples.git",
104+
customGitBranch: "main",
105+
customGitSSHKeyId: null,
106+
buildType: "nixpacks" as const,
107+
buildPath: "/astro",
108+
env: "NODE_ENV=production",
109+
serverId: null,
110+
rollbackActive: false,
111+
enableSubmodules: false,
112+
environmentId: "env-id",
113+
environment: {
114+
projectId: "project-id",
115+
env: "",
116+
name: "production",
117+
project: {
118+
name: "Test Project",
119+
organizationId: "org-id",
120+
env: "",
121+
},
122+
},
123+
domains: [],
124+
...overrides,
125+
});
126+
127+
const createMockDeployment = () => ({
128+
deploymentId: "deployment-id",
129+
logPath: "/tmp/test-deployment.log",
130+
});
131+
132+
describe("deployApplication - Command Generation Tests", () => {
133+
beforeEach(() => {
134+
vi.clearAllMocks();
135+
vi.mocked(db.query.applications.findFirst).mockResolvedValue(
136+
createMockApplication() as any,
137+
);
138+
vi.mocked(applicationService.findApplicationById).mockResolvedValue(
139+
createMockApplication() as any,
140+
);
141+
vi.mocked(adminService.getDokployUrl).mockResolvedValue(
142+
"http://localhost:3000",
143+
);
144+
vi.mocked(deploymentService.createDeployment).mockResolvedValue(
145+
createMockDeployment() as any,
146+
);
147+
vi.mocked(execProcess.execAsync).mockResolvedValue({
148+
stdout: "",
149+
stderr: "",
150+
} as any);
151+
vi.mocked(builders.mechanizeDockerContainer).mockResolvedValue(
152+
undefined as any,
153+
);
154+
vi.mocked(deploymentService.updateDeploymentStatus).mockResolvedValue(
155+
undefined as any,
156+
);
157+
vi.mocked(applicationService.updateApplicationStatus).mockResolvedValue(
158+
{} as any,
159+
);
160+
vi.mocked(notifications.sendBuildSuccessNotifications).mockResolvedValue(
161+
undefined as any,
162+
);
163+
vi.mocked(gitProvider.getGitCommitInfo).mockResolvedValue({
164+
message: "test commit",
165+
hash: "abc123",
166+
});
167+
vi.mocked(deploymentService.updateDeployment).mockResolvedValue({} as any);
168+
});
169+
170+
it("should generate correct git clone command for astro example", async () => {
171+
const app = createMockApplication();
172+
const command = await cloneGitRepository(app);
173+
console.log(command);
174+
175+
expect(command).toContain("https://github.com/Dokploy/examples.git");
176+
expect(command).not.toContain("--recurse-submodules");
177+
expect(command).toContain("--branch main");
178+
expect(command).toContain("--depth 1");
179+
expect(command).toContain("git clone");
180+
});
181+
182+
it("should generate git clone with submodules when enabled", async () => {
183+
const app = createMockApplication({ enableSubmodules: true });
184+
const command = await cloneGitRepository(app);
185+
186+
expect(command).toContain("--recurse-submodules");
187+
expect(command).toContain("https://github.com/Dokploy/examples.git");
188+
});
189+
190+
it("should verify nixpacks command is called with correct app", async () => {
191+
const mockNixpacksCommand = "nixpacks build /path/to/app --name test-app";
192+
vi.mocked(builders.getBuildCommand).mockReturnValue(mockNixpacksCommand);
193+
194+
await deployApplication({
195+
applicationId: "test-app-id",
196+
titleLog: "Test deployment",
197+
descriptionLog: "",
198+
});
199+
200+
expect(builders.getBuildCommand).toHaveBeenCalledWith(
201+
expect.objectContaining({
202+
buildType: "nixpacks",
203+
customGitUrl: "https://github.com/Dokploy/examples.git",
204+
buildPath: "/astro",
205+
}),
206+
);
207+
208+
expect(execProcess.execAsync).toHaveBeenCalledWith(
209+
expect.stringContaining("nixpacks build"),
210+
);
211+
});
212+
213+
it("should verify railpack command includes correct parameters", async () => {
214+
const mockApp = createMockApplication({ buildType: "railpack" });
215+
vi.mocked(db.query.applications.findFirst).mockResolvedValue(
216+
mockApp as any,
217+
);
218+
vi.mocked(applicationService.findApplicationById).mockResolvedValue(
219+
mockApp as any,
220+
);
221+
222+
const mockRailpackCommand = "railpack prepare /path/to/app";
223+
vi.mocked(builders.getBuildCommand).mockReturnValue(mockRailpackCommand);
224+
225+
await deployApplication({
226+
applicationId: "test-app-id",
227+
titleLog: "Railpack test",
228+
descriptionLog: "",
229+
});
230+
231+
expect(builders.getBuildCommand).toHaveBeenCalledWith(
232+
expect.objectContaining({
233+
buildType: "railpack",
234+
}),
235+
);
236+
237+
expect(execProcess.execAsync).toHaveBeenCalledWith(
238+
expect.stringContaining("railpack prepare"),
239+
);
240+
});
241+
242+
it("should execute commands in correct order", async () => {
243+
const mockNixpacksCommand = "nixpacks build";
244+
vi.mocked(builders.getBuildCommand).mockReturnValue(mockNixpacksCommand);
245+
246+
await deployApplication({
247+
applicationId: "test-app-id",
248+
titleLog: "Test",
249+
descriptionLog: "",
250+
});
251+
252+
const execCalls = vi.mocked(execProcess.execAsync).mock.calls;
253+
expect(execCalls.length).toBeGreaterThan(0);
254+
255+
const fullCommand = execCalls[0]?.[0];
256+
expect(fullCommand).toContain("set -e");
257+
expect(fullCommand).toContain("git clone");
258+
expect(fullCommand).toContain("nixpacks build");
259+
});
260+
261+
it("should include log redirection in command", async () => {
262+
const mockCommand = "nixpacks build";
263+
vi.mocked(builders.getBuildCommand).mockReturnValue(mockCommand);
264+
265+
await deployApplication({
266+
applicationId: "test-app-id",
267+
titleLog: "Test",
268+
descriptionLog: "",
269+
});
270+
271+
const execCalls = vi.mocked(execProcess.execAsync).mock.calls;
272+
const fullCommand = execCalls[0]?.[0];
273+
274+
expect(fullCommand).toContain(">> /tmp/test-deployment.log 2>&1");
275+
});
276+
});

0 commit comments

Comments
 (0)