fix test
This commit is contained in:
@@ -3,13 +3,6 @@ import multer from "multer";
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { asyncHandler } from "./middleware/asyncHandler";
|
import { asyncHandler } from "./middleware/asyncHandler";
|
||||||
import { badRequest, created, forbidden, notFound, ok } from "./utils/responses";
|
import { badRequest, created, forbidden, notFound, ok } from "./utils/responses";
|
||||||
import { vi } from "vitest";
|
|
||||||
|
|
||||||
vi.mock("../../src/services/db/UserService", () => ({
|
|
||||||
UserService: {
|
|
||||||
create: vi.fn(),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export class RestStorage {
|
export class RestStorage {
|
||||||
constructor(private readonly s3Service: S3Service) {}
|
constructor(private readonly s3Service: S3Service) {}
|
||||||
@@ -77,7 +70,6 @@ export class RestStorage {
|
|||||||
router.delete(
|
router.delete(
|
||||||
/\/files\/(.*)/,
|
/\/files\/(.*)/,
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
// <-- ÄNDERUNG HIER
|
|
||||||
const userId = req.payload.uuid;
|
const userId = req.payload.uuid;
|
||||||
const objectKey = req.params[0];
|
const objectKey = req.params[0];
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class SpotifyPollingService {
|
|||||||
if (activePolls.has(uuid)) return;
|
if (activePolls.has(uuid)) return;
|
||||||
|
|
||||||
console.log(`[SpotifyPolling] Starting polling for user ${uuid}`);
|
console.log(`[SpotifyPolling] Starting polling for user ${uuid}`);
|
||||||
const intervalId = setInterval(() => this._pollUser(uuid), 3000); // Sicherer 3-Sekunden-Intervall
|
const intervalId = setInterval(() => this._pollUser(uuid), 3000);
|
||||||
activePolls.set(uuid, intervalId);
|
activePolls.set(uuid, intervalId);
|
||||||
|
|
||||||
this._pollUser(uuid);
|
this._pollUser(uuid);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {describe, it, expect, vi, beforeEach, afterEach} from "vitest";
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
import request from "supertest";
|
import request from "supertest";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import {RestStorage} from "../../src/rest/restStorage";
|
import { RestStorage } from "../../src/rest/restStorage";
|
||||||
import {S3Service} from "../../src/services/s3Service";
|
import { S3Service } from "../../src/services/s3Service";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import {createMockS3Service, createTestApp} from "../helpers/testSetup";
|
import { createMockS3Service, createTestApp } from "../helpers/testSetup";
|
||||||
|
|
||||||
vi.mock("../../src/services/s3Service");
|
vi.mock("../../src/services/s3Service");
|
||||||
|
|
||||||
@@ -25,9 +25,8 @@ describe("RestStorage", () => {
|
|||||||
app = createTestApp(restStorage.createRouter(), "/storage", {
|
app = createTestApp(restStorage.createRouter(), "/storage", {
|
||||||
uuid: requestingUserUUID,
|
uuid: requestingUserUUID,
|
||||||
name: "name",
|
name: "name",
|
||||||
id: "1234"
|
id: "1234",
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -39,9 +38,9 @@ describe("RestStorage", () => {
|
|||||||
const objectKey = `user-${requestingUserUUID}/generated-uuid.jpg`;
|
const objectKey = `user-${requestingUserUUID}/generated-uuid.jpg`;
|
||||||
mockS3Service.uploadFile.mockResolvedValue(objectKey);
|
mockS3Service.uploadFile.mockResolvedValue(objectKey);
|
||||||
|
|
||||||
const response = await request(app) // Verwende die 'app' aus dem beforeEach
|
const response = await request(app)
|
||||||
.post("/storage/upload")
|
.post("/storage/upload")
|
||||||
.attach('image', Buffer.from("fake image data"), "test.jpg")
|
.attach("image", Buffer.from("fake image data"), "test.jpg")
|
||||||
.expect(201);
|
.expect(201);
|
||||||
|
|
||||||
expect(response.body.data).toEqual({
|
expect(response.body.data).toEqual({
|
||||||
@@ -52,16 +51,14 @@ describe("RestStorage", () => {
|
|||||||
expect(mockS3Service.uploadFile).toHaveBeenCalledOnce();
|
expect(mockS3Service.uploadFile).toHaveBeenCalledOnce();
|
||||||
expect(mockS3Service.uploadFile).toHaveBeenCalledWith(
|
expect(mockS3Service.uploadFile).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
originalname: 'test.jpg'
|
originalname: "test.jpg",
|
||||||
}),
|
}),
|
||||||
requestingUserUUID
|
requestingUserUUID
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return a 400 Bad Request if no file is provided", async () => {
|
it("should return a 400 Bad Request if no file is provided", async () => {
|
||||||
const response = await request(app)
|
const response = await request(app).post("/storage/upload").expect(400);
|
||||||
.post("/storage/upload")
|
|
||||||
.expect(400);
|
|
||||||
|
|
||||||
expect(response.body.data.message).toBe("No file provided.");
|
expect(response.body.data.message).toBe("No file provided.");
|
||||||
expect(mockS3Service.uploadFile).not.toHaveBeenCalled();
|
expect(mockS3Service.uploadFile).not.toHaveBeenCalled();
|
||||||
@@ -71,14 +68,12 @@ describe("RestStorage", () => {
|
|||||||
describe("GET /files", () => {
|
describe("GET /files", () => {
|
||||||
it("should return a list of files for the current user", async () => {
|
it("should return a list of files for the current user", async () => {
|
||||||
const mockFiles = [
|
const mockFiles = [
|
||||||
{key: `user-${requestingUserUUID}/file1.txt`, lastModified: new Date()},
|
{ key: `user-${requestingUserUUID}/file1.txt`, lastModified: new Date() },
|
||||||
{key: `user-${requestingUserUUID}/image.png`, lastModified: new Date()},
|
{ key: `user-${requestingUserUUID}/image.png`, lastModified: new Date() },
|
||||||
];
|
];
|
||||||
mockS3Service.listFilesForUser.mockResolvedValue(mockFiles);
|
mockS3Service.listFilesForUser.mockResolvedValue(mockFiles);
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app).get("/storage/files").expect(200);
|
||||||
.get("/storage/files")
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const responseData = JSON.parse(JSON.stringify(mockFiles));
|
const responseData = JSON.parse(JSON.stringify(mockFiles));
|
||||||
expect(response.body.data).toEqual(responseData);
|
expect(response.body.data).toEqual(responseData);
|
||||||
@@ -87,7 +82,7 @@ describe("RestStorage", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("GET /files/:key/url", () => { // Beschreibung an die Logik angepasst
|
describe("GET /files/:key/url", () => {
|
||||||
it("should return a signed URL for a file owned by the user", async () => {
|
it("should return a signed URL for a file owned by the user", async () => {
|
||||||
const objectKey = `user-${requestingUserUUID}/my-photo.jpg`;
|
const objectKey = `user-${requestingUserUUID}/my-photo.jpg`;
|
||||||
const signedUrl = "http://s3.com/signed-url";
|
const signedUrl = "http://s3.com/signed-url";
|
||||||
@@ -97,7 +92,7 @@ describe("RestStorage", () => {
|
|||||||
.get(`/storage/files/${encodeURIComponent(objectKey)}/url`)
|
.get(`/storage/files/${encodeURIComponent(objectKey)}/url`)
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.data).toEqual({url: signedUrl});
|
expect(response.body.data).toEqual({ url: signedUrl });
|
||||||
expect(mockS3Service.getSignedDownloadUrl).toHaveBeenCalledOnce();
|
expect(mockS3Service.getSignedDownloadUrl).toHaveBeenCalledOnce();
|
||||||
expect(mockS3Service.getSignedDownloadUrl).toHaveBeenCalledWith(objectKey, 60);
|
expect(mockS3Service.getSignedDownloadUrl).toHaveBeenCalledWith(objectKey, 60);
|
||||||
});
|
});
|
||||||
@@ -114,7 +109,7 @@ describe("RestStorage", () => {
|
|||||||
|
|
||||||
it("should return 404 Not Found if the file does not exist", async () => {
|
it("should return 404 Not Found if the file does not exist", async () => {
|
||||||
const objectKey = `user-${requestingUserUUID}/non-existent.jpg`;
|
const objectKey = `user-${requestingUserUUID}/non-existent.jpg`;
|
||||||
mockS3Service.getSignedDownloadUrl.mockRejectedValue({name: "NoSuchKey"});
|
mockS3Service.getSignedDownloadUrl.mockRejectedValue({ name: "NoSuchKey" });
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.get(`/storage/files/${encodeURIComponent(objectKey)}/url`)
|
.get(`/storage/files/${encodeURIComponent(objectKey)}/url`)
|
||||||
@@ -124,7 +119,7 @@ describe("RestStorage", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("DELETE /files/:key", () => { // Beschreibung an die Logik angepasst
|
describe("DELETE /files/:key", () => {
|
||||||
it("should delete a file owned by the user", async () => {
|
it("should delete a file owned by the user", async () => {
|
||||||
const objectKey = `user-${requestingUserUUID}/file-to-delete.pdf`;
|
const objectKey = `user-${requestingUserUUID}/file-to-delete.pdf`;
|
||||||
mockS3Service.deleteFile.mockResolvedValue(undefined);
|
mockS3Service.deleteFile.mockResolvedValue(undefined);
|
||||||
@@ -148,4 +143,4 @@ describe("RestStorage", () => {
|
|||||||
expect(mockS3Service.deleteFile).not.toHaveBeenCalled();
|
expect(mockS3Service.deleteFile).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
import mongoose from 'mongoose';
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
vi.mock('mongoose', async (importOriginal) => {
|
vi.mock("mongoose", async (importOriginal) => {
|
||||||
const originalMongoose = await importOriginal<typeof mongoose>();
|
const originalMongoose = await importOriginal<typeof mongoose>();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -20,9 +20,7 @@ const mockedMongooseConnect = vi.mocked(mongoose.connect);
|
|||||||
const mockedMongooseDisconnect = vi.mocked(mongoose.disconnect);
|
const mockedMongooseDisconnect = vi.mocked(mongoose.disconnect);
|
||||||
const mockedConnectionOn = vi.mocked(mongoose.connection.on);
|
const mockedConnectionOn = vi.mocked(mongoose.connection.on);
|
||||||
|
|
||||||
|
describe("database.service", () => {
|
||||||
describe('database.service', () => {
|
|
||||||
|
|
||||||
let connectToDatabase: any;
|
let connectToDatabase: any;
|
||||||
let disconnectFromDatabase: any;
|
let disconnectFromDatabase: any;
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ describe('database.service', () => {
|
|||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|
||||||
const dbService = await import('../../../src/services/db/database.service');
|
const dbService = await import("../../../src/services/db/database.service");
|
||||||
connectToDatabase = dbService.connectToDatabase;
|
connectToDatabase = dbService.connectToDatabase;
|
||||||
disconnectFromDatabase = dbService.disconnectFromDatabase;
|
disconnectFromDatabase = dbService.disconnectFromDatabase;
|
||||||
});
|
});
|
||||||
@@ -39,37 +37,39 @@ describe('database.service', () => {
|
|||||||
vi.restoreAllMocks();
|
vi.restoreAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
const TEST_DB_NAME = 'testdb';
|
const TEST_DB_NAME = "testdb";
|
||||||
const TEST_DB_CONN_STRING = 'mongodb://test-host/testdb';
|
const TEST_DB_CONN_STRING = "mongodb://test-host/testdb";
|
||||||
|
|
||||||
describe('connectToDatabase', () => {
|
describe("connectToDatabase", () => {
|
||||||
it('should attempt to connect to MongoDB with correct options', async () => {
|
it("should attempt to connect to MongoDB with correct options", async () => {
|
||||||
mockedMongooseConnect.mockResolvedValue(undefined as any);
|
mockedMongooseConnect.mockResolvedValue(undefined as any);
|
||||||
|
|
||||||
await connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
await connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
||||||
|
|
||||||
expect(mockedMongooseConnect).toHaveBeenCalledOnce();
|
expect(mockedMongooseConnect).toHaveBeenCalledOnce();
|
||||||
expect(mockedMongooseConnect).toHaveBeenCalledWith(TEST_DB_CONN_STRING, expect.objectContaining({
|
expect(mockedMongooseConnect).toHaveBeenCalledWith(
|
||||||
dbName: TEST_DB_NAME,
|
TEST_DB_CONN_STRING,
|
||||||
family: 4,
|
expect.objectContaining({
|
||||||
}));
|
dbName: TEST_DB_NAME,
|
||||||
|
family: 4,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correctly set up event listeners on the connection', async () => {
|
it("should correctly set up event listeners on the connection", async () => {
|
||||||
mockedMongooseConnect.mockResolvedValue(undefined as any);
|
mockedMongooseConnect.mockResolvedValue(undefined as any);
|
||||||
|
|
||||||
await connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
await connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
||||||
|
|
||||||
expect(mockedConnectionOn).toHaveBeenCalledWith('connected', expect.any(Function));
|
expect(mockedConnectionOn).toHaveBeenCalledWith("connected", expect.any(Function));
|
||||||
expect(mockedConnectionOn).toHaveBeenCalledWith('disconnected', expect.any(Function));
|
expect(mockedConnectionOn).toHaveBeenCalledWith("disconnected", expect.any(Function));
|
||||||
expect(mockedConnectionOn).toHaveBeenCalledWith('error', expect.any(Function));
|
expect(mockedConnectionOn).toHaveBeenCalledWith("error", expect.any(Function));
|
||||||
expect(mockedConnectionOn).toHaveBeenCalledTimes(3);
|
expect(mockedConnectionOn).toHaveBeenCalledTimes(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only attempt to connect once when called multiple times (singleton pattern)', async () => {
|
it("should only attempt to connect once when called multiple times (singleton pattern)", async () => {
|
||||||
mockedMongooseConnect.mockResolvedValue(undefined as any);
|
mockedMongooseConnect.mockResolvedValue(undefined as any);
|
||||||
|
|
||||||
// Rufe die Funktion mehrmals parallel auf
|
|
||||||
const promise1 = connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
const promise1 = connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
||||||
const promise2 = connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
const promise2 = connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ describe('database.service', () => {
|
|||||||
expect(mockedMongooseConnect).toHaveBeenCalledOnce();
|
expect(mockedMongooseConnect).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Retry Logic', () => {
|
describe("Retry Logic", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
});
|
});
|
||||||
@@ -86,20 +86,21 @@ describe('database.service', () => {
|
|||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should retry connecting after a 5-second delay if the first attempt fails', async () => {
|
it("should retry connecting after a 5-second delay if the first attempt fails", async () => {
|
||||||
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||||
const connectionError = new Error('Database unavailable');
|
const connectionError = new Error("Database unavailable");
|
||||||
|
|
||||||
mockedMongooseConnect
|
mockedMongooseConnect.mockRejectedValueOnce(connectionError).mockResolvedValueOnce(undefined as any);
|
||||||
.mockRejectedValueOnce(connectionError)
|
|
||||||
.mockResolvedValueOnce(undefined as any);
|
|
||||||
|
|
||||||
const connectionPromise = connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
const connectionPromise = connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
||||||
|
|
||||||
await vi.runAllTicks();
|
await vi.runAllTicks();
|
||||||
|
|
||||||
expect(mockedMongooseConnect).toHaveBeenCalledOnce();
|
expect(mockedMongooseConnect).toHaveBeenCalledOnce();
|
||||||
expect(consoleErrorSpy).toHaveBeenCalledWith("Failed to connect to MongoDB. Retrying in 5 seconds...", connectionError);
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||||
|
"Failed to connect to MongoDB. Retrying in 5 seconds...",
|
||||||
|
connectionError
|
||||||
|
);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(5000);
|
await vi.advanceTimersByTimeAsync(5000);
|
||||||
|
|
||||||
@@ -111,15 +112,15 @@ describe('database.service', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('disconnectFromDatabase', () => {
|
describe("disconnectFromDatabase", () => {
|
||||||
it('should call mongoose.disconnect if the connection is established', async () => {
|
it("should call mongoose.disconnect if the connection is established", async () => {
|
||||||
mockedMongooseConnect.mockResolvedValue(undefined as any);
|
mockedMongooseConnect.mockResolvedValue(undefined as any);
|
||||||
mockedMongooseDisconnect.mockResolvedValue(undefined as any);
|
mockedMongooseDisconnect.mockResolvedValue(undefined as any);
|
||||||
|
|
||||||
await connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
await connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING);
|
||||||
|
|
||||||
const connectedCallback = mockedConnectionOn.mock.calls.find(call => call[0] === 'connected')?.[1];
|
const connectedCallback = mockedConnectionOn.mock.calls.find((call) => call[0] === "connected")?.[1];
|
||||||
if (typeof connectedCallback === 'function') {
|
if (typeof connectedCallback === "function") {
|
||||||
connectedCallback();
|
connectedCallback();
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Connected callback was not found or is not a function");
|
throw new Error("Connected callback was not found or is not a function");
|
||||||
@@ -130,10 +131,10 @@ describe('database.service', () => {
|
|||||||
expect(mockedMongooseDisconnect).toHaveBeenCalledOnce();
|
expect(mockedMongooseDisconnect).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not call mongoose.disconnect if the connection was never established', async () => {
|
it("should not call mongoose.disconnect if the connection was never established", async () => {
|
||||||
await disconnectFromDatabase();
|
await disconnectFromDatabase();
|
||||||
|
|
||||||
expect(mockedMongooseDisconnect).not.toHaveBeenCalled();
|
expect(mockedMongooseDisconnect).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,70 +1,71 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
|
||||||
vi.mock('@aws-sdk/s3-request-presigner');
|
vi.mock("@aws-sdk/s3-request-presigner");
|
||||||
|
|
||||||
vi.mock('@aws-sdk/client-s3', async (importOriginal) => {
|
vi.mock("@aws-sdk/client-s3", async (importOriginal) => {
|
||||||
const originalModule = await importOriginal<typeof import('@aws-sdk/client-s3')>();
|
const originalModule = await importOriginal<typeof import("@aws-sdk/client-s3")>();
|
||||||
return {
|
return {
|
||||||
...originalModule,
|
...originalModule,
|
||||||
S3Client: vi.fn(),
|
S3Client: vi.fn(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
import { S3Service, S3ClientConfig } from '../../src/services/s3Service';
|
import { S3Service, S3ClientConfig } from "../../src/services/s3Service";
|
||||||
import {
|
import {
|
||||||
S3Client,
|
S3Client,
|
||||||
CreateBucketCommand,
|
CreateBucketCommand,
|
||||||
PutObjectCommand,
|
PutObjectCommand,
|
||||||
GetObjectCommand,
|
GetObjectCommand,
|
||||||
ListObjectsV2Command,
|
ListObjectsV2Command,
|
||||||
DeleteObjectCommand
|
DeleteObjectCommand,
|
||||||
} from '@aws-sdk/client-s3';import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
} from "@aws-sdk/client-s3";
|
||||||
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||||
|
|
||||||
const testConfig: S3ClientConfig = {
|
const testConfig: S3ClientConfig = {
|
||||||
endpoint: 'http://test-minio',
|
endpoint: "http://test-minio",
|
||||||
port: 9000,
|
port: 9000,
|
||||||
accessKey: 'test-key',
|
accessKey: "test-key",
|
||||||
secretAccessKey: 'test-secret',
|
secretAccessKey: "test-secret",
|
||||||
bucket: 'test-bucket',
|
bucket: "test-bucket",
|
||||||
};
|
};
|
||||||
|
|
||||||
const MockS3Client = vi.mocked(S3Client);
|
const MockS3Client = vi.mocked(S3Client);
|
||||||
const mockSend = vi.fn(); // Das ist unser gefälschter "send"-Befehl
|
const mockSend = vi.fn();
|
||||||
const mockGetSignedUrl = vi.mocked(getSignedUrl);
|
const mockGetSignedUrl = vi.mocked(getSignedUrl);
|
||||||
|
|
||||||
describe('S3Service', () => {
|
describe("S3Service", () => {
|
||||||
let s3Service: S3Service;
|
let s3Service: S3Service;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|
||||||
// @ts-ignore
|
MockS3Client.mockImplementation(
|
||||||
S3Service.instance = undefined;
|
() =>
|
||||||
|
({
|
||||||
MockS3Client.mockImplementation(() => ({
|
send: mockSend,
|
||||||
send: mockSend,
|
}) as never
|
||||||
}) as any);
|
);
|
||||||
|
|
||||||
s3Service = S3Service.getInstance(testConfig);
|
s3Service = S3Service.getInstance(testConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Initialization and Bucket Creation', () => {
|
describe("Initialization and Bucket Creation", () => {
|
||||||
it('should create a singleton instance correctly', () => {
|
it("should create a singleton instance correctly", () => {
|
||||||
const instance1 = S3Service.getInstance(testConfig);
|
const instance1 = S3Service.getInstance(testConfig);
|
||||||
const instance2 = S3Service.getInstance(); // Ohne Config, da schon initialisiert
|
const instance2 = S3Service.getInstance();
|
||||||
expect(instance1).toBe(instance2);
|
expect(instance1).toBe(instance2);
|
||||||
expect(MockS3Client).toHaveBeenCalledOnce();
|
expect(MockS3Client).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call ensureBucketExists and handle existing buckets gracefully', async () => {
|
it("should call ensureBucketExists and handle existing buckets gracefully", async () => {
|
||||||
mockSend.mockRejectedValue({ name: 'BucketAlreadyOwnedByYou' });
|
mockSend.mockRejectedValue({ name: "BucketAlreadyOwnedByYou" });
|
||||||
|
|
||||||
await expect(s3Service.ensureBucketExists()).resolves.toBeUndefined();
|
await expect(s3Service.ensureBucketExists()).resolves.toBeUndefined();
|
||||||
|
|
||||||
expect(mockSend).toHaveBeenCalledWith(expect.any(CreateBucketCommand));
|
expect(mockSend).toHaveBeenCalledWith(expect.any(CreateBucketCommand));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new bucket if it does not exist', async () => {
|
it("should create a new bucket if it does not exist", async () => {
|
||||||
mockSend.mockResolvedValue({});
|
mockSend.mockResolvedValue({});
|
||||||
|
|
||||||
await expect(s3Service.ensureBucketExists()).resolves.toBeUndefined();
|
await expect(s3Service.ensureBucketExists()).resolves.toBeUndefined();
|
||||||
@@ -73,17 +74,17 @@ describe('S3Service', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('uploadFile', () => {
|
describe("uploadFile", () => {
|
||||||
it('should upload a file and return the correct object key', async () => {
|
it("should upload a file and return the correct object key", async () => {
|
||||||
const mockFile = {
|
const mockFile = {
|
||||||
originalname: 'test-image.jpg',
|
originalname: "test-image.jpg",
|
||||||
buffer: Buffer.from('test-data'),
|
buffer: Buffer.from("test-data"),
|
||||||
mimetype: 'image/jpeg',
|
mimetype: "image/jpeg",
|
||||||
} as any;
|
};
|
||||||
|
|
||||||
const userId = 'user-123';
|
const userId = "user-123";
|
||||||
|
|
||||||
const objectKey = await s3Service.uploadFile(mockFile, userId);
|
const objectKey = await s3Service.uploadFile(mockFile as never, userId);
|
||||||
|
|
||||||
expect(objectKey).toMatch(/^user-user-123\/[a-f0-9-]+\.jpg$/);
|
expect(objectKey).toMatch(/^user-user-123\/[a-f0-9-]+\.jpg$/);
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ describe('S3Service', () => {
|
|||||||
|
|
||||||
const sentCommand = (mockSend.mock.calls[0][0] as PutObjectCommand).input;
|
const sentCommand = (mockSend.mock.calls[0][0] as PutObjectCommand).input;
|
||||||
|
|
||||||
expect(sentCommand.Bucket).toBe('test-bucket');
|
expect(sentCommand.Bucket).toBe("test-bucket");
|
||||||
expect(sentCommand.Key).toBe(objectKey);
|
expect(sentCommand.Key).toBe(objectKey);
|
||||||
expect(sentCommand.Body).toBe(mockFile.buffer);
|
expect(sentCommand.Body).toBe(mockFile.buffer);
|
||||||
});
|
});
|
||||||
@@ -104,8 +105,8 @@ describe('S3Service', () => {
|
|||||||
it("should return a correctly formatted list of files for a user", async () => {
|
it("should return a correctly formatted list of files for a user", async () => {
|
||||||
const mockS3Response = {
|
const mockS3Response = {
|
||||||
Contents: [
|
Contents: [
|
||||||
{ Key: `user-${userId}/file1.txt`, LastModified: new Date('2023-01-01') },
|
{ Key: `user-${userId}/file1.txt`, LastModified: new Date("2023-01-01") },
|
||||||
{ Key: `user-${userId}/image.jpg`, LastModified: new Date('2023-01-02') },
|
{ Key: `user-${userId}/image.jpg`, LastModified: new Date("2023-01-02") },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
mockSend.mockResolvedValue(mockS3Response);
|
mockSend.mockResolvedValue(mockS3Response);
|
||||||
@@ -116,13 +117,13 @@ describe('S3Service', () => {
|
|||||||
expect(mockSend).toHaveBeenCalledWith(expect.any(ListObjectsV2Command));
|
expect(mockSend).toHaveBeenCalledWith(expect.any(ListObjectsV2Command));
|
||||||
|
|
||||||
const sentCommand = (mockSend.mock.calls[0][0] as ListObjectsV2Command).input;
|
const sentCommand = (mockSend.mock.calls[0][0] as ListObjectsV2Command).input;
|
||||||
expect(sentCommand.Bucket).toBe('test-bucket');
|
expect(sentCommand.Bucket).toBe("test-bucket");
|
||||||
expect(sentCommand.Prefix).toBe(`user-${userId}/`);
|
expect(sentCommand.Prefix).toBe(`user-${userId}/`);
|
||||||
|
|
||||||
expect(files).toHaveLength(2);
|
expect(files).toHaveLength(2);
|
||||||
expect(files).toEqual([
|
expect(files).toEqual([
|
||||||
{ key: `user-${userId}/file1.txt`, lastModified: new Date('2023-01-01') },
|
{ key: `user-${userId}/file1.txt`, lastModified: new Date("2023-01-01") },
|
||||||
{ key: `user-${userId}/image.jpg`, lastModified: new Date('2023-01-02') },
|
{ key: `user-${userId}/image.jpg`, lastModified: new Date("2023-01-02") },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -149,9 +150,9 @@ describe('S3Service', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteFile', () => {
|
describe("deleteFile", () => {
|
||||||
it('should call the S3 client with the correct DeleteObjectCommand', async () => {
|
it("should call the S3 client with the correct DeleteObjectCommand", async () => {
|
||||||
const objectKey = 'user-123/some-file-to-delete.txt';
|
const objectKey = "user-123/some-file-to-delete.txt";
|
||||||
mockSend.mockResolvedValue({});
|
mockSend.mockResolvedValue({});
|
||||||
|
|
||||||
await expect(s3Service.deleteFile(objectKey)).resolves.toBeUndefined();
|
await expect(s3Service.deleteFile(objectKey)).resolves.toBeUndefined();
|
||||||
@@ -160,24 +161,23 @@ describe('S3Service', () => {
|
|||||||
expect(mockSend).toHaveBeenCalledWith(expect.any(DeleteObjectCommand));
|
expect(mockSend).toHaveBeenCalledWith(expect.any(DeleteObjectCommand));
|
||||||
|
|
||||||
const sentCommand = (mockSend.mock.calls[0][0] as DeleteObjectCommand).input;
|
const sentCommand = (mockSend.mock.calls[0][0] as DeleteObjectCommand).input;
|
||||||
expect(sentCommand.Bucket).toBe('test-bucket');
|
expect(sentCommand.Bucket).toBe("test-bucket");
|
||||||
expect(sentCommand.Key).toBe(objectKey);
|
expect(sentCommand.Key).toBe(objectKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the S3 client fails to delete the object', async () => {
|
it("should throw an error if the S3 client fails to delete the object", async () => {
|
||||||
const objectKey = 'user-123/failing-file.txt';
|
const objectKey = "user-123/failing-file.txt";
|
||||||
const s3Error = new Error('Access Denied');
|
const s3Error = new Error("Access Denied");
|
||||||
mockSend.mockRejectedValue(s3Error);
|
mockSend.mockRejectedValue(s3Error);
|
||||||
|
|
||||||
await expect(s3Service.deleteFile(objectKey)).rejects.toThrow('Access Denied');
|
await expect(s3Service.deleteFile(objectKey)).rejects.toThrow("Access Denied");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getSignedDownloadUrl", () => {
|
||||||
describe('getSignedDownloadUrl', () => {
|
it("should generate a signed URL for a given object key", async () => {
|
||||||
it('should generate a signed URL for a given object key', async () => {
|
const objectKey = "user-123/image.png";
|
||||||
const objectKey = 'user-123/image.png';
|
const fakeSignedUrl = "http://test-minio:9000/test-bucket/user-123/image.png?signed=true";
|
||||||
const fakeSignedUrl = 'http://test-minio:9000/test-bucket/user-123/image.png?signed=true';
|
|
||||||
|
|
||||||
mockGetSignedUrl.mockResolvedValue(fakeSignedUrl);
|
mockGetSignedUrl.mockResolvedValue(fakeSignedUrl);
|
||||||
|
|
||||||
@@ -186,15 +186,13 @@ describe('S3Service', () => {
|
|||||||
expect(signedUrl).toBe(fakeSignedUrl);
|
expect(signedUrl).toBe(fakeSignedUrl);
|
||||||
|
|
||||||
expect(mockGetSignedUrl).toHaveBeenCalledOnce();
|
expect(mockGetSignedUrl).toHaveBeenCalledOnce();
|
||||||
expect(mockGetSignedUrl).toHaveBeenCalledWith(
|
expect(mockGetSignedUrl).toHaveBeenCalledWith(expect.any(Object), expect.any(GetObjectCommand), {
|
||||||
expect.any(Object),
|
expiresIn: 300,
|
||||||
expect.any(GetObjectCommand),
|
});
|
||||||
{ expiresIn: 300 }
|
|
||||||
);
|
|
||||||
|
|
||||||
const passedCommand = (mockGetSignedUrl.mock.calls[0][1] as GetObjectCommand).input;
|
const passedCommand = (mockGetSignedUrl.mock.calls[0][1] as GetObjectCommand).input;
|
||||||
expect(passedCommand.Bucket).toBe('test-bucket');
|
expect(passedCommand.Bucket).toBe("test-bucket");
|
||||||
expect(passedCommand.Key).toBe(objectKey);
|
expect(passedCommand.Key).toBe(objectKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user