mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 14:15:24 +00:00
177 lines
5.3 KiB
TypeScript
177 lines
5.3 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { startServer, type TestServer } from "./helpers";
|
|
import { parseInventoryProbe } from "../../src/backend/routes.js";
|
|
|
|
let server: TestServer;
|
|
|
|
afterEach(async () => {
|
|
await server?.close();
|
|
});
|
|
|
|
async function createFleet(
|
|
server: TestServer,
|
|
body: Record<string, unknown> = { name: "Web fleet" },
|
|
) {
|
|
const res = await server.request("POST", "/", { body });
|
|
expect(res.status).toBe(200);
|
|
return res.body;
|
|
}
|
|
|
|
describe("fleet CRUD routes", () => {
|
|
beforeEach(async () => {
|
|
server = await startServer();
|
|
});
|
|
|
|
it("creates and lists fleets with member counts", async () => {
|
|
const created = await createFleet(server);
|
|
expect(created).toMatchObject({ name: "Web fleet", tagRules: [] });
|
|
|
|
const list = await server.request("GET", "/");
|
|
expect(list.status).toBe(200);
|
|
expect(list.body).toHaveLength(1);
|
|
expect(list.body[0]).toMatchObject({ id: created.id, memberCount: 0 });
|
|
});
|
|
|
|
it("400s on a missing name", async () => {
|
|
const res = await server.request("POST", "/", { body: {} });
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
it("updates and deletes a fleet", async () => {
|
|
const created = await createFleet(server);
|
|
const updated = await server.request("PATCH", `/${created.id}`, {
|
|
body: { name: "Renamed" },
|
|
});
|
|
expect(updated.status).toBe(200);
|
|
expect(updated.body.name).toBe("Renamed");
|
|
|
|
const deleted = await server.request("DELETE", `/${created.id}`);
|
|
expect(deleted.status).toBe(200);
|
|
expect((await server.request("GET", "/")).body).toHaveLength(0);
|
|
});
|
|
|
|
it("404s for a fleet the caller does not own", async () => {
|
|
const res = await server.request("GET", "/999/members");
|
|
expect(res.status).toBe(404);
|
|
});
|
|
|
|
it("400s on a non-numeric fleet id", async () => {
|
|
const res = await server.request("GET", "/not-a-number/members");
|
|
expect(res.status).toBe(400);
|
|
});
|
|
});
|
|
|
|
describe("fleet membership routes", () => {
|
|
beforeEach(async () => {
|
|
server = await startServer();
|
|
});
|
|
|
|
it("adds and lists members, then removes one", async () => {
|
|
const fleet = await createFleet(server);
|
|
const added = await server.request("POST", `/${fleet.id}/members`, {
|
|
body: { hostId: 10 },
|
|
});
|
|
expect(added.status).toBe(200);
|
|
|
|
const members = await server.request("GET", `/${fleet.id}/members`);
|
|
expect(members.status).toBe(200);
|
|
expect(members.body).toHaveLength(1);
|
|
expect(members.body[0]).toMatchObject({ id: 10, static: true });
|
|
|
|
const removed = await server.request("DELETE", `/${fleet.id}/members/10`);
|
|
expect(removed.status).toBe(200);
|
|
expect(
|
|
(await server.request("GET", `/${fleet.id}/members`)).body,
|
|
).toHaveLength(0);
|
|
});
|
|
|
|
it("404s when adding a host outside the fixture host list", async () => {
|
|
const fleet = await createFleet(server);
|
|
const res = await server.request("POST", `/${fleet.id}/members`, {
|
|
body: { hostId: 999 },
|
|
});
|
|
expect(res.status).toBe(404);
|
|
});
|
|
});
|
|
|
|
describe("permission gates", () => {
|
|
it("view routes 403 without fleets.view", async () => {
|
|
server = await startServer({
|
|
permissions: ["fleets.manage", "fleets.execute"],
|
|
});
|
|
const res = await server.request("GET", "/");
|
|
expect(res.status).toBe(403);
|
|
});
|
|
|
|
it("manage routes 403 without fleets.manage", async () => {
|
|
server = await startServer({
|
|
permissions: ["fleets.view", "fleets.execute"],
|
|
});
|
|
const res = await server.request("POST", "/", { body: { name: "x" } });
|
|
expect(res.status).toBe(403);
|
|
});
|
|
|
|
it("execute routes 403 without fleets.execute", async () => {
|
|
server = await startServer({
|
|
permissions: ["fleets.view", "fleets.manage"],
|
|
});
|
|
const fleet = await createFleet(server);
|
|
const res = await server.request("POST", `/${fleet.id}/execute`, {
|
|
body: { command: "uptime" },
|
|
});
|
|
expect(res.status).toBe(403);
|
|
});
|
|
});
|
|
|
|
describe("parseInventoryProbe", () => {
|
|
it("extracts kernel, arch, hostname, and uptime from key=value lines", () => {
|
|
const out = [
|
|
"kernel=6.1.0-generic",
|
|
"arch=x86_64",
|
|
"hostname=web-1",
|
|
"uptime_seconds=123456",
|
|
].join("\n");
|
|
|
|
expect(parseInventoryProbe(out)).toEqual({
|
|
kernel: "6.1.0-generic",
|
|
architecture: "x86_64",
|
|
hostname: "web-1",
|
|
uptimeSeconds: 123456,
|
|
});
|
|
});
|
|
|
|
it("nulls out fields missing from the probe output", () => {
|
|
expect(parseInventoryProbe("kernel=6.1.0")).toEqual({
|
|
kernel: "6.1.0",
|
|
architecture: null,
|
|
hostname: null,
|
|
uptimeSeconds: null,
|
|
});
|
|
});
|
|
|
|
it("nulls uptimeSeconds when the value is not a bare integer", () => {
|
|
expect(parseInventoryProbe("uptime_seconds=").uptimeSeconds).toBeNull();
|
|
});
|
|
|
|
it("ignores lines with no '=' separator", () => {
|
|
const out = ["garbage line", "kernel=6.1.0"].join("\n");
|
|
expect(parseInventoryProbe(out).kernel).toBe("6.1.0");
|
|
});
|
|
});
|
|
|
|
describe("share target pickers", () => {
|
|
beforeEach(async () => {
|
|
server = await startServer();
|
|
});
|
|
|
|
it("lists users and roles through ctx.hosts", async () => {
|
|
const users = await server.request("GET", "/share-targets/users");
|
|
expect(users.status).toBe(200);
|
|
expect(users.body).toEqual({ users: [] });
|
|
|
|
const roles = await server.request("GET", "/share-targets/roles");
|
|
expect(roles.status).toBe(200);
|
|
expect(roles.body).toEqual({ roles: [] });
|
|
});
|
|
});
|