test(contents): add unit tests for ContentsController and ContentsService with mocked dependencies
This commit is contained in:
230
backend/src/contents/contents.controller.spec.ts
Normal file
230
backend/src/contents/contents.controller.spec.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
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 { CACHE_MANAGER } from "@nestjs/cache-manager";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||
import { OptionalAuthGuard } from "../auth/guards/optional-auth.guard";
|
||||
import { RolesGuard } from "../auth/guards/roles.guard";
|
||||
import { AuthenticatedRequest } from "../common/interfaces/request.interface";
|
||||
import { ContentsController } from "./contents.controller";
|
||||
import { ContentsService } from "./contents.service";
|
||||
|
||||
describe("ContentsController", () => {
|
||||
let controller: ContentsController;
|
||||
let service: ContentsService;
|
||||
|
||||
const mockContentsService = {
|
||||
create: jest.fn(),
|
||||
getUploadUrl: jest.fn(),
|
||||
uploadAndProcess: jest.fn(),
|
||||
findAll: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
incrementViews: jest.fn(),
|
||||
incrementUsage: jest.fn(),
|
||||
remove: jest.fn(),
|
||||
removeAdmin: jest.fn(),
|
||||
generateBotHtml: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCacheManager = {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ContentsController],
|
||||
providers: [
|
||||
{ provide: ContentsService, useValue: mockContentsService },
|
||||
{ provide: CACHE_MANAGER, useValue: mockCacheManager },
|
||||
],
|
||||
})
|
||||
.overrideGuard(AuthGuard)
|
||||
.useValue({ canActivate: () => true })
|
||||
.overrideGuard(RolesGuard)
|
||||
.useValue({ canActivate: () => true })
|
||||
.overrideGuard(OptionalAuthGuard)
|
||||
.useValue({ canActivate: () => true })
|
||||
.compile();
|
||||
|
||||
controller = module.get<ContentsController>(ContentsController);
|
||||
service = module.get<ContentsService>(ContentsService);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
|
||||
describe("create", () => {
|
||||
it("should call service.create", async () => {
|
||||
const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest;
|
||||
const dto = { title: "Title", type: "image" as any };
|
||||
await controller.create(req, dto as any);
|
||||
expect(service.create).toHaveBeenCalledWith("user-uuid", dto);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUploadUrl", () => {
|
||||
it("should call service.getUploadUrl", async () => {
|
||||
const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest;
|
||||
await controller.getUploadUrl(req, "test.jpg");
|
||||
expect(service.getUploadUrl).toHaveBeenCalledWith("user-uuid", "test.jpg");
|
||||
});
|
||||
});
|
||||
|
||||
describe("upload", () => {
|
||||
it("should call service.uploadAndProcess", async () => {
|
||||
const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest;
|
||||
const file = {} as Express.Multer.File;
|
||||
const dto = { title: "Title" };
|
||||
await controller.upload(req, file, dto as any);
|
||||
expect(service.uploadAndProcess).toHaveBeenCalledWith(
|
||||
"user-uuid",
|
||||
file,
|
||||
dto,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("explore", () => {
|
||||
it("should call service.findAll", async () => {
|
||||
const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest;
|
||||
await controller.explore(
|
||||
req,
|
||||
10,
|
||||
0,
|
||||
"trend",
|
||||
"tag",
|
||||
"cat",
|
||||
"auth",
|
||||
"query",
|
||||
false,
|
||||
undefined,
|
||||
);
|
||||
expect(service.findAll).toHaveBeenCalledWith({
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
sortBy: "trend",
|
||||
tag: "tag",
|
||||
category: "cat",
|
||||
author: "auth",
|
||||
query: "query",
|
||||
favoritesOnly: false,
|
||||
userId: "user-uuid",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("trends", () => {
|
||||
it("should call service.findAll with trend sort", async () => {
|
||||
const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest;
|
||||
await controller.trends(req, 10, 0);
|
||||
expect(service.findAll).toHaveBeenCalledWith({
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
sortBy: "trend",
|
||||
userId: "user-uuid",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("recent", () => {
|
||||
it("should call service.findAll with recent sort", async () => {
|
||||
const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest;
|
||||
await controller.recent(req, 10, 0);
|
||||
expect(service.findAll).toHaveBeenCalledWith({
|
||||
limit: 10,
|
||||
offset: 0,
|
||||
sortBy: "recent",
|
||||
userId: "user-uuid",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findOne", () => {
|
||||
it("should return json for normal user", async () => {
|
||||
const req = { user: { sub: "user-uuid" }, headers: {} } as any;
|
||||
const res = { json: jest.fn(), send: jest.fn() } as any;
|
||||
const content = { id: "1" };
|
||||
mockContentsService.findOne.mockResolvedValue(content);
|
||||
|
||||
await controller.findOne("1", req, res);
|
||||
|
||||
expect(res.json).toHaveBeenCalledWith(content);
|
||||
});
|
||||
|
||||
it("should return html for bot", async () => {
|
||||
const req = {
|
||||
user: { sub: "user-uuid" },
|
||||
headers: { "user-agent": "Googlebot" },
|
||||
} as any;
|
||||
const res = { json: jest.fn(), send: jest.fn() } as any;
|
||||
const content = { id: "1" };
|
||||
mockContentsService.findOne.mockResolvedValue(content);
|
||||
mockContentsService.generateBotHtml.mockReturnValue("<html></html>");
|
||||
|
||||
await controller.findOne("1", req, res);
|
||||
|
||||
expect(res.send).toHaveBeenCalledWith("<html></html>");
|
||||
});
|
||||
|
||||
it("should throw NotFoundException if not found", async () => {
|
||||
const req = { user: { sub: "user-uuid" }, headers: {} } as any;
|
||||
const res = { json: jest.fn(), send: jest.fn() } as any;
|
||||
mockContentsService.findOne.mockResolvedValue(null);
|
||||
|
||||
await expect(controller.findOne("1", req, res)).rejects.toThrow(
|
||||
"Contenu non trouvé",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("incrementViews", () => {
|
||||
it("should call service.incrementViews", async () => {
|
||||
await controller.incrementViews("1");
|
||||
expect(service.incrementViews).toHaveBeenCalledWith("1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("incrementUsage", () => {
|
||||
it("should call service.incrementUsage", async () => {
|
||||
await controller.incrementUsage("1");
|
||||
expect(service.incrementUsage).toHaveBeenCalledWith("1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("remove", () => {
|
||||
it("should call service.remove", async () => {
|
||||
const req = { user: { sub: "user-uuid" } } as AuthenticatedRequest;
|
||||
await controller.remove("1", req);
|
||||
expect(service.remove).toHaveBeenCalledWith("1", "user-uuid");
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeAdmin", () => {
|
||||
it("should call service.removeAdmin", async () => {
|
||||
await controller.removeAdmin("1");
|
||||
expect(service.removeAdmin).toHaveBeenCalledWith("1");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,6 +23,7 @@ describe("ContentsService", () => {
|
||||
incrementViews: jest.fn(),
|
||||
incrementUsage: jest.fn(),
|
||||
softDelete: jest.fn(),
|
||||
softDeleteAdmin: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
findBySlug: jest.fn(),
|
||||
};
|
||||
@@ -147,4 +148,81 @@ describe("ContentsService", () => {
|
||||
expect(result[0].views).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("incrementUsage", () => {
|
||||
it("should increment usage", async () => {
|
||||
mockContentsRepository.incrementUsage.mockResolvedValue([
|
||||
{ id: "1", usageCount: 1 },
|
||||
]);
|
||||
await service.incrementUsage("1");
|
||||
expect(mockContentsRepository.incrementUsage).toHaveBeenCalledWith("1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("remove", () => {
|
||||
it("should soft delete content", async () => {
|
||||
mockContentsRepository.softDelete.mockResolvedValue({ id: "1" });
|
||||
await service.remove("1", "u1");
|
||||
expect(mockContentsRepository.softDelete).toHaveBeenCalledWith("1", "u1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeAdmin", () => {
|
||||
it("should soft delete content without checking owner", async () => {
|
||||
mockContentsRepository.softDeleteAdmin.mockResolvedValue({ id: "1" });
|
||||
await service.removeAdmin("1");
|
||||
expect(mockContentsRepository.softDeleteAdmin).toHaveBeenCalledWith("1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("findOne", () => {
|
||||
it("should return content by id", async () => {
|
||||
mockContentsRepository.findOne.mockResolvedValue({
|
||||
id: "1",
|
||||
storageKey: "k",
|
||||
author: { avatarUrl: "a" },
|
||||
});
|
||||
mockS3Service.getPublicUrl.mockReturnValue("url");
|
||||
const result = await service.findOne("1");
|
||||
expect(result.id).toBe("1");
|
||||
expect(result.url).toBe("url");
|
||||
});
|
||||
|
||||
it("should return content by slug", async () => {
|
||||
mockContentsRepository.findOne.mockResolvedValue({
|
||||
id: "1",
|
||||
slug: "s",
|
||||
storageKey: "k",
|
||||
});
|
||||
const result = await service.findOne("s");
|
||||
expect(result.slug).toBe("s");
|
||||
});
|
||||
});
|
||||
|
||||
describe("generateBotHtml", () => {
|
||||
it("should generate html with og tags", () => {
|
||||
const content = { title: "Title", storageKey: "k" };
|
||||
mockS3Service.getPublicUrl.mockReturnValue("url");
|
||||
const html = service.generateBotHtml(content as any);
|
||||
expect(html).toContain("<title>Title</title>");
|
||||
expect(html).toContain('content="Title"');
|
||||
expect(html).toContain('content="url"');
|
||||
});
|
||||
});
|
||||
|
||||
describe("ensureUniqueSlug", () => {
|
||||
it("should return original slug if unique", async () => {
|
||||
mockContentsRepository.findBySlug.mockResolvedValue(null);
|
||||
const slug = (service as any).ensureUniqueSlug("My Title");
|
||||
await expect(slug).resolves.toBe("my-title");
|
||||
});
|
||||
|
||||
it("should append counter if not unique", async () => {
|
||||
mockContentsRepository.findBySlug
|
||||
.mockResolvedValueOnce({ id: "1" })
|
||||
.mockResolvedValueOnce(null);
|
||||
const slug = await (service as any).ensureUniqueSlug("My Title");
|
||||
expect(slug).toBe("my-title-1");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user