117 lines
3.6 KiB
TypeScript
117 lines
3.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import type { ExecutionContext } from "@nestjs/common";
|
|
import {
|
|
PermissionGuard,
|
|
Permissions,
|
|
PERMISSIONS_KEY,
|
|
} from "./permission.guard.js";
|
|
import type { AuthenticatedRequest } from "./auth.middleware.js";
|
|
import { PermissionDeniedError } from "../shared/errors/application-error.js";
|
|
|
|
describe("PermissionGuard", () => {
|
|
let guard: PermissionGuard;
|
|
let reflector: { getAllAndOverride: ReturnType<typeof vi.fn> };
|
|
|
|
function createMockContext(
|
|
request: Partial<AuthenticatedRequest>,
|
|
): ExecutionContext {
|
|
return {
|
|
switchToHttp: () => ({
|
|
getRequest: () => request as unknown as AuthenticatedRequest,
|
|
}),
|
|
getHandler: () => ({}),
|
|
getClass: () => ({}),
|
|
} as unknown as ExecutionContext;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
reflector = {
|
|
getAllAndOverride: vi.fn(),
|
|
};
|
|
guard = new PermissionGuard(reflector as never);
|
|
});
|
|
|
|
it("should allow access when DEV_MODE is true", () => {
|
|
const original = process.env.DEV_MODE;
|
|
process.env.DEV_MODE = "true";
|
|
const ctx = createMockContext({ userRoles: [] });
|
|
expect(guard.canActivate(ctx)).toBe(true);
|
|
process.env.DEV_MODE = original;
|
|
});
|
|
|
|
it("should allow access when no required permissions", () => {
|
|
process.env.DEV_MODE = "false";
|
|
reflector.getAllAndOverride.mockReturnValue(undefined);
|
|
const ctx = createMockContext({ userRoles: [] });
|
|
expect(guard.canActivate(ctx)).toBe(true);
|
|
});
|
|
|
|
it("should allow access when no required permissions (empty array)", () => {
|
|
reflector.getAllAndOverride.mockReturnValue([]);
|
|
const ctx = createMockContext({ userRoles: [] });
|
|
expect(guard.canActivate(ctx)).toBe(true);
|
|
});
|
|
|
|
it("should allow access when user role has required permission", () => {
|
|
reflector.getAllAndOverride.mockReturnValue([
|
|
Permissions.CONTENT_TEXTBOOK_READ,
|
|
]);
|
|
const ctx = createMockContext({
|
|
userRoles: ["admin"],
|
|
});
|
|
expect(guard.canActivate(ctx)).toBe(true);
|
|
});
|
|
|
|
it("should allow access when teacher role has matching permission", () => {
|
|
reflector.getAllAndOverride.mockReturnValue([
|
|
Permissions.CONTENT_CHAPTER_CREATE,
|
|
]);
|
|
const ctx = createMockContext({
|
|
userRoles: ["teacher"],
|
|
});
|
|
expect(guard.canActivate(ctx)).toBe(true);
|
|
});
|
|
|
|
it("should allow access when at least one role matches", () => {
|
|
reflector.getAllAndOverride.mockReturnValue([
|
|
Permissions.CONTENT_TEXTBOOK_READ,
|
|
]);
|
|
const ctx = createMockContext({
|
|
userRoles: ["student", "unknown-role"],
|
|
});
|
|
expect(guard.canActivate(ctx)).toBe(true);
|
|
});
|
|
|
|
it("should throw PermissionDeniedError when user lacks permission", () => {
|
|
reflector.getAllAndOverride.mockReturnValue([
|
|
Permissions.CONTENT_TEXTBOOK_DELETE,
|
|
]);
|
|
const ctx = createMockContext({
|
|
userRoles: ["student"],
|
|
});
|
|
expect(() => guard.canActivate(ctx)).toThrow(PermissionDeniedError);
|
|
});
|
|
|
|
it("should throw PermissionDeniedError when user has no roles", () => {
|
|
reflector.getAllAndOverride.mockReturnValue([
|
|
Permissions.CONTENT_TEXTBOOK_READ,
|
|
]);
|
|
const ctx = createMockContext({
|
|
userRoles: [],
|
|
});
|
|
expect(() => guard.canActivate(ctx)).toThrow(PermissionDeniedError);
|
|
});
|
|
|
|
it("should use empty roles array when userRoles is undefined", () => {
|
|
reflector.getAllAndOverride.mockReturnValue([
|
|
Permissions.CONTENT_TEXTBOOK_READ,
|
|
]);
|
|
const ctx = createMockContext({});
|
|
expect(() => guard.canActivate(ctx)).toThrow(PermissionDeniedError);
|
|
});
|
|
|
|
it("should expose PERMISSIONS_KEY constant", () => {
|
|
expect(PERMISSIONS_KEY).toBe("permissions");
|
|
});
|
|
});
|