mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-01 03:55:22 +02:00
- Replaced `forwardAuthProviderId` with `forwardAuthEnabled` in the domain schema to simplify the configuration of forward authentication. - Updated related tests to reflect this change, ensuring consistency across the application. - Introduced a new SQL migration to create the `forward_auth_settings` table for managing authentication domains and their configurations. This refactor enhances the clarity and maintainability of the forward authentication logic within the application.
219 lines
6.8 KiB
TypeScript
219 lines
6.8 KiB
TypeScript
import type { Domain } from "@dokploy/server";
|
|
import { createDomainLabels } from "@dokploy/server";
|
|
import { describe, expect, it } from "vitest";
|
|
import { parse, stringify } from "yaml";
|
|
|
|
/**
|
|
* Regression tests for Traefik Host rule label format.
|
|
*
|
|
* These tests verify that the Host rule is generated with the correct format:
|
|
* - Host(`domain.com`) - with opening and closing parentheses
|
|
* - Host(`domain.com`) && PathPrefix(`/path`) - for path-based routing
|
|
*
|
|
* Issue: https://github.com/Dokploy/dokploy/issues/3161
|
|
* The bug caused Host rules to be malformed as Host`domain.com`)
|
|
* (missing opening parenthesis) which broke all domain routing.
|
|
*/
|
|
describe("Host rule format regression tests", () => {
|
|
const baseDomain: Domain = {
|
|
host: "example.com",
|
|
port: 8080,
|
|
https: false,
|
|
uniqueConfigKey: 1,
|
|
customCertResolver: null,
|
|
certificateType: "none",
|
|
applicationId: "",
|
|
composeId: "",
|
|
domainType: "compose",
|
|
serviceName: "test-app",
|
|
domainId: "",
|
|
path: "/",
|
|
createdAt: "",
|
|
previewDeploymentId: "",
|
|
internalPath: "/",
|
|
stripPath: false,
|
|
customEntrypoint: null,
|
|
middlewares: null,
|
|
forwardAuthEnabled: false,
|
|
};
|
|
|
|
describe("Host rule format validation", () => {
|
|
it("should generate Host rule with correct parentheses format", async () => {
|
|
const labels = await createDomainLabels("test-app", baseDomain, "web");
|
|
const ruleLabel = labels.find((l) => l.includes(".rule="));
|
|
|
|
expect(ruleLabel).toBeDefined();
|
|
// Verify exact format: Host(`domain`)
|
|
expect(ruleLabel).toMatch(/Host\(`[^`]+`\)/);
|
|
// Ensure opening parenthesis is present after Host
|
|
expect(ruleLabel).toContain("Host(`example.com`)");
|
|
// Ensure it does NOT have the malformed format
|
|
expect(ruleLabel).not.toMatch(/Host`[^`]+`\)/);
|
|
});
|
|
|
|
it("should generate PathPrefix with correct parentheses format", async () => {
|
|
const labels = await createDomainLabels(
|
|
"test-app",
|
|
{ ...baseDomain, path: "/api" },
|
|
"web",
|
|
);
|
|
const ruleLabel = labels.find((l) => l.includes(".rule="));
|
|
|
|
expect(ruleLabel).toBeDefined();
|
|
// Verify PathPrefix format
|
|
expect(ruleLabel).toMatch(/PathPrefix\(`[^`]+`\)/);
|
|
expect(ruleLabel).toContain("PathPrefix(`/api`)");
|
|
// Ensure opening parenthesis is present
|
|
expect(ruleLabel).not.toMatch(/PathPrefix`[^`]+`\)/);
|
|
});
|
|
|
|
it("should generate combined Host and PathPrefix with correct format", async () => {
|
|
const labels = await createDomainLabels(
|
|
"test-app",
|
|
{ ...baseDomain, path: "/api/v1" },
|
|
"websecure",
|
|
);
|
|
const ruleLabel = labels.find((l) => l.includes(".rule="));
|
|
|
|
expect(ruleLabel).toBeDefined();
|
|
expect(ruleLabel).toBe(
|
|
"traefik.http.routers.test-app-1-websecure.rule=Host(`example.com`) && PathPrefix(`/api/v1`)",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("YAML serialization preserves Host rule format", () => {
|
|
it("should preserve Host rule format through YAML stringify/parse", async () => {
|
|
const labels = await createDomainLabels("test-app", baseDomain, "web");
|
|
const ruleLabel = labels.find((l) => l.includes(".rule="));
|
|
|
|
// Simulate compose file structure
|
|
const composeSpec = {
|
|
services: {
|
|
myapp: {
|
|
image: "nginx",
|
|
labels: labels,
|
|
},
|
|
},
|
|
};
|
|
|
|
// Stringify to YAML
|
|
const yamlOutput = stringify(composeSpec, { lineWidth: 1000 });
|
|
|
|
// Parse back
|
|
const parsed = parse(yamlOutput) as typeof composeSpec;
|
|
const parsedRuleLabel = parsed.services.myapp.labels.find((l: string) =>
|
|
l.includes(".rule="),
|
|
);
|
|
|
|
// Verify format is preserved
|
|
expect(parsedRuleLabel).toBe(ruleLabel);
|
|
expect(parsedRuleLabel).toContain("Host(`example.com`)");
|
|
expect(parsedRuleLabel).not.toMatch(/Host`[^`]+`\)/);
|
|
});
|
|
|
|
it("should preserve complex rule format through YAML serialization", async () => {
|
|
const labels = await createDomainLabels(
|
|
"test-app",
|
|
{ ...baseDomain, path: "/api", https: true },
|
|
"websecure",
|
|
);
|
|
|
|
const composeSpec = {
|
|
services: {
|
|
myapp: {
|
|
labels: labels,
|
|
},
|
|
},
|
|
};
|
|
|
|
const yamlOutput = stringify(composeSpec, { lineWidth: 1000 });
|
|
const parsed = parse(yamlOutput) as typeof composeSpec;
|
|
const parsedRuleLabel = parsed.services.myapp.labels.find((l: string) =>
|
|
l.includes(".rule="),
|
|
);
|
|
|
|
expect(parsedRuleLabel).toContain(
|
|
"Host(`example.com`) && PathPrefix(`/api`)",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Edge cases for domain names", () => {
|
|
const domainCases = [
|
|
{ name: "simple domain", host: "example.com" },
|
|
{ name: "subdomain", host: "app.example.com" },
|
|
{ name: "deep subdomain", host: "api.v1.app.example.com" },
|
|
{ name: "numeric domain", host: "123.example.com" },
|
|
{ name: "hyphenated domain", host: "my-app.example-host.com" },
|
|
{ name: "localhost", host: "localhost" },
|
|
{ name: "IP address style", host: "192.168.1.100" },
|
|
];
|
|
|
|
for (const { name, host } of domainCases) {
|
|
it(`should generate correct Host rule for ${name}: ${host}`, async () => {
|
|
const labels = await createDomainLabels(
|
|
"test-app",
|
|
{ ...baseDomain, host },
|
|
"web",
|
|
);
|
|
const ruleLabel = labels.find((l) => l.includes(".rule="));
|
|
|
|
expect(ruleLabel).toBeDefined();
|
|
expect(ruleLabel).toContain(`Host(\`${host}\`)`);
|
|
// Verify parenthesis is present
|
|
expect(ruleLabel).toMatch(
|
|
new RegExp(`Host\\(\\\`${host.replace(/\./g, "\\.")}\\\`\\)`),
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe("Multiple domains scenario", () => {
|
|
it("should generate correct format for both web and websecure entrypoints", async () => {
|
|
const webLabels = await createDomainLabels("test-app", baseDomain, "web");
|
|
const websecureLabels = await createDomainLabels(
|
|
"test-app",
|
|
baseDomain,
|
|
"websecure",
|
|
);
|
|
|
|
const webRule = webLabels.find((l) => l.includes(".rule="));
|
|
const websecureRule = websecureLabels.find((l) => l.includes(".rule="));
|
|
|
|
// Both should have correct format
|
|
expect(webRule).toContain("Host(`example.com`)");
|
|
expect(websecureRule).toContain("Host(`example.com`)");
|
|
|
|
// Neither should have malformed format
|
|
expect(webRule).not.toMatch(/Host`[^`]+`\)/);
|
|
expect(websecureRule).not.toMatch(/Host`[^`]+`\)/);
|
|
});
|
|
});
|
|
|
|
describe("Special characters in paths", () => {
|
|
const pathCases = [
|
|
{ name: "simple path", path: "/api" },
|
|
{ name: "nested path", path: "/api/v1/users" },
|
|
{ name: "path with hyphen", path: "/api-v1" },
|
|
{ name: "path with underscore", path: "/api_v1" },
|
|
];
|
|
|
|
for (const { name, path } of pathCases) {
|
|
it(`should generate correct PathPrefix for ${name}: ${path}`, async () => {
|
|
const labels = await createDomainLabels(
|
|
"test-app",
|
|
{ ...baseDomain, path },
|
|
"web",
|
|
);
|
|
const ruleLabel = labels.find((l) => l.includes(".rule="));
|
|
|
|
expect(ruleLabel).toBeDefined();
|
|
expect(ruleLabel).toContain(`PathPrefix(\`${path}\`)`);
|
|
// Verify parenthesis is present
|
|
expect(ruleLabel).not.toMatch(/PathPrefix`[^`]+`\)/);
|
|
});
|
|
}
|
|
});
|
|
});
|