Files
Termix/plugins/automations/tests/backend/tunnels.test.ts
T

91 lines
3.0 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { createFakeContext } from "@termix/plugin-sdk/testing";
vi.mock("../../../../src/backend/database/repositories/factory.js", () => ({
createCurrentNotificationChannelRepository: vi.fn(),
}));
const { setTunnelServices } = await import("../../src/backend/tunnels.js");
const { executeStep } = await import("../../src/backend/actions/index.js");
afterEach(() => {
setTunnelServices(null);
});
function context(dryRun = false) {
return {
userId: "user-1",
automationId: 1,
runId: 1,
dryRun,
template: {},
} as never;
}
const step = (action: "connect" | "disconnect") =>
({
id: "s1",
type: "tunnel",
action,
tunnelName: "7::0::web::8080::db::5432",
}) as never;
describe("the tunnel step", () => {
it("starts and stops the tunnel through tunnels.access as the automation's owner", async () => {
const fake = createFakeContext({ pluginId: "automations" });
const tunnels = {
start: vi.fn(async () => undefined),
stop: vi.fn(async () => undefined),
};
fake.ctx.services.provide("tunnels.access", tunnels);
const get = vi.spyOn(fake.ctx.services, "get");
setTunnelServices(fake.ctx.services);
const connected = await executeStep(step("connect"), context());
expect(connected.success).toBe(true);
expect(tunnels.start).toHaveBeenCalledWith("7::0::web::8080::db::5432");
expect(get).toHaveBeenCalledWith("tunnels.access", { userId: "user-1" });
const stopped = await executeStep(step("disconnect"), context());
expect(stopped.success).toBe(true);
expect(tunnels.stop).toHaveBeenCalledWith("7::0::web::8080::db::5432");
});
it("reports the tunnel plugin's own error", async () => {
const fake = createFakeContext({ pluginId: "automations" });
fake.ctx.services.provide("tunnels.access", {
start: vi.fn(async () => {
throw new Error('Tunnel "x" is not configured');
}),
stop: vi.fn(),
});
setTunnelServices(fake.ctx.services);
const result = await executeStep(step("connect"), context());
expect(result.success).toBe(false);
expect(result.error ?? result.output).toMatch(/not configured/);
});
it("fails the step cleanly while the tunnels plugin is off", async () => {
// No provider: the optional dependency is missing.
setTunnelServices(
createFakeContext({ pluginId: "automations" }).ctx.services,
);
const withoutProvider = await executeStep(step("connect"), context());
expect(withoutProvider.success).toBe(false);
expect(withoutProvider.error ?? withoutProvider.output).toMatch(
/tunnels plugin is not available/,
);
setTunnelServices(null);
const inactive = await executeStep(step("disconnect"), context());
expect(inactive.success).toBe(false);
});
it("only describes what it would do on a dry run", async () => {
const result = await executeStep(step("connect"), context(true));
expect(result.success).toBe(true);
expect(result.output).toMatch(/Would connect tunnel/);
});
});