mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 22:25:24 +00:00
feat: move snippets backend and tables into a plugin (B2)
This commit is contained in:
1 parent
fdedca39fd
commit
e07221eaf3
51 files changed
+28426
-3553
No files matched your search
@@ -38,10 +38,6 @@ describe("RbacAccessRepository", () => {
|
||||
INSERT INTO ssh_data (
|
||||
id, user_id, name, ip, port, username, credential_id, rdp_credential_id, vnc_credential_id, telnet_credential_id, folder, tags, auth_type)
|
||||
VALUES (42, 'owner-1', 'prod', '10.0.0.42', 22, 'root', 123, 124, 125, 126, 'servers', 'linux', 'password');
|
||||
INSERT INTO snippets (id, user_id, name, content)
|
||||
VALUES
|
||||
(99, 'owner-1', 'deploy', 'echo deploy'),
|
||||
(100, 'owner-1', 'rollback', 'echo rollback');
|
||||
INSERT INTO host_access (
|
||||
id, host_id, user_id, role_id, granted_by, permission_level, expires_at, created_at
|
||||
)
|
||||
@@ -49,12 +45,6 @@ describe("RbacAccessRepository", () => {
|
||||
(1, 42, 'user-1', NULL, 'admin', 'view', NULL, '2026-06-26T00:00:00.000Z'),
|
||||
(2, 42, NULL, 7, 'admin', 'view', '2026-06-27T00:00:00.000Z', '2026-06-26T01:00:00.000Z'),
|
||||
(5, 44, 'user-1', NULL, 'admin', 'view', '2026-06-25T00:00:00.000Z', '2026-06-24T00:00:00.000Z');
|
||||
INSERT INTO snippet_access (
|
||||
id, snippet_id, user_id, role_id, granted_by, permission_level, expires_at, created_at
|
||||
)
|
||||
VALUES
|
||||
(3, 99, 'user-1', NULL, 'admin', 'view', NULL, '2026-06-26T00:00:00.000Z'),
|
||||
(4, 99, NULL, 7, 'admin', 'view', '2026-06-27T00:00:00.000Z', '2026-06-26T01:00:00.000Z');
|
||||
INSERT INTO shared_host_secrets (
|
||||
id, host_access_id, target_user_id, protocol, source_type, original_credential_id, encrypted_username, encrypted_auth_type
|
||||
)
|
||||
@@ -95,29 +85,6 @@ describe("RbacAccessRepository", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("lists snippet access with user and role target metadata", async () => {
|
||||
const repo = await createRepository();
|
||||
|
||||
const accessList = await repo.listSnippetAccess(99);
|
||||
|
||||
expect(accessList.map((access) => access.targetType)).toEqual([
|
||||
"role",
|
||||
"user",
|
||||
]);
|
||||
expect(accessList[0]).toMatchObject({
|
||||
id: 4,
|
||||
roleId: 7,
|
||||
roleName: "ops",
|
||||
grantedByUsername: "admin",
|
||||
});
|
||||
expect(accessList[1]).toMatchObject({
|
||||
id: 3,
|
||||
userId: "user-1",
|
||||
username: "alice",
|
||||
grantedByUsername: "admin",
|
||||
});
|
||||
});
|
||||
|
||||
it("lists shared hosts for direct and role access", async () => {
|
||||
const repo = await createRepository();
|
||||
|
||||
@@ -253,67 +220,6 @@ describe("RbacAccessRepository", () => {
|
||||
expect(writeCount).toBe(1);
|
||||
});
|
||||
|
||||
it("lists shared snippets and preserves route-level direct-over-role behavior", async () => {
|
||||
const repo = await createRepository();
|
||||
|
||||
const sharedSnippets = await repo.listSharedSnippets(
|
||||
"user-1",
|
||||
[7],
|
||||
activeAccessTime,
|
||||
);
|
||||
|
||||
expect(sharedSnippets).toHaveLength(1);
|
||||
expect(sharedSnippets[0]).toMatchObject({
|
||||
id: 99,
|
||||
name: "deploy",
|
||||
ownerUsername: "owner",
|
||||
permissionLevel: "view",
|
||||
});
|
||||
});
|
||||
|
||||
it("lists visible shared snippets for the main snippets route", async () => {
|
||||
const repo = await createRepository();
|
||||
|
||||
const sharedSnippets = await repo.listVisibleSharedSnippets(
|
||||
"user-1",
|
||||
[7],
|
||||
activeAccessTime,
|
||||
);
|
||||
|
||||
expect(sharedSnippets.map((snippet) => snippet.id)).toEqual([99, 99]);
|
||||
expect(sharedSnippets[0]).toMatchObject({
|
||||
userId: "owner-1",
|
||||
name: "deploy",
|
||||
content: "echo deploy",
|
||||
ownerUsername: "owner",
|
||||
});
|
||||
});
|
||||
|
||||
it("finds an accessible shared snippet for direct or role access", async () => {
|
||||
const repo = await createRepository();
|
||||
|
||||
await expect(
|
||||
repo.findAccessibleSharedSnippet(99, "user-2", [7], activeAccessTime),
|
||||
).resolves.toMatchObject({
|
||||
id: 99,
|
||||
userId: "owner-1",
|
||||
name: "deploy",
|
||||
content: "echo deploy",
|
||||
ownerUsername: "owner",
|
||||
permissionLevel: "view",
|
||||
hostFilter: null,
|
||||
});
|
||||
|
||||
await expect(
|
||||
repo.findAccessibleSharedSnippet(
|
||||
99,
|
||||
"user-2",
|
||||
[7],
|
||||
"2026-06-28T00:00:00.000Z",
|
||||
),
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("upserts host access and updates overrides", async () => {
|
||||
let writeCount = 0;
|
||||
const repo = await createRepository(() => {
|
||||
@@ -432,39 +338,4 @@ describe("RbacAccessRepository", () => {
|
||||
expect(await repo.deleteHostAccessForUserReferences("admin")).toBe(0);
|
||||
expect(writeCount).toBe(1);
|
||||
});
|
||||
|
||||
it("upserts and revokes snippet access", async () => {
|
||||
let writeCount = 0;
|
||||
const repo = await createRepository(() => {
|
||||
writeCount += 1;
|
||||
});
|
||||
|
||||
const updated = await repo.upsertSnippetAccess({
|
||||
snippetId: 99,
|
||||
targetType: "user",
|
||||
targetUserId: "user-1",
|
||||
grantedBy: "admin",
|
||||
expiresAt: "2026-06-28T00:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(updated).toEqual({ id: 3, created: false });
|
||||
expect(
|
||||
(await repo.listSnippetAccess(99)).find((row) => row.id === 3),
|
||||
).toMatchObject({ expiresAt: "2026-06-28T00:00:00.000Z" });
|
||||
|
||||
const created = await repo.upsertSnippetAccess({
|
||||
snippetId: 100,
|
||||
targetType: "role",
|
||||
targetRoleId: 7,
|
||||
grantedBy: "admin",
|
||||
expiresAt: null,
|
||||
});
|
||||
expect(created.created).toBe(true);
|
||||
|
||||
await repo.revokeSnippetAccess(3, 99);
|
||||
expect((await repo.listSnippetAccess(99)).map((row) => row.id)).toEqual([
|
||||
4,
|
||||
]);
|
||||
expect(writeCount).toBe(3);
|
||||
});
|
||||
});
|
||||
@@ -1,356 +0,0 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { TestSqliteDatabase } from "./test-support.js";
|
||||
import { SnippetRepository } from "../../../database/repositories/snippet-repository.js";
|
||||
|
||||
describe("SnippetRepository", () => {
|
||||
let adapter: TestSqliteDatabase | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
if (adapter) {
|
||||
await adapter.close();
|
||||
adapter = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function createRepository(onWrite?: () => void): Promise<{
|
||||
repository: SnippetRepository;
|
||||
}> {
|
||||
adapter = new TestSqliteDatabase();
|
||||
const context = await adapter.connect();
|
||||
await adapter.exec(`
|
||||
INSERT INTO users (id, username, password_hash) VALUES
|
||||
('user-1', 'user-1', 'hash'),
|
||||
('user-2', 'user-2', 'hash');
|
||||
|
||||
INSERT INTO snippets (
|
||||
id, user_id, name, content, description, folder, "order", host_filter
|
||||
)
|
||||
VALUES
|
||||
(1, 'user-1', 'root', 'uptime', NULL, NULL, 2, NULL),
|
||||
(2, 'user-1', 'deploy', 'make deploy', 'Deploy app', 'ops', 1, 'linux'),
|
||||
(3, 'user-2', 'other', 'whoami', NULL, NULL, 1, NULL);
|
||||
INSERT INTO snippet_folders (id, user_id, name, color, icon)
|
||||
VALUES
|
||||
(1, 'user-1', 'ops', '#123456', 'terminal'),
|
||||
(2, 'user-1', 'db', NULL, NULL),
|
||||
(3, 'user-2', 'other', NULL, NULL);
|
||||
`);
|
||||
|
||||
return {
|
||||
repository: new SnippetRepository(context, onWrite),
|
||||
};
|
||||
}
|
||||
|
||||
it("finds owned snippets only", async () => {
|
||||
const { repository } = await createRepository();
|
||||
|
||||
await expect(repository.findOwnedById("user-1", 1)).resolves.toMatchObject({
|
||||
id: 1,
|
||||
userId: "user-1",
|
||||
name: "root",
|
||||
});
|
||||
await expect(repository.findOwnedById("user-1", 3)).resolves.toBeNull();
|
||||
await expect(repository.findOwnedById("user-1", 999)).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it("lists folders by name for a user", async () => {
|
||||
const { repository } = await createRepository();
|
||||
|
||||
const rows = await repository.listFolders("user-1");
|
||||
|
||||
expect(rows.map((row) => row.name)).toEqual(["db", "ops"]);
|
||||
});
|
||||
|
||||
it("lists export data for a user", async () => {
|
||||
const { repository } = await createRepository();
|
||||
|
||||
const snippets = await repository.listSnippetsForExport("user-1");
|
||||
const folders = await repository.listFoldersForExport("user-1");
|
||||
|
||||
expect(snippets.map((row) => row.name)).toEqual(["root", "deploy"]);
|
||||
expect(folders.map((row) => row.name)).toEqual(["db", "ops"]);
|
||||
});
|
||||
|
||||
it("lists owned snippets for route merging", async () => {
|
||||
const { repository } = await createRepository();
|
||||
|
||||
const rows = await repository.listOwnedSnippets("user-1");
|
||||
|
||||
expect(rows.map((row) => row.name)).toEqual(["root", "deploy"]);
|
||||
});
|
||||
|
||||
it("reorders snippets", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
await repository.reorderSnippets("user-1", [
|
||||
{ id: 1, order: 9, folder: " ops " },
|
||||
{ id: 999, order: 1 },
|
||||
]);
|
||||
|
||||
await expect(repository.findOwnedById("user-1", 1)).resolves.toMatchObject({
|
||||
order: 9,
|
||||
folder: "ops",
|
||||
});
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("creates snippets with the next folder order", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
const created = await repository.createSnippet("user-1", {
|
||||
name: " new ",
|
||||
content: " echo ok ",
|
||||
description: " desc ",
|
||||
folder: "ops",
|
||||
hostFilter: { os: "linux" },
|
||||
});
|
||||
|
||||
expect(created).toMatchObject({
|
||||
userId: "user-1",
|
||||
name: "new",
|
||||
content: "echo ok",
|
||||
description: "desc",
|
||||
folder: "ops",
|
||||
order: 2,
|
||||
hostFilter: JSON.stringify({ os: "linux" }),
|
||||
});
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("updates snippets and returns the original row for audit names", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
const result = await repository.updateSnippet("user-1", 2, {
|
||||
name: " deploy new ",
|
||||
content: " make deploy2 ",
|
||||
description: null,
|
||||
folder: null,
|
||||
order: 7,
|
||||
hostFilter: null,
|
||||
});
|
||||
const missing = await repository.updateSnippet("user-1", 3, {
|
||||
name: "nope",
|
||||
});
|
||||
|
||||
expect(result?.existing).toMatchObject({ name: "deploy" });
|
||||
expect(result?.updated).toMatchObject({
|
||||
name: "deploy new",
|
||||
content: "make deploy2",
|
||||
description: null,
|
||||
folder: null,
|
||||
order: 7,
|
||||
hostFilter: null,
|
||||
});
|
||||
expect(missing).toBeNull();
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("deletes snippets and returns the deleted row for audit names", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
const deleted = await repository.deleteSnippet("user-1", 2);
|
||||
const missing = await repository.deleteSnippet("user-1", 3);
|
||||
|
||||
expect(deleted).toMatchObject({ id: 2, name: "deploy" });
|
||||
expect(missing).toBeNull();
|
||||
await expect(repository.findOwnedById("user-1", 2)).resolves.toBeNull();
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("deletes all snippets and folders for a user", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
await expect(repository.deleteByUserId("user-1")).resolves.toEqual({
|
||||
snippetsDeleted: 2,
|
||||
foldersDeleted: 2,
|
||||
});
|
||||
|
||||
expect(
|
||||
await adapter!.query(sql`SELECT id FROM snippets ORDER BY id`),
|
||||
).toEqual([{ id: 3 }]);
|
||||
expect(
|
||||
await adapter!.query(sql`SELECT id FROM snippet_folders ORDER BY id`),
|
||||
).toEqual([{ id: 3 }]);
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("bulk imports folders and snippets", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
const result = await repository.bulkImport(
|
||||
"user-1",
|
||||
[
|
||||
{
|
||||
name: " new snippet ",
|
||||
content: " echo hi ",
|
||||
description: " desc ",
|
||||
folder: " new folder ",
|
||||
hostFilter: "linux",
|
||||
},
|
||||
{ name: "", content: "bad" },
|
||||
{ name: "deploy", content: "skip", folder: "ops" },
|
||||
],
|
||||
[
|
||||
{ name: " new folder ", color: " #fff ", icon: " star " },
|
||||
{ name: "ops" },
|
||||
{ name: "" },
|
||||
],
|
||||
false,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
snippetsImported: 1,
|
||||
snippetsSkipped: 1,
|
||||
snippetsUpdated: 0,
|
||||
foldersImported: 1,
|
||||
foldersSkipped: 1,
|
||||
failed: 2,
|
||||
errors: [
|
||||
"Folder missing name",
|
||||
"Snippet 2: name and content are required",
|
||||
],
|
||||
});
|
||||
await expect(repository.findOwnedById("user-1", 4)).resolves.toMatchObject({
|
||||
name: "new snippet",
|
||||
content: "echo hi",
|
||||
description: "desc",
|
||||
folder: "new folder",
|
||||
order: 0,
|
||||
hostFilter: "linux",
|
||||
});
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("bulk import overwrites existing snippets", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
const result = await repository.bulkImport(
|
||||
"user-1",
|
||||
[
|
||||
{
|
||||
name: "deploy",
|
||||
content: "make deploy v2",
|
||||
folder: "ops",
|
||||
order: 5,
|
||||
hostFilter: "prod",
|
||||
},
|
||||
],
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
snippetsImported: 0,
|
||||
snippetsSkipped: 0,
|
||||
snippetsUpdated: 1,
|
||||
failed: 0,
|
||||
});
|
||||
await expect(repository.findOwnedById("user-1", 2)).resolves.toMatchObject({
|
||||
content: "make deploy v2",
|
||||
order: 5,
|
||||
hostFilter: "prod",
|
||||
});
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("creates folders and rejects duplicate names", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
const created = await repository.createFolder(
|
||||
"user-1",
|
||||
" new ",
|
||||
" #fff ",
|
||||
" star ",
|
||||
);
|
||||
const duplicate = await repository.createFolder(
|
||||
"user-1",
|
||||
"ops",
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
expect(created).toMatchObject({
|
||||
userId: "user-1",
|
||||
name: "new",
|
||||
color: "#fff",
|
||||
icon: "star",
|
||||
});
|
||||
expect(duplicate).toBeNull();
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("updates folder metadata", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
const updated = await repository.updateFolderMetadata(
|
||||
"user-1",
|
||||
"ops",
|
||||
" #abc ",
|
||||
undefined,
|
||||
);
|
||||
const missing = await repository.updateFolderMetadata(
|
||||
"user-1",
|
||||
"missing",
|
||||
null,
|
||||
null,
|
||||
);
|
||||
|
||||
expect(updated).toMatchObject({
|
||||
name: "ops",
|
||||
color: "#abc",
|
||||
icon: "terminal",
|
||||
});
|
||||
expect(missing).toBeNull();
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renames folders and attached snippets", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
await expect(
|
||||
repository.renameFolder("user-1", "missing", "new"),
|
||||
).resolves.toEqual({ status: "missing" });
|
||||
await expect(
|
||||
repository.renameFolder("user-1", "ops", "db"),
|
||||
).resolves.toEqual({
|
||||
status: "conflict",
|
||||
});
|
||||
await expect(
|
||||
repository.renameFolder("user-1", "ops", "deploys"),
|
||||
).resolves.toEqual({ status: "renamed" });
|
||||
|
||||
await expect(repository.listFolders("user-1")).resolves.toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: "deploys" })]),
|
||||
);
|
||||
await expect(repository.findOwnedById("user-1", 2)).resolves.toMatchObject({
|
||||
folder: "deploys",
|
||||
});
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("deletes folders and moves snippets to the root", async () => {
|
||||
const onWrite = vi.fn();
|
||||
const { repository } = await createRepository(onWrite);
|
||||
|
||||
await repository.deleteFolder("user-1", "ops");
|
||||
|
||||
expect(
|
||||
(await repository.listFolders("user-1")).map((row) => row.name),
|
||||
).toEqual(["db"]);
|
||||
await expect(repository.findOwnedById("user-1", 2)).resolves.toMatchObject({
|
||||
folder: null,
|
||||
});
|
||||
expect(onWrite).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,79 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createSnippetExecutionResult,
|
||||
getSnippetExecutionTimeoutMs,
|
||||
resolveSnippetCommand,
|
||||
} from "../../../database/routes/snippets-execution.js";
|
||||
|
||||
describe("snippet execution", () => {
|
||||
it("treats stderr as diagnostic output when the command succeeds", () => {
|
||||
expect(createSnippetExecutionResult(0, "done\n", "warning\n")).toEqual({
|
||||
success: true,
|
||||
output: "done\n",
|
||||
error: "warning\n",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the exit code to report command failure", () => {
|
||||
expect(createSnippetExecutionResult(1, "", "failed\n")).toEqual({
|
||||
success: false,
|
||||
output: "",
|
||||
error: "failed\n",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves the previous fallback when no exit code is available", () => {
|
||||
expect(createSnippetExecutionResult(null, "done\n", "")).toEqual({
|
||||
success: true,
|
||||
output: "done\n",
|
||||
});
|
||||
expect(createSnippetExecutionResult(null, "", "failed\n").success).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("disables the command timeout by default", () => {
|
||||
expect(getSnippetExecutionTimeoutMs(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("converts a configured timeout from seconds to milliseconds", () => {
|
||||
expect(getSnippetExecutionTimeoutMs("45")).toBe(45_000);
|
||||
});
|
||||
|
||||
it.each(["", "0", "-1", "invalid"])(
|
||||
"ignores invalid timeout value %j",
|
||||
(value) => {
|
||||
expect(getSnippetExecutionTimeoutMs(value)).toBeUndefined();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("resolveSnippetCommand", () => {
|
||||
const host = { ip: "10.0.0.5", username: "root", port: 22, name: "web-01" };
|
||||
|
||||
it("substitutes host variables per target host", () => {
|
||||
expect(
|
||||
resolveSnippetCommand("ssh $USER@$HOST -p $PORT # $NAME", host),
|
||||
).toBe("ssh root@10.0.0.5 -p 22 # web-01");
|
||||
});
|
||||
|
||||
it("supports brace syntax for host variables", () => {
|
||||
expect(resolveSnippetCommand("ping ${HOST}", host)).toBe("ping 10.0.0.5");
|
||||
});
|
||||
|
||||
it("substitutes input placeholders from inputValues", () => {
|
||||
expect(
|
||||
resolveSnippetCommand("nc -zv $HOST ${INPUT_1:Port}", host, {
|
||||
INPUT_1: "8080",
|
||||
}),
|
||||
).toBe("nc -zv 10.0.0.5 8080");
|
||||
});
|
||||
|
||||
it("leaves host variables literal when no host context is given", () => {
|
||||
expect(resolveSnippetCommand("ping $HOST", null)).toBe("ping $HOST");
|
||||
});
|
||||
|
||||
it("leaves input placeholders literal when no value was supplied", () => {
|
||||
expect(resolveSnippetCommand("echo $INPUT_1", null)).toBe("echo $INPUT_1");
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
describe("snippet execution host access", () => {
|
||||
it("uses the shared-host-aware resolver", () => {
|
||||
const source = fs.readFileSync(
|
||||
path.resolve(__dirname, "../../../database/routes/snippets.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const executeRoute = source.slice(
|
||||
source.indexOf('router.post(\n "/execute"'),
|
||||
);
|
||||
|
||||
expect(executeRoute).toContain(
|
||||
"const host = await resolveHostById(parseInt(hostId), userId)",
|
||||
);
|
||||
expect(executeRoute).not.toContain("host.userId !== userId");
|
||||
});
|
||||
});
|
||||
@@ -33,8 +33,6 @@ describe("isValidEntityType", () => {
|
||||
"hosts",
|
||||
"sshCredentials",
|
||||
"sshFolders",
|
||||
"snippets",
|
||||
"snippetFolders",
|
||||
"vaultProfiles",
|
||||
"dashboardServiceLinks",
|
||||
"homepageItems",
|
||||
@@ -88,7 +86,7 @@ describe("stripWritePayload", () => {
|
||||
|
||||
it("does not mutate the original payload object", () => {
|
||||
const payload = { id: 1, userId: "user-1", syncId: "abc", name: "x" };
|
||||
stripWritePayload("snippets", payload);
|
||||
stripWritePayload("sshFolders", payload);
|
||||
expect(payload).toEqual({
|
||||
id: 1,
|
||||
userId: "user-1",
|
||||
|
||||
@@ -6,6 +6,7 @@ const calls = vi.hoisted(() => ({
|
||||
deletedFor: [] as string[],
|
||||
rotatedFor: [] as string[],
|
||||
legacyWrapsDeletedFor: [] as string[],
|
||||
dataWipedEvents: [] as Array<{ userId: string }>,
|
||||
}));
|
||||
|
||||
function deletingRepo(label: string) {
|
||||
@@ -29,11 +30,19 @@ vi.mock("../../../database/repositories/factory.js", () => ({
|
||||
createCurrentFileManagerBookmarkRepository: deletingRepo("bookmarks"),
|
||||
createCurrentRecentActivityRepository: deletingRepo("activity"),
|
||||
createCurrentDismissedAlertRepository: deletingRepo("alerts"),
|
||||
createCurrentSnippetRepository: deletingRepo("snippets"),
|
||||
createCurrentHostRepository: deletingRepo("hosts"),
|
||||
createCurrentCredentialRepository: deletingRepo("credentials"),
|
||||
}));
|
||||
|
||||
vi.mock("../../../plugins/events.js", () => ({
|
||||
pluginEvents: {
|
||||
emit: (_topic: string, payload: { userId: string }) => {
|
||||
calls.dataWipedEvents.push(payload);
|
||||
},
|
||||
},
|
||||
TOPICS: { userDataWiped: "user.data_wiped" },
|
||||
}));
|
||||
|
||||
vi.mock("../../../utils/user-keys.js", () => ({
|
||||
UserKeyManager: {
|
||||
getInstance: () => ({
|
||||
@@ -65,6 +74,7 @@ beforeEach(() => {
|
||||
calls.deletedFor = [];
|
||||
calls.rotatedFor = [];
|
||||
calls.legacyWrapsDeletedFor = [];
|
||||
calls.dataWipedEvents = [];
|
||||
});
|
||||
|
||||
describe("resetUserPassword", () => {
|
||||
@@ -110,10 +120,10 @@ describe("resetUserPassword", () => {
|
||||
"bookmarks:user-1",
|
||||
"activity:user-1",
|
||||
"alerts:user-1",
|
||||
"snippets:user-1",
|
||||
"hosts:user-1",
|
||||
"credentials:user-1",
|
||||
]);
|
||||
expect(calls.dataWipedEvents).toEqual([{ userId: "user-1" }]);
|
||||
expect(calls.rotatedFor).toEqual(["user-1"]);
|
||||
expect(calls.legacyWrapsDeletedFor).toEqual(["user-1"]);
|
||||
expect(
|
||||
|
||||
Reference in new issue
Block a user