Files
Termix/plugins/snippets/tests/backend/routes.test.ts
T

236 lines
7.4 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { startServer, type TestServer } from "./helpers";
let server: TestServer;
beforeEach(async () => {
server = await startServer();
});
afterEach(async () => {
await server.close();
});
async function create(
overrides: Record<string, unknown> = {},
user = "user-1",
) {
const res = await server.request("POST", "/", {
user,
body: { name: "Deploy", content: "echo deploy", ...overrides },
});
expect(res.status).toBe(201);
return res.body;
}
describe("snippet routes", () => {
it("creates and lists snippets, tagging owned ones as not shared", async () => {
const created = await create();
expect(created).toMatchObject({ name: "Deploy", content: "echo deploy" });
expect(created.syncId).toEqual(expect.any(String));
const list = await server.request("GET", "/");
expect(list.body).toEqual([
expect.objectContaining({ id: created.id, isShared: false }),
]);
});
it("rejects a missing name or content", async () => {
expect(
(await server.request("POST", "/", { body: { content: "x" } })).status,
).toBe(400);
expect(
(await server.request("POST", "/", { body: { name: "x" } })).status,
).toBe(400);
});
it("gets, updates and deletes a snippet", async () => {
const created = await create();
const got = await server.request("GET", `/${created.id}`);
expect(got.status).toBe(200);
expect(got.body.id).toBe(created.id);
const updated = await server.request("PUT", `/${created.id}`, {
body: { content: "echo updated" },
});
expect(updated.status).toBe(200);
expect(updated.body.content).toBe("echo updated");
const deleted = await server.request("DELETE", `/${created.id}`);
expect(deleted.status).toBe(200);
expect((await server.request("GET", `/${created.id}`)).status).toBe(404);
});
it("keeps snippets isolated per user", async () => {
await create({ name: "Owner snippet" }, "user-1");
const list = await server.request("GET", "/", { user: "user-2" });
expect(list.body).toEqual([]);
});
it("creates, renames and deletes a folder, moving its snippets to the root", async () => {
const folder = await server.request("POST", "/folders", {
body: { name: "prod", color: "#fff", icon: "server" },
});
expect(folder.status).toBe(201);
const conflict = await server.request("POST", "/folders", {
body: { name: "prod" },
});
expect(conflict.status).toBe(409);
const rename = await server.request("PUT", "/folders/rename", {
body: { oldName: "prod", newName: "production" },
});
expect(rename.status).toBe(200);
const snippet = await create({ folder: "production" });
expect(snippet.folder).toBe("production");
const deleteFolder = await server.request("DELETE", "/folders/production");
expect(deleteFolder.status).toBe(200);
const afterDelete = await server.request("GET", `/${snippet.id}`);
expect(afterDelete.body.folder).toBeNull();
});
it("reorders snippets, accepting both the snippets and legacy updates keys", async () => {
const a = await create({ name: "a" });
const b = await create({ name: "b" });
const reordered = await server.request("PUT", "/reorder", {
body: {
snippets: [
{ id: a.id, order: 2 },
{ id: b.id, order: 1 },
],
},
});
expect(reordered.status).toBe(200);
const legacy = await server.request("PUT", "/reorder", {
body: { updates: [{ id: a.id, order: 5 }] },
});
expect(legacy.status).toBe(200);
expect((await server.request("PUT", "/reorder", { body: {} })).status).toBe(
400,
);
});
it("exports and bulk-imports snippets and folders", async () => {
await create({ name: "Exportable" });
const exported = await server.request("GET", "/export");
expect(exported.status).toBe(200);
expect(exported.body.snippets).toHaveLength(1);
const imported = await server.request("POST", "/bulk-import", {
body: {
snippets: [{ name: "Imported", content: "echo hi" }],
folders: [],
overwrite: false,
},
});
expect(imported.status).toBe(200);
expect(imported.body.snippetsImported).toBe(1);
});
it("shares a snippet with another user, who can then read it as shared", async () => {
const snippet = await create();
const share = await server.request("POST", `/${snippet.id}/share`, {
body: { targetType: "user", targetUserId: "user-2" },
});
expect(share.status).toBe(200);
const sharedList = await server.request("GET", "/shared", {
user: "user-2",
});
expect(sharedList.body.sharedSnippets).toEqual([
expect.objectContaining({ id: snippet.id, name: snippet.name }),
]);
const visibleToOwner = await server.request("GET", `/${snippet.id}/access`);
expect(visibleToOwner.status).toBe(200);
expect(visibleToOwner.body).toHaveLength(1);
const revoke = await server.request(
"DELETE",
`/${snippet.id}/access/${visibleToOwner.body[0].id}`,
);
expect(revoke.status).toBe(200);
const afterRevoke = await server.request("GET", "/shared", {
user: "user-2",
});
expect(afterRevoke.body.sharedSnippets).toEqual([]);
});
it("refuses sharing a snippet the caller does not own", async () => {
const snippet = await create({}, "user-1");
const attempt = await server.request("POST", `/${snippet.id}/share`, {
user: "user-2",
body: { targetType: "user", targetUserId: "user-1" },
});
expect(attempt.status).toBe(403);
});
it("shares every snippet in a folder at once", async () => {
await create({ name: "s1", folder: "team" });
await create({ name: "s2", folder: "team" });
const share = await server.request("PUT", "/folder/share", {
body: { folder: "team", targetType: "user", targetUserId: "user-2" },
});
expect(share.status).toBe(200);
expect(share.body.snippetsShared).toBe(2);
});
it("denies every route without its permission", async () => {
const denied = await startServer({ permissions: [] });
try {
expect((await denied.request("GET", "/")).status).toBe(403);
expect((await denied.request("POST", "/", { body: {} })).status).toBe(
403,
);
expect((await denied.request("GET", "/folders")).status).toBe(403);
expect(
(await denied.request("POST", "/folders", { body: { name: "x" } }))
.status,
).toBe(403);
} finally {
await denied.close();
}
});
it("lets execute run without any snippets.* permission", async () => {
const noPerms = await startServer({ permissions: [] });
try {
const res = await noPerms.request("POST", "/execute", {
body: { snippetId: 999, hostId: 1 },
});
// 404 (no accessible snippet), not 403: execute needs no grant.
expect(res.status).toBe(404);
} finally {
await noPerms.close();
}
});
it("rejects executing a note", async () => {
const note = await server.request("POST", "/", {
body: { name: "Note", content: "remember this", isNote: true },
});
const res = await server.request("POST", "/execute", {
body: { snippetId: note.body.id, hostId: 1 },
});
expect(res.status).toBe(400);
});
it("404s executing a snippet the caller cannot see", async () => {
const res = await server.request("POST", "/execute", {
body: { snippetId: 999, hostId: 1 },
});
expect(res.status).toBe(404);
});
});