import { ConfigService } from "@nestjs/config"; import { Test, TestingModule } from "@nestjs/testing"; import * as Minio from "minio"; import { S3Service } from "./s3.service"; jest.mock("minio"); describe("S3Service", () => { let service: S3Service; let _configService: ConfigService; // biome-ignore lint/suspicious/noExplicitAny: Fine for testing purposes let minioClient: any; beforeEach(async () => { minioClient = { bucketExists: jest.fn().mockResolvedValue(true), makeBucket: jest.fn().mockResolvedValue(undefined), putObject: jest.fn().mockResolvedValue(undefined), getObject: jest.fn().mockResolvedValue({}), presignedUrl: jest.fn().mockResolvedValue("http://localhost/url"), removeObject: jest.fn().mockResolvedValue(undefined), statObject: jest.fn().mockResolvedValue({ size: 100, etag: "123" }), copyObject: jest.fn().mockResolvedValue(undefined), }; (Minio.Client as jest.Mock).mockImplementation(() => minioClient); const module: TestingModule = await Test.createTestingModule({ providers: [ S3Service, { provide: ConfigService, useValue: { get: jest.fn((key: string, defaultValue?: string) => { if (key === "S3_PORT") return "9000"; if (key === "S3_USE_SSL") return "false"; return defaultValue || key; }), }, }, ], }).compile(); service = module.get(S3Service); _configService = module.get(ConfigService); }); it("should be defined", () => { expect(service).toBeDefined(); }); describe("onModuleInit", () => { it("should create bucket if it does not exist", async () => { minioClient.bucketExists.mockResolvedValue(false); await service.onModuleInit(); expect(minioClient.makeBucket).toHaveBeenCalledWith("memegoat"); }); it("should not create bucket if it exists", async () => { minioClient.bucketExists.mockResolvedValue(true); await service.onModuleInit(); expect(minioClient.makeBucket).not.toHaveBeenCalled(); }); }); describe("uploadFile", () => { it("should upload a file to default bucket", async () => { const fileName = "test.txt"; const file = Buffer.from("test content"); const mimeType = "text/plain"; const result = await service.uploadFile(fileName, file, mimeType); expect(minioClient.putObject).toHaveBeenCalledWith( "memegoat", fileName, file, file.length, { "Content-Type": mimeType }, ); expect(result).toBe(fileName); }); it("should upload a file to specific bucket", async () => { const fileName = "test.txt"; const file = Buffer.from("test content"); const mimeType = "text/plain"; const bucketName = "other-bucket"; await service.uploadFile(fileName, file, mimeType, {}, bucketName); expect(minioClient.putObject).toHaveBeenCalledWith( bucketName, fileName, file, file.length, { "Content-Type": mimeType }, ); }); }); describe("getFile", () => { it("should get a file from default bucket", async () => { const fileName = "test.txt"; await service.getFile(fileName); expect(minioClient.getObject).toHaveBeenCalledWith("memegoat", fileName); }); it("should get a file from specific bucket", async () => { const fileName = "test.txt"; const bucketName = "other-bucket"; await service.getFile(fileName, bucketName); expect(minioClient.getObject).toHaveBeenCalledWith(bucketName, fileName); }); }); describe("getFileUrl", () => { it("should get a presigned URL from default bucket", async () => { const fileName = "test.txt"; const url = await service.getFileUrl(fileName); expect(minioClient.presignedUrl).toHaveBeenCalledWith( "GET", "memegoat", fileName, 3600, ); expect(url).toBe("http://localhost/url"); }); it("should get a presigned URL from specific bucket", async () => { const fileName = "test.txt"; const bucketName = "other-bucket"; await service.getFileUrl(fileName, 3600, bucketName); expect(minioClient.presignedUrl).toHaveBeenCalledWith( "GET", bucketName, fileName, 3600, ); }); }); describe("deleteFile", () => { it("should delete a file from default bucket", async () => { const fileName = "test.txt"; await service.deleteFile(fileName); expect(minioClient.removeObject).toHaveBeenCalledWith("memegoat", fileName); }); it("should delete a file from specific bucket", async () => { const fileName = "test.txt"; const bucketName = "other-bucket"; await service.deleteFile(fileName, bucketName); expect(minioClient.removeObject).toHaveBeenCalledWith(bucketName, fileName); }); }); describe("ensureBucketExists", () => { it("should create bucket if it does not exist", async () => { minioClient.bucketExists.mockResolvedValue(false); await service.ensureBucketExists("new-bucket"); expect(minioClient.makeBucket).toHaveBeenCalledWith("new-bucket"); }); it("should not create bucket if it exists", async () => { minioClient.bucketExists.mockResolvedValue(true); await service.ensureBucketExists("existing-bucket"); expect(minioClient.makeBucket).not.toHaveBeenCalled(); }); }); describe("getFileInfo", () => { it("should return file info from default bucket", async () => { const fileName = "test.txt"; const info = await service.getFileInfo(fileName); expect(minioClient.statObject).toHaveBeenCalledWith("memegoat", fileName); expect(info).toEqual({ size: 100, etag: "123" }); }); it("should return file info from specific bucket", async () => { const fileName = "test.txt"; const bucketName = "other-bucket"; await service.getFileInfo(fileName, bucketName); expect(minioClient.statObject).toHaveBeenCalledWith(bucketName, fileName); }); }); describe("moveFile", () => { it("should move file within default bucket", async () => { const source = "source.txt"; const dest = "dest.txt"; await service.moveFile(source, dest); expect(minioClient.copyObject).toHaveBeenCalledWith( "memegoat", dest, "/memegoat/source.txt", expect.any(Minio.CopyConditions), ); expect(minioClient.removeObject).toHaveBeenCalledWith("memegoat", source); }); it("should move file between different buckets", async () => { const source = "source.txt"; const dest = "dest.txt"; const sBucket = "source-bucket"; const dBucket = "dest-bucket"; await service.moveFile(source, dest, sBucket, dBucket); expect(minioClient.copyObject).toHaveBeenCalledWith( dBucket, dest, `/${sBucket}/${source}`, expect.any(Minio.CopyConditions), ); expect(minioClient.removeObject).toHaveBeenCalledWith(sBucket, source); }); }); });