test(auth): add unit tests for AuthGuard and AuthController with mocked dependencies
This commit is contained in:
184
backend/src/auth/auth.controller.spec.ts
Normal file
184
backend/src/auth/auth.controller.spec.ts
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
jest.mock("uuid", () => ({
|
||||||
|
v4: jest.fn(() => "mocked-uuid"),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("@noble/post-quantum/ml-kem.js", () => ({
|
||||||
|
ml_kem768: {
|
||||||
|
keygen: jest.fn(),
|
||||||
|
encapsulate: jest.fn(),
|
||||||
|
decapsulate: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock("jose", () => ({
|
||||||
|
SignJWT: jest.fn().mockReturnValue({
|
||||||
|
setProtectedHeader: jest.fn().mockReturnThis(),
|
||||||
|
setIssuedAt: jest.fn().mockReturnThis(),
|
||||||
|
setExpirationTime: jest.fn().mockReturnThis(),
|
||||||
|
sign: jest.fn().mockResolvedValue("mocked-jwt"),
|
||||||
|
}),
|
||||||
|
jwtVerify: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
|
import { AuthController } from "./auth.controller";
|
||||||
|
import { AuthService } from "./auth.service";
|
||||||
|
|
||||||
|
jest.mock("iron-session", () => ({
|
||||||
|
getIronSession: jest.fn().mockResolvedValue({
|
||||||
|
save: jest.fn(),
|
||||||
|
destroy: jest.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("AuthController", () => {
|
||||||
|
let controller: AuthController;
|
||||||
|
let authService: AuthService;
|
||||||
|
let _configService: ConfigService;
|
||||||
|
|
||||||
|
const mockAuthService = {
|
||||||
|
register: jest.fn(),
|
||||||
|
login: jest.fn(),
|
||||||
|
verifyTwoFactorLogin: jest.fn(),
|
||||||
|
refresh: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockConfigService = {
|
||||||
|
get: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue("complex_password_at_least_32_characters_long"),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [AuthController],
|
||||||
|
providers: [
|
||||||
|
{ provide: AuthService, useValue: mockAuthService },
|
||||||
|
{ provide: ConfigService, useValue: mockConfigService },
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<AuthController>(AuthController);
|
||||||
|
authService = module.get<AuthService>(AuthService);
|
||||||
|
_configService = module.get<ConfigService>(ConfigService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be defined", () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("register", () => {
|
||||||
|
it("should call authService.register", async () => {
|
||||||
|
const dto = {
|
||||||
|
email: "test@example.com",
|
||||||
|
password: "password",
|
||||||
|
username: "test",
|
||||||
|
};
|
||||||
|
await controller.register(dto as any);
|
||||||
|
expect(authService.register).toHaveBeenCalledWith(dto);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("login", () => {
|
||||||
|
it("should call authService.login and setup session if success", async () => {
|
||||||
|
const dto = { email: "test@example.com", password: "password" };
|
||||||
|
const req = { ip: "127.0.0.1" } as any;
|
||||||
|
const res = { json: jest.fn() } as any;
|
||||||
|
const loginResult = {
|
||||||
|
access_token: "at",
|
||||||
|
refresh_token: "rt",
|
||||||
|
userId: "1",
|
||||||
|
message: "ok",
|
||||||
|
};
|
||||||
|
mockAuthService.login.mockResolvedValue(loginResult);
|
||||||
|
|
||||||
|
await controller.login(dto as any, "ua", req, res);
|
||||||
|
|
||||||
|
expect(authService.login).toHaveBeenCalledWith(dto, "ua", "127.0.0.1");
|
||||||
|
expect(res.json).toHaveBeenCalledWith({ message: "ok", userId: "1" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return result if no access_token", async () => {
|
||||||
|
const dto = { email: "test@example.com", password: "password" };
|
||||||
|
const req = { ip: "127.0.0.1" } as any;
|
||||||
|
const res = { json: jest.fn() } as any;
|
||||||
|
const loginResult = { message: "2fa_required", userId: "1" };
|
||||||
|
mockAuthService.login.mockResolvedValue(loginResult);
|
||||||
|
|
||||||
|
await controller.login(dto as any, "ua", req, res);
|
||||||
|
|
||||||
|
expect(res.json).toHaveBeenCalledWith(loginResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("verifyTwoFactor", () => {
|
||||||
|
it("should call authService.verifyTwoFactorLogin and setup session", async () => {
|
||||||
|
const dto = { userId: "1", token: "123456" };
|
||||||
|
const req = { ip: "127.0.0.1" } as any;
|
||||||
|
const res = { json: jest.fn() } as any;
|
||||||
|
const verifyResult = {
|
||||||
|
access_token: "at",
|
||||||
|
refresh_token: "rt",
|
||||||
|
message: "ok",
|
||||||
|
};
|
||||||
|
mockAuthService.verifyTwoFactorLogin.mockResolvedValue(verifyResult);
|
||||||
|
|
||||||
|
await controller.verifyTwoFactor(dto, "ua", req, res);
|
||||||
|
|
||||||
|
expect(authService.verifyTwoFactorLogin).toHaveBeenCalledWith(
|
||||||
|
"1",
|
||||||
|
"123456",
|
||||||
|
"ua",
|
||||||
|
"127.0.0.1",
|
||||||
|
);
|
||||||
|
expect(res.json).toHaveBeenCalledWith({ message: "ok" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("refresh", () => {
|
||||||
|
it("should refresh token if session has refresh token", async () => {
|
||||||
|
const { getIronSession } = require("iron-session");
|
||||||
|
const session = { refreshToken: "rt", save: jest.fn() };
|
||||||
|
getIronSession.mockResolvedValue(session);
|
||||||
|
const req = {} as any;
|
||||||
|
const res = { json: jest.fn() } as any;
|
||||||
|
mockAuthService.refresh.mockResolvedValue({
|
||||||
|
access_token: "at2",
|
||||||
|
refresh_token: "rt2",
|
||||||
|
});
|
||||||
|
|
||||||
|
await controller.refresh(req, res);
|
||||||
|
|
||||||
|
expect(authService.refresh).toHaveBeenCalledWith("rt");
|
||||||
|
expect(res.json).toHaveBeenCalledWith({ message: "Token refreshed" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return 401 if no refresh token", async () => {
|
||||||
|
const { getIronSession } = require("iron-session");
|
||||||
|
const session = { save: jest.fn() };
|
||||||
|
getIronSession.mockResolvedValue(session);
|
||||||
|
const req = {} as any;
|
||||||
|
const res = { status: jest.fn().mockReturnThis(), json: jest.fn() } as any;
|
||||||
|
|
||||||
|
await controller.refresh(req, res);
|
||||||
|
|
||||||
|
expect(res.status).toHaveBeenCalledWith(401);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("logout", () => {
|
||||||
|
it("should destroy session", async () => {
|
||||||
|
const { getIronSession } = require("iron-session");
|
||||||
|
const session = { destroy: jest.fn() };
|
||||||
|
getIronSession.mockResolvedValue(session);
|
||||||
|
const req = {} as any;
|
||||||
|
const res = { json: jest.fn() } as any;
|
||||||
|
|
||||||
|
await controller.logout(req, res);
|
||||||
|
|
||||||
|
expect(session.destroy).toHaveBeenCalled();
|
||||||
|
expect(res.json).toHaveBeenCalledWith({ message: "User logged out" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
89
backend/src/auth/guards/auth.guard.spec.ts
Normal file
89
backend/src/auth/guards/auth.guard.spec.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { ExecutionContext, UnauthorizedException } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
|
import { getIronSession } from "iron-session";
|
||||||
|
import { JwtService } from "../../crypto/services/jwt.service";
|
||||||
|
import { AuthGuard } from "./auth.guard";
|
||||||
|
|
||||||
|
jest.mock("jose", () => ({}));
|
||||||
|
jest.mock("iron-session", () => ({
|
||||||
|
getIronSession: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("AuthGuard", () => {
|
||||||
|
let guard: AuthGuard;
|
||||||
|
let _jwtService: JwtService;
|
||||||
|
let _configService: ConfigService;
|
||||||
|
|
||||||
|
const mockJwtService = {
|
||||||
|
verifyJwt: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockConfigService = {
|
||||||
|
get: jest.fn().mockReturnValue("session-password"),
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
AuthGuard,
|
||||||
|
{ provide: JwtService, useValue: mockJwtService },
|
||||||
|
{ provide: ConfigService, useValue: mockConfigService },
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
guard = module.get<AuthGuard>(AuthGuard);
|
||||||
|
_jwtService = module.get<JwtService>(JwtService);
|
||||||
|
_configService = module.get<ConfigService>(ConfigService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return true for valid token", async () => {
|
||||||
|
const request = { user: null };
|
||||||
|
const context = {
|
||||||
|
switchToHttp: () => ({
|
||||||
|
getRequest: () => request,
|
||||||
|
getResponse: () => ({}),
|
||||||
|
}),
|
||||||
|
} as unknown as ExecutionContext;
|
||||||
|
|
||||||
|
(getIronSession as jest.Mock).mockResolvedValue({
|
||||||
|
accessToken: "valid-token",
|
||||||
|
});
|
||||||
|
mockJwtService.verifyJwt.mockResolvedValue({ sub: "user1" });
|
||||||
|
|
||||||
|
const result = await guard.canActivate(context);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(request.user).toEqual({ sub: "user1" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw UnauthorizedException if no token", async () => {
|
||||||
|
const context = {
|
||||||
|
switchToHttp: () => ({
|
||||||
|
getRequest: () => ({}),
|
||||||
|
getResponse: () => ({}),
|
||||||
|
}),
|
||||||
|
} as ExecutionContext;
|
||||||
|
|
||||||
|
(getIronSession as jest.Mock).mockResolvedValue({});
|
||||||
|
|
||||||
|
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||||
|
UnauthorizedException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw UnauthorizedException if token invalid", async () => {
|
||||||
|
const context = {
|
||||||
|
switchToHttp: () => ({
|
||||||
|
getRequest: () => ({}),
|
||||||
|
getResponse: () => ({}),
|
||||||
|
}),
|
||||||
|
} as ExecutionContext;
|
||||||
|
|
||||||
|
(getIronSession as jest.Mock).mockResolvedValue({ accessToken: "invalid" });
|
||||||
|
mockJwtService.verifyJwt.mockRejectedValue(new Error("invalid"));
|
||||||
|
|
||||||
|
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||||
|
UnauthorizedException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user