feat: auto committed
This commit is contained in:
59
services/content/src/middleware/auth.middleware.test.ts
Normal file
59
services/content/src/middleware/auth.middleware.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { AuthMiddleware } from "./auth.middleware.js";
|
||||
import type { AuthenticatedRequest } from "./auth.middleware.js";
|
||||
import { UnauthorizedException } from "@nestjs/common";
|
||||
import type { Response, NextFunction } from "express";
|
||||
|
||||
describe("AuthMiddleware", () => {
|
||||
let middleware: AuthMiddleware;
|
||||
const next = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
middleware = new AuthMiddleware();
|
||||
});
|
||||
|
||||
function createReq(headers: Record<string, unknown>): AuthenticatedRequest {
|
||||
return { headers } as unknown as AuthenticatedRequest;
|
||||
}
|
||||
|
||||
it("should set userId and userRoles from headers", () => {
|
||||
const req = createReq({
|
||||
"x-user-id": "user-1",
|
||||
"x-user-roles": "admin,teacher",
|
||||
});
|
||||
middleware.use(req, {} as Response, next as NextFunction);
|
||||
expect(req.userId).toBe("user-1");
|
||||
expect(req.userRoles).toEqual(["admin", "teacher"]);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should set empty roles array when x-user-roles missing", () => {
|
||||
const req = createReq({ "x-user-id": "user-1" });
|
||||
middleware.use(req, {} as Response, next as NextFunction);
|
||||
expect(req.userId).toBe("user-1");
|
||||
expect(req.userRoles).toEqual([]);
|
||||
});
|
||||
|
||||
it("should throw UnauthorizedException when x-user-id missing", () => {
|
||||
const req = createReq({});
|
||||
expect(() =>
|
||||
middleware.use(req, {} as Response, next as NextFunction),
|
||||
).toThrow(UnauthorizedException);
|
||||
});
|
||||
|
||||
it("should throw when x-user-id is not a string", () => {
|
||||
const req = createReq({ "x-user-id": ["not-a-string"] });
|
||||
expect(() =>
|
||||
middleware.use(req, {} as Response, next as NextFunction),
|
||||
).toThrow(UnauthorizedException);
|
||||
});
|
||||
|
||||
it("should not call next when unauthorized", () => {
|
||||
const req = createReq({});
|
||||
expect(() =>
|
||||
middleware.use(req, {} as Response, next as NextFunction),
|
||||
).toThrow();
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
116
services/content/src/middleware/permission.guard.test.ts
Normal file
116
services/content/src/middleware/permission.guard.test.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user