Skip to content

Commit

Permalink
test: adjust app router tests with app permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
Meierschlumpf committed Nov 9, 2024
1 parent 8ef9153 commit 3a78179
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 15 deletions.
7 changes: 6 additions & 1 deletion apps/nextjs/src/app/[locale]/manage/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,27 +97,32 @@ export default async function ManageLayout({ children }: PropsWithChildren) {
{
label: t("items.tools.label"),
icon: IconTool,
hidden: !session?.user.permissions.includes("admin"),
// As permissions always include there children permissions, we can check for the lowest permission
hidden: !session?.user.permissions.includes("other-view-logs"),
items: [
{
label: t("items.tools.items.docker"),
icon: IconBrandDocker,
href: "/manage/tools/docker",
hidden: !session?.user.permissions.includes("admin"),
},
{
label: t("items.tools.items.api"),
icon: IconPlug,
href: "/manage/tools/api",
hidden: !session?.user.permissions.includes("admin"),
},
{
label: t("items.tools.items.logs"),
icon: IconLogs,
href: "/manage/tools/logs",
hidden: !session?.user.permissions.includes("other-view-logs"),
},
{
label: t("items.tools.items.tasks"),
icon: IconReport,
href: "/manage/tools/tasks",
hidden: !session?.user.permissions.includes("admin"),
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion apps/nextjs/src/app/[locale]/manage/tools/logs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export async function generateMetadata() {

export default async function LogsManagementPage() {
const session = await auth();
if (!session?.user || !session.user.permissions.includes("admin")) {
if (!session?.user || !session.user.permissions.includes("other-view-logs")) {
notFound();
}

Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/router/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { loggingChannel } from "@homarr/redis";
import { createTRPCRouter, permissionRequiredProcedure } from "../trpc";

export const logRouter = createTRPCRouter({
subscribe: permissionRequiredProcedure.requiresPermission("admin").subscription(() => {
subscribe: permissionRequiredProcedure.requiresPermission("other-view-logs").subscription(() => {
return observable<LoggerMessage>((emit) => {
const unsubscribe = loggingChannel.subscribe((data) => {
emit.next(data);
Expand Down
88 changes: 77 additions & 11 deletions packages/api/src/router/test/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,26 @@ 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 defaultSession: Session = {
user: { id: createId(), permissions: [], colorScheme: "light" },
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", async () => {
test("should return all apps with session", async () => {
// Arrange
const db = createDb();
const caller = appRouter.createCaller({
db,
session: null,
session: createDefaultSession(),
});

await db.insert(apps).values([
Expand All @@ -48,15 +51,30 @@ describe("all should return all apps", () => {
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", async () => {
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([
{
Expand All @@ -73,28 +91,61 @@ describe("byId should return an app by id", () => {
},
]);

// 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: defaultSession,
session: createDefaultSession(["app-create"]),
});
const input = {
name: "Mantine",
Expand All @@ -103,8 +154,10 @@ describe("create should create a new app with all arguments", () => {
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);
Expand All @@ -114,10 +167,11 @@ describe("create should create a new app with all arguments", () => {
});

test("should create a new app only with required arguments", async () => {
// Arrange
const db = createDb();
const caller = appRouter.createCaller({
db,
session: defaultSession,
session: createDefaultSession(["app-create"]),
});
const input = {
name: "Mantine",
Expand All @@ -126,8 +180,10 @@ describe("create should create a new app with all arguments", () => {
href: null,
};

// Act
await caller.create(input);

// Assert
const dbApp = await db.query.apps.findFirst();
expect(dbApp).toBeDefined();
expect(dbApp!.name).toBe(input.name);
Expand All @@ -139,10 +195,11 @@ describe("create should create a new app with all arguments", () => {

describe("update should update an app", () => {
test("should update an app", async () => {
// Arrange
const db = createDb();
const caller = appRouter.createCaller({
db,
session: defaultSession,
session: createDefaultSession(["app-modify-all"]),
});

const appId = createId();
Expand All @@ -162,8 +219,10 @@ describe("update should update an app", () => {
href: "https://mantine.dev",
};

// Act
await caller.update(input);

// Assert
const dbApp = await db.query.apps.findFirst();

expect(dbApp).toBeDefined();
Expand All @@ -174,12 +233,14 @@ describe("update should update an app", () => {
});

test("should throw an error if the app does not exist", async () => {
// Arrange
const db = createDb();
const caller = appRouter.createCaller({
db,
session: defaultSession,
session: createDefaultSession(["app-modify-all"]),
});

// Act
const actAsync = async () =>
await caller.update({
id: createId(),
Expand All @@ -188,16 +249,19 @@ describe("update should update an app", () => {
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: defaultSession,
session: createDefaultSession(["app-full-all"]),
});

const appId = createId();
Expand All @@ -207,8 +271,10 @@ describe("delete should delete an app", () => {
iconUrl: "https://mantine.dev/favicon.svg",
});

// Act
await caller.delete({ id: appId });

// Assert
const dbApp = await db.query.apps.findFirst();
expect(dbApp).toBeUndefined();
});
Expand Down
2 changes: 1 addition & 1 deletion packages/spotlight/src/modes/page/pages-search-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const pagesSearchGroup = createGroup<{
icon: IconLogs,
path: "/manage/tools/logs",
name: t("manageLog.label"),
hidden: !session?.user.permissions.includes("admin"),
hidden: !session?.user.permissions.includes("other-view-logs"),
},
{
icon: IconReport,
Expand Down

0 comments on commit 3a78179

Please sign in to comment.