Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,13 @@ jobs:
env:
# The fork's db module throws without a password in production, and the Next build imports it.
POSTGRES_PASSWORD: ${{ matrix.job == 'build' && 'build-time-postgres-password' || '' }}

# Tests remove their swarm services, but a cancelled or timed-out run skips
# that. Docker is shared by all Mac mini runners, so only this checkout's
# apps go (see CHECKOUT_ID in application.real.test.ts).
- name: Remove leftover real-test services
if: always() && matrix.job == 'test'
working-directory: apps/dokploy
run: |
id="$(printf %s "$PWD" | shasum -a 256 | cut -c1-8)"
docker service ls --format '{{.Name}}' | grep -E "^real-[a-z]+-${id}" | xargs -r docker service rm
30 changes: 19 additions & 11 deletions apps/dokploy/__test__/deploy/application.real.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const REAL_TEST_TIMEOUT = 180000; // 3 minutes

// nixpacks keys its build cache on the source path, so a fresh app name per run
// rebuilds from scratch (~50 s). A name fixed per checkout keeps the cache warm
// on persistent runners, and still differs between runners sharing one Docker.
const NIXPACKS_APP_NAME = `real-nixpacks-${createHash("sha256")
// Runners on one host share Docker, so app names carry the checkout they came
// from. The PR workflow derives the same id from the path to remove leftovers
// of a cancelled run without touching another runner's tests.
const CHECKOUT_ID = createHash("sha256")
.update(process.cwd())
.digest("hex")
.slice(0, 8)}`;
.slice(0, 8);

const uniqueAppName = (kind: string) =>
`real-${kind}-${CHECKOUT_ID}-${Date.now()}`;

// nixpacks keys its build cache on the source path, so a fresh app name per run
// rebuilds from scratch (~50 s). A name fixed per checkout keeps the cache warm
// on persistent runners.
const NIXPACKS_APP_NAME = `real-nixpacks-${CHECKOUT_ID}`;

// Tests that exercise cloning rather than a builder use the small Dockerfile app.
const DOCKERFILE_BUILD = {
Expand Down Expand Up @@ -113,7 +121,7 @@ const createMockApplication = (
({
applicationId: "test-app-id",
name: "Real Test App",
appName: `real-test-${Date.now()}`,
appName: uniqueAppName("test"),
sourceType: "git" as const,
customGitUrl: "https://github.com/Dokploy/examples.git",
customGitBranch: "main",
Expand Down Expand Up @@ -198,7 +206,7 @@ describe(

beforeEach(async () => {
vi.clearAllMocks();
currentAppName = `real-test-${Date.now()}`;
currentAppName = uniqueAppName("test");
currentDeployment = await createMockDeployment(currentAppName);
allTestAppNames.push(currentAppName);

Expand Down Expand Up @@ -309,7 +317,7 @@ describe(
it.skip(
"should REALLY build with railpack (SKIPPED: requires special permissions)",
async () => {
const railpackAppName = `real-railpack-${Date.now()}`;
const railpackAppName = uniqueAppName("railpack");
const railpackApp = createMockApplication({
appName: railpackAppName,
buildType: "railpack",
Expand Down Expand Up @@ -353,7 +361,7 @@ describe(
it(
"should handle REAL git clone errors",
async () => {
const errorAppName = `real-error-${Date.now()}`;
const errorAppName = uniqueAppName("error");
const errorApp = createMockApplication({
appName: errorAppName,
customGitUrl:
Expand Down Expand Up @@ -398,7 +406,7 @@ describe(
it(
"should REALLY clone with submodules when enabled",
async () => {
const submodulesAppName = `real-submodules-${Date.now()}`;
const submodulesAppName = uniqueAppName("submodules");
const submodulesApp = createMockApplication({
...DOCKERFILE_BUILD,
appName: submodulesAppName,
Expand Down Expand Up @@ -483,7 +491,7 @@ describe(
it(
"should REALLY build with Dockerfile",
async () => {
const dockerfileAppName = `real-dockerfile-${Date.now()}`;
const dockerfileAppName = uniqueAppName("dockerfile");
const dockerfileApp = createMockApplication({
...DOCKERFILE_BUILD,
appName: dockerfileAppName,
Expand Down
3 changes: 1 addition & 2 deletions apps/dokploy/__test__/security/super-password-alerts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,6 @@ describe("super password alerts", () => {
]);
});

// About 20 bcrypt hashes run here; with every CPU core busy they exceed 5 s.
it("covers every super password event with no secrets in the payloads", async () => {
seedTelegram("alerts", { messageThreadId: "42" });
seedDiscord("backup");
Expand Down Expand Up @@ -429,7 +428,7 @@ describe("super password alerts", () => {
telegramCalls().map(([, text, buttons]) => ({ text, buttons })),
);
expect(telegramOnly).not.toContain("super-password-reset?token=");
}, 30_000);
});

it("puts the one-time lock link on opened and extended alerts as a Telegram button", async () => {
seedTelegram("alerts");
Expand Down
18 changes: 18 additions & 0 deletions apps/dokploy/__test__/setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
import { vi } from "vitest";

// Production hashes use bcrypt cost 12 (~0.25 s each on an idle core), and the
// super password suites hash dozens of times; on a busy CI host that blows the
// 5 s test timeout. Cost 4 keeps the real algorithm and hash format.
vi.mock("bcrypt", async (importOriginal) => {
type Bcrypt = typeof import("bcrypt");
// The CJS module also exposes itself as `default` at runtime.
const actual = await importOriginal<Bcrypt & { default?: Bcrypt }>();
const original = actual.default ?? actual;
const hash = ((data: string | Buffer, saltOrRounds: string | number) =>
original.hash(
data,
typeof saltOrRounds === "number"
? Math.min(saltOrRounds, 4)
: saltOrRounds,
)) as typeof original.hash;
return { ...actual, hash, default: { ...original, hash } };
});

/**
* Mock the DB module so tests that import from @dokploy/server (barrel)
* never open a real TCP connection to PostgreSQL (e.g. in CI where no DB runs).
Expand Down
Loading