Introduce repository pattern across multiple services, including `favorites`, `tags`, `sessions`, `reports`, `auth`, and more. Decouple crypto functionalities into modular services like `HashingService`, `JwtService`, and `EncryptionService`. Improve testability and maintainability by simplifying dependencies and consolidating utility logic.
100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
import * as fs from "node:fs/promises";
|
|
import { BadRequestException, Logger } from "@nestjs/common";
|
|
import { ConfigService } from "@nestjs/config";
|
|
import { Test, TestingModule } from "@nestjs/testing";
|
|
import ffmpeg from "fluent-ffmpeg";
|
|
import sharp from "sharp";
|
|
import { MediaService } from "./media.service";
|
|
|
|
import { ImageProcessorStrategy } from "./strategies/image-processor.strategy";
|
|
import { VideoProcessorStrategy } from "./strategies/video-processor.strategy";
|
|
|
|
jest.mock("sharp");
|
|
jest.mock("fluent-ffmpeg");
|
|
jest.mock("node:fs/promises");
|
|
jest.mock("uuid", () => ({ v4: () => "mock-uuid" }));
|
|
|
|
describe("MediaService", () => {
|
|
let service: MediaService;
|
|
|
|
const mockSharp = {
|
|
metadata: jest.fn().mockResolvedValue({ width: 100, height: 100 }),
|
|
webp: jest.fn().mockReturnThis(),
|
|
toBuffer: jest.fn().mockResolvedValue(Buffer.from("processed")),
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
jest.clearAllMocks();
|
|
jest.spyOn(Logger.prototype, "error").mockImplementation(() => {});
|
|
jest.spyOn(Logger.prototype, "warn").mockImplementation(() => {});
|
|
(sharp as unknown as jest.Mock).mockReturnValue(mockSharp);
|
|
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
MediaService,
|
|
ImageProcessorStrategy,
|
|
VideoProcessorStrategy,
|
|
{
|
|
provide: ConfigService,
|
|
useValue: {
|
|
get: jest.fn((key) => {
|
|
if (key === "CLAMAV_HOST") return "localhost";
|
|
if (key === "CLAMAV_PORT") return 3310;
|
|
}),
|
|
},
|
|
},
|
|
],
|
|
}).compile();
|
|
|
|
service = module.get<MediaService>(MediaService);
|
|
});
|
|
|
|
it("should be defined", () => {
|
|
expect(service).toBeDefined();
|
|
});
|
|
|
|
describe("processImage", () => {
|
|
it("should process an image to webp", async () => {
|
|
const result = await service.processImage(Buffer.from("input"), "webp");
|
|
expect(result.extension).toBe("webp");
|
|
expect(result.buffer).toEqual(Buffer.from("processed"));
|
|
expect(mockSharp.webp).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should throw BadRequestException on error", async () => {
|
|
mockSharp.toBuffer.mockRejectedValue(new Error("Sharp error"));
|
|
await expect(service.processImage(Buffer.from("input"))).rejects.toThrow(
|
|
BadRequestException,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("processVideo", () => {
|
|
it("should process a video", async () => {
|
|
const mockFfmpegCommand = {
|
|
toFormat: jest.fn().mockReturnThis(),
|
|
videoCodec: jest.fn().mockReturnThis(),
|
|
audioCodec: jest.fn().mockReturnThis(),
|
|
outputOptions: jest.fn().mockReturnThis(),
|
|
on: jest.fn().mockImplementation(function (event, cb) {
|
|
if (event === "end") setTimeout(cb, 0);
|
|
return this;
|
|
}),
|
|
save: jest.fn().mockReturnThis(),
|
|
};
|
|
(ffmpeg as unknown as jest.Mock).mockReturnValue(mockFfmpegCommand);
|
|
(fs.readFile as jest.Mock).mockResolvedValue(Buffer.from("processed-video"));
|
|
(fs.writeFile as jest.Mock).mockResolvedValue(undefined);
|
|
(fs.unlink as jest.Mock).mockResolvedValue(undefined);
|
|
|
|
const result = await service.processVideo(
|
|
Buffer.from("video-input"),
|
|
"webm",
|
|
);
|
|
|
|
expect(result.extension).toBe("webm");
|
|
expect(result.buffer).toEqual(Buffer.from("processed-video"));
|
|
});
|
|
});
|
|
});
|