From 0aafe74a74e9aa096584f404aec7e665fb5574a4 Mon Sep 17 00:00:00 2001 From: StarAppeal Date: Thu, 25 Sep 2025 23:16:23 +0200 Subject: [PATCH] fix test --- src/rest/restStorage.ts | 8 -- src/services/spotifyPollingService.ts | 2 +- tests/rest/restStorage.test.ts | 39 +++---- tests/services/db/database.service.test.ts | 71 ++++++------- tests/services/s3Service.test.ts | 116 ++++++++++----------- 5 files changed, 111 insertions(+), 125 deletions(-) diff --git a/src/rest/restStorage.ts b/src/rest/restStorage.ts index d55b0ad..2fae66e 100644 --- a/src/rest/restStorage.ts +++ b/src/rest/restStorage.ts @@ -3,13 +3,6 @@ import multer from "multer"; import express from "express"; import { asyncHandler } from "./middleware/asyncHandler"; 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 { constructor(private readonly s3Service: S3Service) {} @@ -77,7 +70,6 @@ export class RestStorage { router.delete( /\/files\/(.*)/, asyncHandler(async (req, res) => { - // <-- ÄNDERUNG HIER const userId = req.payload.uuid; const objectKey = req.params[0]; diff --git a/src/services/spotifyPollingService.ts b/src/services/spotifyPollingService.ts index 6f5e01c..921c8b0 100644 --- a/src/services/spotifyPollingService.ts +++ b/src/services/spotifyPollingService.ts @@ -20,7 +20,7 @@ export class SpotifyPollingService { if (activePolls.has(uuid)) return; 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); this._pollUser(uuid); diff --git a/tests/rest/restStorage.test.ts b/tests/rest/restStorage.test.ts index c4c40e5..9468166 100644 --- a/tests/rest/restStorage.test.ts +++ b/tests/rest/restStorage.test.ts @@ -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 express from "express"; -import {RestStorage} from "../../src/rest/restStorage"; -import {S3Service} from "../../src/services/s3Service"; +import { RestStorage } from "../../src/rest/restStorage"; +import { S3Service } from "../../src/services/s3Service"; // @ts-ignore -import {createMockS3Service, createTestApp} from "../helpers/testSetup"; +import { createMockS3Service, createTestApp } from "../helpers/testSetup"; vi.mock("../../src/services/s3Service"); @@ -25,9 +25,8 @@ describe("RestStorage", () => { app = createTestApp(restStorage.createRouter(), "/storage", { uuid: requestingUserUUID, name: "name", - id: "1234" + id: "1234", }); - }); afterEach(() => { @@ -39,9 +38,9 @@ describe("RestStorage", () => { const objectKey = `user-${requestingUserUUID}/generated-uuid.jpg`; mockS3Service.uploadFile.mockResolvedValue(objectKey); - const response = await request(app) // Verwende die 'app' aus dem beforeEach + const response = await request(app) .post("/storage/upload") - .attach('image', Buffer.from("fake image data"), "test.jpg") + .attach("image", Buffer.from("fake image data"), "test.jpg") .expect(201); expect(response.body.data).toEqual({ @@ -52,16 +51,14 @@ describe("RestStorage", () => { expect(mockS3Service.uploadFile).toHaveBeenCalledOnce(); expect(mockS3Service.uploadFile).toHaveBeenCalledWith( expect.objectContaining({ - originalname: 'test.jpg' + originalname: "test.jpg", }), requestingUserUUID ); }); it("should return a 400 Bad Request if no file is provided", async () => { - const response = await request(app) - .post("/storage/upload") - .expect(400); + const response = await request(app).post("/storage/upload").expect(400); expect(response.body.data.message).toBe("No file provided."); expect(mockS3Service.uploadFile).not.toHaveBeenCalled(); @@ -71,14 +68,12 @@ describe("RestStorage", () => { describe("GET /files", () => { it("should return a list of files for the current user", async () => { const mockFiles = [ - {key: `user-${requestingUserUUID}/file1.txt`, lastModified: new Date()}, - {key: `user-${requestingUserUUID}/image.png`, lastModified: new Date()}, + { key: `user-${requestingUserUUID}/file1.txt`, lastModified: new Date() }, + { key: `user-${requestingUserUUID}/image.png`, lastModified: new Date() }, ]; mockS3Service.listFilesForUser.mockResolvedValue(mockFiles); - const response = await request(app) - .get("/storage/files") - .expect(200); + const response = await request(app).get("/storage/files").expect(200); const responseData = JSON.parse(JSON.stringify(mockFiles)); 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 () => { const objectKey = `user-${requestingUserUUID}/my-photo.jpg`; const signedUrl = "http://s3.com/signed-url"; @@ -97,7 +92,7 @@ describe("RestStorage", () => { .get(`/storage/files/${encodeURIComponent(objectKey)}/url`) .expect(200); - expect(response.body.data).toEqual({url: signedUrl}); + expect(response.body.data).toEqual({ url: signedUrl }); expect(mockS3Service.getSignedDownloadUrl).toHaveBeenCalledOnce(); 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 () => { const objectKey = `user-${requestingUserUUID}/non-existent.jpg`; - mockS3Service.getSignedDownloadUrl.mockRejectedValue({name: "NoSuchKey"}); + mockS3Service.getSignedDownloadUrl.mockRejectedValue({ name: "NoSuchKey" }); const response = await request(app) .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 () => { const objectKey = `user-${requestingUserUUID}/file-to-delete.pdf`; mockS3Service.deleteFile.mockResolvedValue(undefined); @@ -148,4 +143,4 @@ describe("RestStorage", () => { expect(mockS3Service.deleteFile).not.toHaveBeenCalled(); }); }); -}); \ No newline at end of file +}); diff --git a/tests/services/db/database.service.test.ts b/tests/services/db/database.service.test.ts index bbc8a71..b139f12 100644 --- a/tests/services/db/database.service.test.ts +++ b/tests/services/db/database.service.test.ts @@ -1,7 +1,7 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import mongoose from 'mongoose'; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import mongoose from "mongoose"; -vi.mock('mongoose', async (importOriginal) => { +vi.mock("mongoose", async (importOriginal) => { const originalMongoose = await importOriginal(); return { @@ -20,9 +20,7 @@ const mockedMongooseConnect = vi.mocked(mongoose.connect); const mockedMongooseDisconnect = vi.mocked(mongoose.disconnect); const mockedConnectionOn = vi.mocked(mongoose.connection.on); - -describe('database.service', () => { - +describe("database.service", () => { let connectToDatabase: any; let disconnectFromDatabase: any; @@ -30,7 +28,7 @@ describe('database.service', () => { vi.resetModules(); vi.clearAllMocks(); - const dbService = await import('../../../src/services/db/database.service'); + const dbService = await import("../../../src/services/db/database.service"); connectToDatabase = dbService.connectToDatabase; disconnectFromDatabase = dbService.disconnectFromDatabase; }); @@ -39,37 +37,39 @@ describe('database.service', () => { vi.restoreAllMocks(); }); - const TEST_DB_NAME = 'testdb'; - const TEST_DB_CONN_STRING = 'mongodb://test-host/testdb'; + const TEST_DB_NAME = "testdb"; + const TEST_DB_CONN_STRING = "mongodb://test-host/testdb"; - describe('connectToDatabase', () => { - it('should attempt to connect to MongoDB with correct options', async () => { + describe("connectToDatabase", () => { + it("should attempt to connect to MongoDB with correct options", async () => { mockedMongooseConnect.mockResolvedValue(undefined as any); await connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING); expect(mockedMongooseConnect).toHaveBeenCalledOnce(); - expect(mockedMongooseConnect).toHaveBeenCalledWith(TEST_DB_CONN_STRING, expect.objectContaining({ - dbName: TEST_DB_NAME, - family: 4, - })); + expect(mockedMongooseConnect).toHaveBeenCalledWith( + TEST_DB_CONN_STRING, + 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); await connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING); - expect(mockedConnectionOn).toHaveBeenCalledWith('connected', expect.any(Function)); - expect(mockedConnectionOn).toHaveBeenCalledWith('disconnected', expect.any(Function)); - expect(mockedConnectionOn).toHaveBeenCalledWith('error', expect.any(Function)); + expect(mockedConnectionOn).toHaveBeenCalledWith("connected", expect.any(Function)); + expect(mockedConnectionOn).toHaveBeenCalledWith("disconnected", expect.any(Function)); + expect(mockedConnectionOn).toHaveBeenCalledWith("error", expect.any(Function)); 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); - // Rufe die Funktion mehrmals parallel auf const promise1 = 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(); }); - describe('Retry Logic', () => { + describe("Retry Logic", () => { beforeEach(() => { vi.useFakeTimers(); }); @@ -86,20 +86,21 @@ describe('database.service', () => { vi.useRealTimers(); }); - it('should retry connecting after a 5-second delay if the first attempt fails', async () => { - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); - const connectionError = new Error('Database unavailable'); + it("should retry connecting after a 5-second delay if the first attempt fails", async () => { + const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + const connectionError = new Error("Database unavailable"); - mockedMongooseConnect - .mockRejectedValueOnce(connectionError) - .mockResolvedValueOnce(undefined as any); + mockedMongooseConnect.mockRejectedValueOnce(connectionError).mockResolvedValueOnce(undefined as any); const connectionPromise = connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING); await vi.runAllTicks(); 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); @@ -111,15 +112,15 @@ describe('database.service', () => { }); }); - describe('disconnectFromDatabase', () => { - it('should call mongoose.disconnect if the connection is established', async () => { + describe("disconnectFromDatabase", () => { + it("should call mongoose.disconnect if the connection is established", async () => { mockedMongooseConnect.mockResolvedValue(undefined as any); mockedMongooseDisconnect.mockResolvedValue(undefined as any); await connectToDatabase(TEST_DB_NAME, TEST_DB_CONN_STRING); - const connectedCallback = mockedConnectionOn.mock.calls.find(call => call[0] === 'connected')?.[1]; - if (typeof connectedCallback === 'function') { + const connectedCallback = mockedConnectionOn.mock.calls.find((call) => call[0] === "connected")?.[1]; + if (typeof connectedCallback === "function") { connectedCallback(); } else { throw new Error("Connected callback was not found or is not a function"); @@ -130,10 +131,10 @@ describe('database.service', () => { 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(); expect(mockedMongooseDisconnect).not.toHaveBeenCalled(); }); }); -}); \ No newline at end of file +}); diff --git a/tests/services/s3Service.test.ts b/tests/services/s3Service.test.ts index 2761f88..7654b50 100644 --- a/tests/services/s3Service.test.ts +++ b/tests/services/s3Service.test.ts @@ -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) => { - const originalModule = await importOriginal(); +vi.mock("@aws-sdk/client-s3", async (importOriginal) => { + const originalModule = await importOriginal(); return { ...originalModule, S3Client: vi.fn(), }; }); -import { S3Service, S3ClientConfig } from '../../src/services/s3Service'; +import { S3Service, S3ClientConfig } from "../../src/services/s3Service"; import { S3Client, CreateBucketCommand, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, - DeleteObjectCommand -} from '@aws-sdk/client-s3';import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; + DeleteObjectCommand, +} from "@aws-sdk/client-s3"; +import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; const testConfig: S3ClientConfig = { - endpoint: 'http://test-minio', + endpoint: "http://test-minio", port: 9000, - accessKey: 'test-key', - secretAccessKey: 'test-secret', - bucket: 'test-bucket', + accessKey: "test-key", + secretAccessKey: "test-secret", + bucket: "test-bucket", }; 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); -describe('S3Service', () => { +describe("S3Service", () => { let s3Service: S3Service; beforeEach(() => { vi.clearAllMocks(); - // @ts-ignore - S3Service.instance = undefined; - - MockS3Client.mockImplementation(() => ({ - send: mockSend, - }) as any); + MockS3Client.mockImplementation( + () => + ({ + send: mockSend, + }) as never + ); s3Service = S3Service.getInstance(testConfig); }); - describe('Initialization and Bucket Creation', () => { - it('should create a singleton instance correctly', () => { + describe("Initialization and Bucket Creation", () => { + it("should create a singleton instance correctly", () => { const instance1 = S3Service.getInstance(testConfig); - const instance2 = S3Service.getInstance(); // Ohne Config, da schon initialisiert + const instance2 = S3Service.getInstance(); expect(instance1).toBe(instance2); expect(MockS3Client).toHaveBeenCalledOnce(); }); - it('should call ensureBucketExists and handle existing buckets gracefully', async () => { - mockSend.mockRejectedValue({ name: 'BucketAlreadyOwnedByYou' }); + it("should call ensureBucketExists and handle existing buckets gracefully", async () => { + mockSend.mockRejectedValue({ name: "BucketAlreadyOwnedByYou" }); await expect(s3Service.ensureBucketExists()).resolves.toBeUndefined(); 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({}); await expect(s3Service.ensureBucketExists()).resolves.toBeUndefined(); @@ -73,17 +74,17 @@ describe('S3Service', () => { }); }); - describe('uploadFile', () => { - it('should upload a file and return the correct object key', async () => { + describe("uploadFile", () => { + it("should upload a file and return the correct object key", async () => { const mockFile = { - originalname: 'test-image.jpg', - buffer: Buffer.from('test-data'), - mimetype: 'image/jpeg', - } as any; + originalname: "test-image.jpg", + buffer: Buffer.from("test-data"), + mimetype: "image/jpeg", + }; - 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$/); @@ -92,7 +93,7 @@ describe('S3Service', () => { 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.Body).toBe(mockFile.buffer); }); @@ -104,8 +105,8 @@ describe('S3Service', () => { it("should return a correctly formatted list of files for a user", async () => { const mockS3Response = { Contents: [ - { 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}/file1.txt`, LastModified: new Date("2023-01-01") }, + { Key: `user-${userId}/image.jpg`, LastModified: new Date("2023-01-02") }, ], }; mockSend.mockResolvedValue(mockS3Response); @@ -116,13 +117,13 @@ describe('S3Service', () => { expect(mockSend).toHaveBeenCalledWith(expect.any(ListObjectsV2Command)); 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(files).toHaveLength(2); expect(files).toEqual([ - { 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}/file1.txt`, lastModified: new Date("2023-01-01") }, + { key: `user-${userId}/image.jpg`, lastModified: new Date("2023-01-02") }, ]); }); @@ -149,9 +150,9 @@ describe('S3Service', () => { }); }); - describe('deleteFile', () => { - it('should call the S3 client with the correct DeleteObjectCommand', async () => { - const objectKey = 'user-123/some-file-to-delete.txt'; + describe("deleteFile", () => { + it("should call the S3 client with the correct DeleteObjectCommand", async () => { + const objectKey = "user-123/some-file-to-delete.txt"; mockSend.mockResolvedValue({}); await expect(s3Service.deleteFile(objectKey)).resolves.toBeUndefined(); @@ -160,24 +161,23 @@ describe('S3Service', () => { expect(mockSend).toHaveBeenCalledWith(expect.any(DeleteObjectCommand)); 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); }); - it('should throw an error if the S3 client fails to delete the object', async () => { - const objectKey = 'user-123/failing-file.txt'; - const s3Error = new Error('Access Denied'); + it("should throw an error if the S3 client fails to delete the object", async () => { + const objectKey = "user-123/failing-file.txt"; + const s3Error = new Error("Access Denied"); mockSend.mockRejectedValue(s3Error); - await expect(s3Service.deleteFile(objectKey)).rejects.toThrow('Access Denied'); + await expect(s3Service.deleteFile(objectKey)).rejects.toThrow("Access Denied"); }); }); - - describe('getSignedDownloadUrl', () => { - it('should generate a signed URL for a given object key', async () => { - const objectKey = 'user-123/image.png'; - const fakeSignedUrl = 'http://test-minio:9000/test-bucket/user-123/image.png?signed=true'; + describe("getSignedDownloadUrl", () => { + it("should generate a signed URL for a given object key", async () => { + const objectKey = "user-123/image.png"; + const fakeSignedUrl = "http://test-minio:9000/test-bucket/user-123/image.png?signed=true"; mockGetSignedUrl.mockResolvedValue(fakeSignedUrl); @@ -186,15 +186,13 @@ describe('S3Service', () => { expect(signedUrl).toBe(fakeSignedUrl); expect(mockGetSignedUrl).toHaveBeenCalledOnce(); - expect(mockGetSignedUrl).toHaveBeenCalledWith( - expect.any(Object), - expect.any(GetObjectCommand), - { expiresIn: 300 } - ); + expect(mockGetSignedUrl).toHaveBeenCalledWith(expect.any(Object), expect.any(GetObjectCommand), { + expiresIn: 300, + }); 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); }); }); -}); \ No newline at end of file +});