* feat: add more group permissions * feat: restrict access with app permissions * feat: restrict access with search-engine permissions * feat: restrict access with media permissions * refactor: remove permissions for users, groups and invites * test: adjust app router tests with app permissions * fix: integration page accessible without session * fix: search for users, groups and integrations shown to unauthenticated users * chore: address pull request feedback
282 lines
7.5 KiB
TypeScript
282 lines
7.5 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
import { describe, expect, test, vi } from "vitest";
|
|
|
|
import type { Session } from "@homarr/auth";
|
|
import { createId } from "@homarr/db";
|
|
import { apps } from "@homarr/db/schema/sqlite";
|
|
import { createDb } from "@homarr/db/test";
|
|
import type { GroupPermissionKey } from "@homarr/definitions";
|
|
|
|
import { appRouter } from "../app";
|
|
import * as appAccessControl from "../app/app-access-control";
|
|
|
|
// Mock the auth module to return an empty session
|
|
vi.mock("@homarr/auth", () => ({ auth: () => ({}) as Session }));
|
|
|
|
const createDefaultSession = (permissions: GroupPermissionKey[] = []): Session => ({
|
|
user: { id: createId(), permissions, colorScheme: "light" },
|
|
expires: new Date().toISOString(),
|
|
});
|
|
|
|
describe("all should return all apps", () => {
|
|
test("should return all apps with session", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const caller = appRouter.createCaller({
|
|
db,
|
|
session: createDefaultSession(),
|
|
});
|
|
|
|
await db.insert(apps).values([
|
|
{
|
|
id: "2",
|
|
name: "Mantine",
|
|
description: "React components and hooks library",
|
|
iconUrl: "https://mantine.dev/favicon.svg",
|
|
href: "https://mantine.dev",
|
|
},
|
|
{
|
|
id: "1",
|
|
name: "Tabler Icons",
|
|
iconUrl: "https://tabler.io/favicon.ico",
|
|
},
|
|
]);
|
|
|
|
const result = await caller.all();
|
|
expect(result.length).toBe(2);
|
|
expect(result[0]!.id).toBe("2");
|
|
expect(result[1]!.id).toBe("1");
|
|
expect(result[0]!.href).toBeDefined();
|
|
expect(result[0]!.description).toBeDefined();
|
|
expect(result[1]!.href).toBeNull();
|
|
expect(result[1]!.description).toBeNull();
|
|
});
|
|
test("should throw UNAUTHORIZED if the user is not authenticated", async () => {
|
|
// Arrange
|
|
const caller = appRouter.createCaller({
|
|
db: createDb(),
|
|
session: null,
|
|
});
|
|
|
|
// Act
|
|
const actAsync = async () => await caller.all();
|
|
|
|
// Assert
|
|
await expect(actAsync()).rejects.toThrow("UNAUTHORIZED");
|
|
});
|
|
});
|
|
|
|
describe("byId should return an app by id", () => {
|
|
test("should return an app by id when canUserSeeAppAsync returns true", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const caller = appRouter.createCaller({
|
|
db,
|
|
session: null,
|
|
});
|
|
vi.spyOn(appAccessControl, "canUserSeeAppAsync").mockReturnValue(Promise.resolve(true));
|
|
|
|
await db.insert(apps).values([
|
|
{
|
|
id: "2",
|
|
name: "Mantine",
|
|
description: "React components and hooks library",
|
|
iconUrl: "https://mantine.dev/favicon.svg",
|
|
href: "https://mantine.dev",
|
|
},
|
|
{
|
|
id: "1",
|
|
name: "Tabler Icons",
|
|
iconUrl: "https://tabler.io/favicon.ico",
|
|
},
|
|
]);
|
|
|
|
// Act
|
|
const result = await caller.byId({ id: "2" });
|
|
|
|
// Assert
|
|
expect(result.name).toBe("Mantine");
|
|
});
|
|
|
|
test("should throw NOT_FOUND error when canUserSeeAppAsync returns false", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const caller = appRouter.createCaller({
|
|
db,
|
|
session: null,
|
|
});
|
|
await db.insert(apps).values([
|
|
{
|
|
id: "2",
|
|
name: "Mantine",
|
|
description: "React components and hooks library",
|
|
iconUrl: "https://mantine.dev/favicon.svg",
|
|
href: "https://mantine.dev",
|
|
},
|
|
]);
|
|
vi.spyOn(appAccessControl, "canUserSeeAppAsync").mockReturnValue(Promise.resolve(false));
|
|
|
|
// Act
|
|
const actAsync = async () => await caller.byId({ id: "2" });
|
|
|
|
// Assert
|
|
await expect(actAsync()).rejects.toThrow("App not found");
|
|
});
|
|
|
|
test("should throw an error if the app does not exist", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const caller = appRouter.createCaller({
|
|
db,
|
|
session: null,
|
|
});
|
|
|
|
// Act
|
|
const actAsync = async () => await caller.byId({ id: "2" });
|
|
|
|
// Assert
|
|
await expect(actAsync()).rejects.toThrow("App not found");
|
|
});
|
|
});
|
|
|
|
describe("create should create a new app with all arguments", () => {
|
|
test("should create a new app", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const caller = appRouter.createCaller({
|
|
db,
|
|
session: createDefaultSession(["app-create"]),
|
|
});
|
|
const input = {
|
|
name: "Mantine",
|
|
description: "React components and hooks library",
|
|
iconUrl: "https://mantine.dev/favicon.svg",
|
|
href: "https://mantine.dev",
|
|
};
|
|
|
|
// Act
|
|
await caller.create(input);
|
|
|
|
// Assert
|
|
const dbApp = await db.query.apps.findFirst();
|
|
expect(dbApp).toBeDefined();
|
|
expect(dbApp!.name).toBe(input.name);
|
|
expect(dbApp!.description).toBe(input.description);
|
|
expect(dbApp!.iconUrl).toBe(input.iconUrl);
|
|
expect(dbApp!.href).toBe(input.href);
|
|
});
|
|
|
|
test("should create a new app only with required arguments", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const caller = appRouter.createCaller({
|
|
db,
|
|
session: createDefaultSession(["app-create"]),
|
|
});
|
|
const input = {
|
|
name: "Mantine",
|
|
description: null,
|
|
iconUrl: "https://mantine.dev/favicon.svg",
|
|
href: null,
|
|
};
|
|
|
|
// Act
|
|
await caller.create(input);
|
|
|
|
// Assert
|
|
const dbApp = await db.query.apps.findFirst();
|
|
expect(dbApp).toBeDefined();
|
|
expect(dbApp!.name).toBe(input.name);
|
|
expect(dbApp!.description).toBe(input.description);
|
|
expect(dbApp!.iconUrl).toBe(input.iconUrl);
|
|
expect(dbApp!.href).toBe(input.href);
|
|
});
|
|
});
|
|
|
|
describe("update should update an app", () => {
|
|
test("should update an app", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const caller = appRouter.createCaller({
|
|
db,
|
|
session: createDefaultSession(["app-modify-all"]),
|
|
});
|
|
|
|
const appId = createId();
|
|
const toInsert = {
|
|
id: appId,
|
|
name: "Mantine",
|
|
iconUrl: "https://mantine.dev/favicon.svg",
|
|
};
|
|
|
|
await db.insert(apps).values(toInsert);
|
|
|
|
const input = {
|
|
id: appId,
|
|
name: "Mantine2",
|
|
description: "React components and hooks library",
|
|
iconUrl: "https://mantine.dev/favicon.svg2",
|
|
href: "https://mantine.dev",
|
|
};
|
|
|
|
// Act
|
|
await caller.update(input);
|
|
|
|
// Assert
|
|
const dbApp = await db.query.apps.findFirst();
|
|
|
|
expect(dbApp).toBeDefined();
|
|
expect(dbApp!.name).toBe(input.name);
|
|
expect(dbApp!.description).toBe(input.description);
|
|
expect(dbApp!.iconUrl).toBe(input.iconUrl);
|
|
expect(dbApp!.href).toBe(input.href);
|
|
});
|
|
|
|
test("should throw an error if the app does not exist", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const caller = appRouter.createCaller({
|
|
db,
|
|
session: createDefaultSession(["app-modify-all"]),
|
|
});
|
|
|
|
// Act
|
|
const actAsync = async () =>
|
|
await caller.update({
|
|
id: createId(),
|
|
name: "Mantine",
|
|
iconUrl: "https://mantine.dev/favicon.svg",
|
|
description: null,
|
|
href: null,
|
|
});
|
|
|
|
// Assert
|
|
await expect(actAsync()).rejects.toThrow("App not found");
|
|
});
|
|
});
|
|
|
|
describe("delete should delete an app", () => {
|
|
test("should delete an app", async () => {
|
|
// Arrange
|
|
const db = createDb();
|
|
const caller = appRouter.createCaller({
|
|
db,
|
|
session: createDefaultSession(["app-full-all"]),
|
|
});
|
|
|
|
const appId = createId();
|
|
await db.insert(apps).values({
|
|
id: appId,
|
|
name: "Mantine",
|
|
iconUrl: "https://mantine.dev/favicon.svg",
|
|
});
|
|
|
|
// Act
|
|
await caller.delete({ id: appId });
|
|
|
|
// Assert
|
|
const dbApp = await db.query.apps.findFirst();
|
|
expect(dbApp).toBeUndefined();
|
|
});
|
|
});
|