mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07:05:24 +00:00
feat: add the snippets plugin (B2)
This commit is contained in:
1 parent
1eac0f54b0
commit
3656caf168
29 files changed
+5293
No files matched your search
@@ -0,0 +1,186 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { createTestDb, type TestDb } from "@termix/plugin-sdk/testing";
|
||||
import {
|
||||
snippetAccess,
|
||||
snippetFolders,
|
||||
snippets,
|
||||
} from "../../src/backend/tables.js";
|
||||
import {
|
||||
createSnippetRepository,
|
||||
type SnippetRepository,
|
||||
} from "../../src/backend/repository.js";
|
||||
import { pluginDir } from "./helpers";
|
||||
|
||||
let db: TestDb;
|
||||
let repo: SnippetRepository;
|
||||
|
||||
beforeEach(async () => {
|
||||
db = await createTestDb(pluginDir, {
|
||||
before: (sqlite) => {
|
||||
sqlite.exec("INSERT INTO users (id) VALUES ('user-1'), ('user-2')");
|
||||
sqlite.exec(
|
||||
"INSERT INTO roles (id, name, display_name) VALUES (7, 'ops', 'Operations')",
|
||||
);
|
||||
},
|
||||
});
|
||||
repo = createSnippetRepository(
|
||||
db.database,
|
||||
await db.database.define(snippets),
|
||||
await db.database.define(snippetFolders),
|
||||
await db.database.define(snippetAccess),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => db.close());
|
||||
|
||||
describe("snippet repository", () => {
|
||||
it("reads a new row back by its own sync id", async () => {
|
||||
const a = await repo.createSnippet("user-1", {
|
||||
name: "A",
|
||||
content: "echo a",
|
||||
});
|
||||
const b = await repo.createSnippet("user-1", {
|
||||
name: "B",
|
||||
content: "echo b",
|
||||
});
|
||||
expect(a.name).toBe("A");
|
||||
expect(b.name).toBe("B");
|
||||
expect(a.syncId).not.toBe(b.syncId);
|
||||
});
|
||||
|
||||
it("auto-assigns the next order within a folder", async () => {
|
||||
await repo.createSnippet("user-1", {
|
||||
name: "A",
|
||||
content: "a",
|
||||
folder: "prod",
|
||||
});
|
||||
const second = await repo.createSnippet("user-1", {
|
||||
name: "B",
|
||||
content: "b",
|
||||
folder: "prod",
|
||||
});
|
||||
const rootLevel = await repo.createSnippet("user-1", {
|
||||
name: "C",
|
||||
content: "c",
|
||||
});
|
||||
expect(second.order).toBe(1);
|
||||
expect(rootLevel.order).toBe(0);
|
||||
});
|
||||
|
||||
it("renames a folder and re-points its snippets, refusing a name collision", async () => {
|
||||
await repo.createFolder("user-1", "prod", null, null);
|
||||
await repo.createFolder("user-1", "staging", null, null);
|
||||
const snippet = await repo.createSnippet("user-1", {
|
||||
name: "A",
|
||||
content: "a",
|
||||
folder: "prod",
|
||||
});
|
||||
|
||||
const conflict = await repo.renameFolder("user-1", "prod", "staging");
|
||||
expect(conflict.status).toBe("conflict");
|
||||
|
||||
const missing = await repo.renameFolder("user-1", "ghost", "new");
|
||||
expect(missing.status).toBe("missing");
|
||||
|
||||
const renamed = await repo.renameFolder("user-1", "prod", "production");
|
||||
expect(renamed.status).toBe("renamed");
|
||||
|
||||
const updated = await repo.findOwnedById("user-1", snippet.id);
|
||||
expect(updated?.folder).toBe("production");
|
||||
});
|
||||
|
||||
it("moves snippets to the root when their folder is deleted", async () => {
|
||||
await repo.createFolder("user-1", "prod", null, null);
|
||||
const snippet = await repo.createSnippet("user-1", {
|
||||
name: "A",
|
||||
content: "a",
|
||||
folder: "prod",
|
||||
});
|
||||
|
||||
const result = await repo.deleteFolder("user-1", "prod");
|
||||
expect(result).not.toBeNull();
|
||||
|
||||
const updated = await repo.findOwnedById("user-1", snippet.id);
|
||||
expect(updated?.folder).toBeNull();
|
||||
});
|
||||
|
||||
it("bulk imports, skipping existing snippets unless overwrite is set", async () => {
|
||||
await repo.createSnippet("user-1", { name: "A", content: "old" });
|
||||
|
||||
const skipped = await repo.bulkImport(
|
||||
"user-1",
|
||||
[{ name: "A", content: "new" }],
|
||||
[],
|
||||
false,
|
||||
);
|
||||
expect(skipped.snippetsSkipped).toBe(1);
|
||||
expect(skipped.snippetsImported).toBe(0);
|
||||
|
||||
const overwritten = await repo.bulkImport(
|
||||
"user-1",
|
||||
[{ name: "A", content: "new" }],
|
||||
[],
|
||||
true,
|
||||
);
|
||||
expect(overwritten.snippetsUpdated).toBe(1);
|
||||
|
||||
const list = await repo.listOwnedSnippets("user-1");
|
||||
expect(list).toHaveLength(1);
|
||||
expect(list[0].content).toBe("new");
|
||||
});
|
||||
|
||||
it("shares a snippet and lists it for the recipient, then revokes it", async () => {
|
||||
const snippet = await repo.createSnippet("user-1", {
|
||||
name: "A",
|
||||
content: "a",
|
||||
});
|
||||
|
||||
const created = await repo.upsertSnippetAccess({
|
||||
snippetId: snippet.id,
|
||||
targetType: "user",
|
||||
targetUserId: "user-2",
|
||||
grantedBy: "user-1",
|
||||
expiresAt: null,
|
||||
});
|
||||
expect(created.created).toBe(true);
|
||||
|
||||
const again = await repo.upsertSnippetAccess({
|
||||
snippetId: snippet.id,
|
||||
targetType: "user",
|
||||
targetUserId: "user-2",
|
||||
grantedBy: "user-1",
|
||||
expiresAt: "2099-01-01T00:00:00.000Z",
|
||||
});
|
||||
expect(again.created).toBe(false);
|
||||
expect(again.id).toBe(created.id);
|
||||
|
||||
const roleIds = await repo.listUserRoleIds("user-2");
|
||||
const shared = await repo.listSharedSnippets("user-2", roleIds);
|
||||
expect(shared).toEqual([
|
||||
expect.objectContaining({ id: snippet.id, name: "A" }),
|
||||
]);
|
||||
|
||||
const accessible = await repo.findAccessibleSharedSnippet(
|
||||
snippet.id,
|
||||
"user-2",
|
||||
roleIds,
|
||||
);
|
||||
expect(accessible?.id).toBe(snippet.id);
|
||||
|
||||
await repo.revokeSnippetAccess(created.id, snippet.id);
|
||||
const afterRevoke = await repo.listSharedSnippets("user-2", roleIds);
|
||||
expect(afterRevoke).toEqual([]);
|
||||
});
|
||||
|
||||
it("wipes a user's snippets and folders on deleteByUserId", async () => {
|
||||
await repo.createSnippet("user-1", { name: "A", content: "a" });
|
||||
await repo.createFolder("user-1", "prod", null, null);
|
||||
await repo.createSnippet("user-2", { name: "B", content: "b" });
|
||||
|
||||
await repo.deleteByUserId("user-1");
|
||||
|
||||
expect(await repo.listOwnedSnippets("user-1")).toEqual([]);
|
||||
expect(await repo.listFolders("user-1")).toEqual([]);
|
||||
expect(await repo.listOwnedSnippets("user-2")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user