Deploy Dokploy applications from GitHub and Gitea Actions
A dependency-free node20 action that triggers application.deploy or compose.deploy, then follows the deployment to a terminal status so a failed build fails the CI run. Snapshots the deployment list before triggering, so a concurrent deployment is never mistaken for this one. Optionally points the application at a freshly built image first, which is what lets a workflow lint, test, push and deploy the same artifact.
This commit is contained in:
166
test/resolve.test.js
Normal file
166
test/resolve.test.js
Normal file
@@ -0,0 +1,166 @@
|
||||
"use strict";
|
||||
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const { FakeApi } = require("./helpers");
|
||||
const { resolveTarget } = require("../src/resolve");
|
||||
|
||||
const PROJECTS = [
|
||||
{
|
||||
projectId: "p1",
|
||||
name: "shop",
|
||||
environments: [
|
||||
{
|
||||
environmentId: "e1",
|
||||
name: "production",
|
||||
isDefault: true,
|
||||
applications: [{ applicationId: "a1", name: "api", appName: "shop-api-prod" }],
|
||||
compose: [],
|
||||
},
|
||||
{
|
||||
environmentId: "e2",
|
||||
name: "staging",
|
||||
isDefault: false,
|
||||
applications: [{ applicationId: "a2", name: "api", appName: "shop-api-stage" }],
|
||||
compose: [{ composeId: "c1", name: "worker", appName: "shop-worker" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ projectId: "p2", name: "blog", environments: [] },
|
||||
];
|
||||
|
||||
function apiWithProjects(projects = PROJECTS) {
|
||||
return new FakeApi({ "project.all": projects });
|
||||
}
|
||||
|
||||
test("an application id is confirmed through application.one", async () => {
|
||||
const api = new FakeApi({
|
||||
"application.one": { name: "api", appName: "shop-api-prod", environmentId: "e1" },
|
||||
});
|
||||
|
||||
const target = await resolveTarget(api, { applicationId: "a1" });
|
||||
|
||||
assert.deepEqual(target, { kind: "application", id: "a1", name: "api", appName: "shop-api-prod", environmentId: "e1" });
|
||||
assert.deepEqual(api.find("application.one").input, { applicationId: "a1" });
|
||||
});
|
||||
|
||||
test("a compose id is confirmed through compose.one", async () => {
|
||||
const api = new FakeApi({ "compose.one": { name: "worker", appName: "shop-worker", environmentId: "e2" } });
|
||||
|
||||
const target = await resolveTarget(api, { composeId: "c1" });
|
||||
|
||||
assert.equal(target.kind, "compose");
|
||||
assert.equal(target.id, "c1");
|
||||
});
|
||||
|
||||
test("a name resolves against the project's default environment", async () => {
|
||||
const api = apiWithProjects();
|
||||
|
||||
const target = await resolveTarget(api, { project: "shop", service: "api" });
|
||||
|
||||
assert.equal(target.id, "a1");
|
||||
assert.equal(target.environmentId, "e1");
|
||||
assert.equal(target.environmentName, "production");
|
||||
assert.equal(target.appName, "shop-api-prod");
|
||||
});
|
||||
|
||||
test("an explicit environment picks the other copy of the same name", async () => {
|
||||
const api = apiWithProjects();
|
||||
|
||||
const target = await resolveTarget(api, { project: "shop", service: "api", environment: "staging" });
|
||||
|
||||
assert.equal(target.id, "a2");
|
||||
assert.equal(target.environmentName, "staging");
|
||||
});
|
||||
|
||||
test("a Compose stack resolves by name", async () => {
|
||||
const api = apiWithProjects();
|
||||
|
||||
const target = await resolveTarget(api, { project: "shop", service: "worker", environment: "staging" });
|
||||
|
||||
assert.equal(target.kind, "compose");
|
||||
assert.equal(target.id, "c1");
|
||||
});
|
||||
|
||||
test("the Docker service name works as well as the display name", async () => {
|
||||
const api = apiWithProjects();
|
||||
|
||||
const target = await resolveTarget(api, { project: "shop", service: "shop-api-prod" });
|
||||
|
||||
assert.equal(target.id, "a1");
|
||||
});
|
||||
|
||||
test("names match case-insensitively", async () => {
|
||||
const api = apiWithProjects();
|
||||
|
||||
const target = await resolveTarget(api, { project: "SHOP", service: "API" });
|
||||
|
||||
assert.equal(target.id, "a1");
|
||||
});
|
||||
|
||||
test("an unknown project lists the ones that exist", async () => {
|
||||
const api = apiWithProjects();
|
||||
|
||||
await assert.rejects(
|
||||
() => resolveTarget(api, { project: "store", service: "api" }),
|
||||
/No project named "store"\. Available: blog, shop\./,
|
||||
);
|
||||
});
|
||||
|
||||
test("an unknown environment lists the ones that exist", async () => {
|
||||
const api = apiWithProjects();
|
||||
|
||||
await assert.rejects(
|
||||
() => resolveTarget(api, { project: "shop", service: "api", environment: "qa" }),
|
||||
/no environment named "qa"\. Available: production, staging\./,
|
||||
);
|
||||
});
|
||||
|
||||
test("an unknown service lists the ones in that environment", async () => {
|
||||
const api = apiWithProjects();
|
||||
|
||||
await assert.rejects(
|
||||
() => resolveTarget(api, { project: "shop", service: "cron", environment: "staging" }),
|
||||
/No application or Compose stack named "cron" in shop\/staging\. Available: api, worker\./,
|
||||
);
|
||||
});
|
||||
|
||||
test("a name shared by an application and a Compose stack asks for an id", async () => {
|
||||
const api = apiWithProjects([
|
||||
{
|
||||
projectId: "p1",
|
||||
name: "shop",
|
||||
environments: [
|
||||
{
|
||||
environmentId: "e1",
|
||||
name: "production",
|
||||
isDefault: true,
|
||||
applications: [{ applicationId: "a1", name: "api", appName: "shop-api" }],
|
||||
compose: [{ composeId: "c1", name: "api", appName: "shop-api-compose" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
await assert.rejects(
|
||||
() => resolveTarget(api, { project: "shop", service: "api" }),
|
||||
/ambiguous in shop\/production \(application and compose\)\. Use `application-id` or `compose-id`/,
|
||||
);
|
||||
});
|
||||
|
||||
test("a project with no environments is reported as such", async () => {
|
||||
const api = apiWithProjects();
|
||||
|
||||
await assert.rejects(() => resolveTarget(api, { project: "blog", service: "api" }), /has no environments/);
|
||||
});
|
||||
|
||||
test("conflicting or missing target inputs are rejected before any request", async () => {
|
||||
const api = new FakeApi({});
|
||||
|
||||
await assert.rejects(() => resolveTarget(api, { applicationId: "a1", composeId: "c1" }), /not both/);
|
||||
await assert.rejects(() => resolveTarget(api, { applicationId: "a1", project: "shop", service: "api" }), /not both/);
|
||||
await assert.rejects(() => resolveTarget(api, {}), /No target/);
|
||||
await assert.rejects(() => resolveTarget(api, { project: "shop" }), /No target/);
|
||||
assert.equal(api.calls.length, 0);
|
||||
});
|
||||
Reference in New Issue
Block a user