diff --git a/apps/storage/apps/backend/src/__tests__/utils/mock-factories.ts b/apps/storage/apps/backend/src/__tests__/utils/mock-factories.ts index dbec53d87..40f7277d1 100644 --- a/apps/storage/apps/backend/src/__tests__/utils/mock-factories.ts +++ b/apps/storage/apps/backend/src/__tests__/utils/mock-factories.ts @@ -13,6 +13,8 @@ export const mockFileFactory = { storageKey: `users/test-user-id/${randomUUID()}-test-file.pdf`, parentFolderId: null, currentVersion: 1, + checksum: null, + thumbnailPath: null, isFavorite: false, isDeleted: false, deletedAt: null, @@ -53,9 +55,9 @@ export const mockShareFactory = { userId: 'test-user-id', fileId: null, folderId: null, - shareType: 'file', + shareType: 'file' as const, shareToken: randomUUID().replace(/-/g, '') + randomUUID().replace(/-/g, ''), - accessLevel: 'view', + accessLevel: 'view' as const, password: null, maxDownloads: null, downloadCount: 0, diff --git a/apps/storage/apps/backend/src/file/file.controller.spec.ts b/apps/storage/apps/backend/src/file/file.controller.spec.ts new file mode 100644 index 000000000..939966628 --- /dev/null +++ b/apps/storage/apps/backend/src/file/file.controller.spec.ts @@ -0,0 +1,247 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { BadRequestException } from '@nestjs/common'; +import { JwtAuthGuard } from '@manacore/shared-nestjs-auth'; +import { FileController } from './file.controller'; +import { FileService } from './file.service'; +import { mockFileFactory } from '../__tests__/utils/mock-factories'; + +describe('FileController', () => { + let controller: FileController; + let fileService: jest.Mocked; + + const mockUser = { userId: 'test-user-id', email: 'test@example.com', role: 'user' }; + + beforeEach(async () => { + const mockFileService = { + findAll: jest.fn(), + findOne: jest.fn(), + upload: jest.fn(), + update: jest.fn(), + move: jest.fn(), + delete: jest.fn(), + toggleFavorite: jest.fn(), + download: jest.fn(), + getDownloadUrl: jest.fn(), + getStats: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + controllers: [FileController], + providers: [{ provide: FileService, useValue: mockFileService }], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(FileController); + fileService = module.get(FileService); + }); + + describe('findAll', () => { + it('should return files for user at root level', async () => { + const files = mockFileFactory.createMany(3); + fileService.findAll.mockResolvedValue(files); + + const result = await controller.findAll(mockUser); + + expect(fileService.findAll).toHaveBeenCalledWith('test-user-id', undefined); + expect(result).toEqual(files); + }); + + it('should return files for user in specific folder', async () => { + const folderId = 'folder-123'; + const files = mockFileFactory.createMany(2, { parentFolderId: folderId }); + fileService.findAll.mockResolvedValue(files); + + const result = await controller.findAll(mockUser, folderId); + + expect(fileService.findAll).toHaveBeenCalledWith('test-user-id', folderId); + expect(result).toEqual(files); + }); + }); + + describe('findOne', () => { + it('should return a single file by id', async () => { + const file = mockFileFactory.create({ id: 'file-123' }); + fileService.findOne.mockResolvedValue(file); + + const result = await controller.findOne(mockUser, 'file-123'); + + expect(fileService.findOne).toHaveBeenCalledWith('test-user-id', 'file-123'); + expect(result).toEqual(file); + }); + }); + + describe('getStats', () => { + it('should return file stats for user', async () => { + const stats = { totalFiles: 10, totalSize: 1024000, byType: {} }; + fileService.getStats.mockResolvedValue(stats as any); + + const result = await controller.getStats(mockUser); + + expect(fileService.getStats).toHaveBeenCalledWith('test-user-id'); + expect(result).toEqual(stats); + }); + }); + + describe('upload', () => { + it('should upload a file and return the result', async () => { + const mockFile = { + originalname: 'test.pdf', + mimetype: 'application/pdf', + size: 1024, + buffer: Buffer.from('test'), + } as Express.Multer.File; + const dto = { parentFolderId: 'folder-123' }; + const createdFile = mockFileFactory.create({ name: 'test.pdf' }); + fileService.upload.mockResolvedValue(createdFile); + + const result = await controller.upload(mockUser, mockFile, dto); + + expect(fileService.upload).toHaveBeenCalledWith('test-user-id', mockFile, dto); + expect(result).toEqual(createdFile); + }); + + it('should throw BadRequestException when no file is provided', async () => { + const dto = {}; + + await expect(controller.upload(mockUser, undefined as any, dto)).rejects.toThrow( + BadRequestException + ); + await expect(controller.upload(mockUser, undefined as any, dto)).rejects.toThrow( + 'No file provided' + ); + expect(fileService.upload).not.toHaveBeenCalled(); + }); + }); + + describe('uploadMultiple', () => { + it('should upload multiple files and return results', async () => { + const mockFiles = [ + { + originalname: 'file1.pdf', + mimetype: 'application/pdf', + size: 1024, + buffer: Buffer.from('test1'), + }, + { + originalname: 'file2.png', + mimetype: 'image/png', + size: 2048, + buffer: Buffer.from('test2'), + }, + ] as Express.Multer.File[]; + const dto = {}; + const created1 = mockFileFactory.create({ name: 'file1.pdf' }); + const created2 = mockFileFactory.create({ name: 'file2.png' }); + fileService.upload.mockResolvedValueOnce(created1).mockResolvedValueOnce(created2); + + const result = await controller.uploadMultiple(mockUser, mockFiles, dto); + + expect(fileService.upload).toHaveBeenCalledTimes(2); + expect(fileService.upload).toHaveBeenCalledWith('test-user-id', mockFiles[0], dto); + expect(fileService.upload).toHaveBeenCalledWith('test-user-id', mockFiles[1], dto); + expect(result).toEqual([created1, created2]); + }); + + it('should throw BadRequestException when no files provided', async () => { + await expect(controller.uploadMultiple(mockUser, [], {})).rejects.toThrow( + BadRequestException + ); + await expect(controller.uploadMultiple(mockUser, [], {})).rejects.toThrow( + 'No files provided' + ); + }); + + it('should throw BadRequestException when files is undefined', async () => { + await expect(controller.uploadMultiple(mockUser, undefined as any, {})).rejects.toThrow( + BadRequestException + ); + }); + }); + + describe('update', () => { + it('should update a file and return the result', async () => { + const dto = { name: 'renamed-file.pdf' }; + const updatedFile = mockFileFactory.create({ name: 'renamed-file.pdf' }); + fileService.update.mockResolvedValue(updatedFile); + + const result = await controller.update(mockUser, 'file-123', dto); + + expect(fileService.update).toHaveBeenCalledWith('test-user-id', 'file-123', dto); + expect(result).toEqual(updatedFile); + }); + }); + + describe('move', () => { + it('should move a file to a new folder', async () => { + const dto = { parentFolderId: 'new-folder-id' }; + const movedFile = mockFileFactory.create({ parentFolderId: 'new-folder-id' }); + fileService.move.mockResolvedValue(movedFile); + + const result = await controller.move(mockUser, 'file-123', dto); + + expect(fileService.move).toHaveBeenCalledWith('test-user-id', 'file-123', dto); + expect(result).toEqual(movedFile); + }); + }); + + describe('delete', () => { + it('should delete a file and return success', async () => { + fileService.delete.mockResolvedValue(undefined); + + const result = await controller.delete(mockUser, 'file-123'); + + expect(fileService.delete).toHaveBeenCalledWith('test-user-id', 'file-123'); + expect(result).toEqual({ success: true }); + }); + }); + + describe('toggleFavorite', () => { + it('should toggle favorite status and return the file', async () => { + const file = mockFileFactory.create({ isFavorite: true }); + fileService.toggleFavorite.mockResolvedValue(file); + + const result = await controller.toggleFavorite(mockUser, 'file-123'); + + expect(fileService.toggleFavorite).toHaveBeenCalledWith('test-user-id', 'file-123'); + expect(result).toEqual(file); + }); + }); + + describe('download', () => { + it('should set response headers and send buffer for file download', async () => { + const file = mockFileFactory.create({ + name: 'test-file.pdf', + mimeType: 'application/pdf', + }); + const buffer = Buffer.from('file-content'); + fileService.download.mockResolvedValue({ buffer, file } as any); + + const mockRes = { set: jest.fn(), send: jest.fn(), json: jest.fn() } as any; + + await controller.download(mockUser, 'file-123', undefined as any, mockRes); + + expect(fileService.download).toHaveBeenCalledWith('test-user-id', 'file-123'); + expect(mockRes.set).toHaveBeenCalledWith({ + 'Content-Type': 'application/pdf', + 'Content-Disposition': `attachment; filename="${encodeURIComponent('test-file.pdf')}"`, + 'Content-Length': buffer.length, + }); + expect(mockRes.send).toHaveBeenCalledWith(buffer); + }); + + it('should return JSON with url when url=true query param is set', async () => { + const downloadUrl = 'https://storage.example.com/presigned-url'; + fileService.getDownloadUrl.mockResolvedValue(downloadUrl); + + const mockRes = { set: jest.fn(), send: jest.fn(), json: jest.fn() } as any; + + await controller.download(mockUser, 'file-123', 'true', mockRes); + + expect(fileService.getDownloadUrl).toHaveBeenCalledWith('test-user-id', 'file-123'); + expect(mockRes.json).toHaveBeenCalledWith({ url: downloadUrl }); + expect(mockRes.send).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/storage/apps/backend/src/folder/folder.controller.spec.ts b/apps/storage/apps/backend/src/folder/folder.controller.spec.ts new file mode 100644 index 000000000..48b675bf3 --- /dev/null +++ b/apps/storage/apps/backend/src/folder/folder.controller.spec.ts @@ -0,0 +1,150 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { JwtAuthGuard } from '@manacore/shared-nestjs-auth'; +import { FolderController } from './folder.controller'; +import { FolderService } from './folder.service'; +import { mockFolderFactory } from '../__tests__/utils/mock-factories'; + +describe('FolderController', () => { + let controller: FolderController; + let folderService: jest.Mocked; + + const mockUser = { userId: 'test-user-id', email: 'test@example.com', role: 'user' }; + + beforeEach(async () => { + const mockFolderService = { + findAll: jest.fn(), + findOne: jest.fn(), + create: jest.fn(), + update: jest.fn(), + move: jest.fn(), + delete: jest.fn(), + toggleFavorite: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + controllers: [FolderController], + providers: [{ provide: FolderService, useValue: mockFolderService }], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(FolderController); + folderService = module.get(FolderService); + }); + + describe('findAll', () => { + it('should return root folders when no parentFolderId given', async () => { + const folders = mockFolderFactory.createMany(3); + folderService.findAll.mockResolvedValue(folders); + + const result = await controller.findAll(mockUser); + + expect(folderService.findAll).toHaveBeenCalledWith('test-user-id', undefined); + expect(result).toEqual(folders); + }); + + it('should return child folders when parentFolderId is given', async () => { + const parentId = 'parent-folder-id'; + const folders = mockFolderFactory.createMany(2, { parentFolderId: parentId }); + folderService.findAll.mockResolvedValue(folders); + + const result = await controller.findAll(mockUser, parentId); + + expect(folderService.findAll).toHaveBeenCalledWith('test-user-id', parentId); + expect(result).toEqual(folders); + }); + }); + + describe('findOne', () => { + it('should return a single folder by id', async () => { + const folder = mockFolderFactory.create({ id: 'folder-123' }); + folderService.findOne.mockResolvedValue(folder); + + const result = await controller.findOne(mockUser, 'folder-123'); + + expect(folderService.findOne).toHaveBeenCalledWith('test-user-id', 'folder-123'); + expect(result).toEqual(folder); + }); + }); + + describe('create', () => { + it('should create a folder and return it', async () => { + const dto = { name: 'New Folder', parentFolderId: 'parent-id', color: '#ff0000' }; + const created = mockFolderFactory.create({ + name: 'New Folder', + parentFolderId: 'parent-id', + color: '#ff0000', + }); + folderService.create.mockResolvedValue(created); + + const result = await controller.create(mockUser, dto); + + expect(folderService.create).toHaveBeenCalledWith('test-user-id', dto); + expect(result).toEqual(created); + }); + + it('should create a root folder without parentFolderId', async () => { + const dto = { name: 'Root Folder' }; + const created = mockFolderFactory.create({ name: 'Root Folder' }); + folderService.create.mockResolvedValue(created); + + const result = await controller.create(mockUser, dto); + + expect(folderService.create).toHaveBeenCalledWith('test-user-id', dto); + expect(result).toEqual(created); + }); + }); + + describe('update', () => { + it('should update a folder and return the result', async () => { + const dto = { name: 'Renamed Folder', color: '#00ff00' }; + const updated = mockFolderFactory.create({ + name: 'Renamed Folder', + color: '#00ff00', + }); + folderService.update.mockResolvedValue(updated); + + const result = await controller.update(mockUser, 'folder-123', dto); + + expect(folderService.update).toHaveBeenCalledWith('test-user-id', 'folder-123', dto); + expect(result).toEqual(updated); + }); + }); + + describe('move', () => { + it('should move a folder to a new parent', async () => { + const dto = { parentFolderId: 'new-parent-id' }; + const moved = mockFolderFactory.create({ parentFolderId: 'new-parent-id' }); + folderService.move.mockResolvedValue(moved); + + const result = await controller.move(mockUser, 'folder-123', dto); + + expect(folderService.move).toHaveBeenCalledWith('test-user-id', 'folder-123', dto); + expect(result).toEqual(moved); + }); + }); + + describe('delete', () => { + it('should delete a folder and return success', async () => { + folderService.delete.mockResolvedValue(undefined); + + const result = await controller.delete(mockUser, 'folder-123'); + + expect(folderService.delete).toHaveBeenCalledWith('test-user-id', 'folder-123'); + expect(result).toEqual({ success: true }); + }); + }); + + describe('toggleFavorite', () => { + it('should toggle favorite and return the folder', async () => { + const folder = mockFolderFactory.create({ isFavorite: true }); + folderService.toggleFavorite.mockResolvedValue(folder); + + const result = await controller.toggleFavorite(mockUser, 'folder-123'); + + expect(folderService.toggleFavorite).toHaveBeenCalledWith('test-user-id', 'folder-123'); + expect(result).toEqual(folder); + }); + }); +}); diff --git a/apps/storage/apps/backend/src/search/search.controller.spec.ts b/apps/storage/apps/backend/src/search/search.controller.spec.ts new file mode 100644 index 000000000..81c27a8f6 --- /dev/null +++ b/apps/storage/apps/backend/src/search/search.controller.spec.ts @@ -0,0 +1,90 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { JwtAuthGuard } from '@manacore/shared-nestjs-auth'; +import { SearchController } from './search.controller'; +import { SearchService } from './search.service'; +import { mockFileFactory, mockFolderFactory } from '../__tests__/utils/mock-factories'; + +describe('SearchController', () => { + let controller: SearchController; + let searchService: jest.Mocked; + + const mockUser = { userId: 'test-user-id', email: 'test@example.com', role: 'user' }; + + beforeEach(async () => { + const mockSearchService = { + search: jest.fn(), + getFavorites: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + controllers: [SearchController], + providers: [{ provide: SearchService, useValue: mockSearchService }], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(SearchController); + searchService = module.get(SearchService); + }); + + describe('search', () => { + it('should search and return matching files and folders', async () => { + const searchResults = { + files: mockFileFactory.createMany(2, { name: 'report.pdf' }), + folders: mockFolderFactory.createMany(1, { name: 'Reports' }), + }; + searchService.search.mockResolvedValue(searchResults as any); + + const result = await controller.search(mockUser, 'report'); + + expect(searchService.search).toHaveBeenCalledWith('test-user-id', 'report'); + expect(result).toEqual(searchResults); + }); + + it('should return empty arrays when query is empty', async () => { + const result = await controller.search(mockUser, ''); + + expect(searchService.search).not.toHaveBeenCalled(); + expect(result).toEqual({ files: [], folders: [] }); + }); + + it('should return empty arrays when query is undefined', async () => { + const result = await controller.search(mockUser, undefined as any); + + expect(searchService.search).not.toHaveBeenCalled(); + expect(result).toEqual({ files: [], folders: [] }); + }); + + it('should return empty arrays when query is only whitespace', async () => { + const result = await controller.search(mockUser, ' '); + + expect(searchService.search).not.toHaveBeenCalled(); + expect(result).toEqual({ files: [], folders: [] }); + }); + + it('should trim the query before passing to service', async () => { + const searchResults = { files: [], folders: [] }; + searchService.search.mockResolvedValue(searchResults as any); + + await controller.search(mockUser, ' report '); + + expect(searchService.search).toHaveBeenCalledWith('test-user-id', 'report'); + }); + }); + + describe('getFavorites', () => { + it('should return favorite files and folders', async () => { + const favorites = { + files: mockFileFactory.createMany(2, { isFavorite: true }), + folders: mockFolderFactory.createMany(1, { isFavorite: true }), + }; + searchService.getFavorites.mockResolvedValue(favorites as any); + + const result = await controller.getFavorites(mockUser); + + expect(searchService.getFavorites).toHaveBeenCalledWith('test-user-id'); + expect(result).toEqual(favorites); + }); + }); +}); diff --git a/apps/storage/apps/backend/src/share/share.controller.spec.ts b/apps/storage/apps/backend/src/share/share.controller.spec.ts new file mode 100644 index 000000000..229970ac2 --- /dev/null +++ b/apps/storage/apps/backend/src/share/share.controller.spec.ts @@ -0,0 +1,161 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { JwtAuthGuard } from '@manacore/shared-nestjs-auth'; +import { ShareController } from './share.controller'; +import { ShareService } from './share.service'; +import { mockShareFactory } from '../__tests__/utils/mock-factories'; + +describe('ShareController', () => { + let controller: ShareController; + let shareService: jest.Mocked; + + const mockUser = { userId: 'test-user-id', email: 'test@example.com', role: 'user' }; + + beforeEach(async () => { + const mockShareService = { + findAll: jest.fn(), + findByToken: jest.fn(), + create: jest.fn(), + delete: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + controllers: [ShareController], + providers: [{ provide: ShareService, useValue: mockShareService }], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(ShareController); + shareService = module.get(ShareService); + }); + + describe('findAll', () => { + it('should return all shares for user', async () => { + const shares = [ + mockShareFactory.create({ fileId: 'file-1' }), + mockShareFactory.create({ folderId: 'folder-1', shareType: 'folder' as const }), + ]; + shareService.findAll.mockResolvedValue(shares as any); + + const result = await controller.findAll(mockUser); + + expect(shareService.findAll).toHaveBeenCalledWith('test-user-id'); + expect(result).toEqual(shares); + }); + }); + + describe('findByToken', () => { + it('should return shared item by token (public, no auth required)', async () => { + const share = mockShareFactory.create({ shareToken: 'abc123token' }); + shareService.findByToken.mockResolvedValue(share as any); + + const result = await controller.findByToken('abc123token'); + + expect(shareService.findByToken).toHaveBeenCalledWith('abc123token'); + expect(result).toEqual(share); + }); + }); + + describe('create', () => { + it('should create a file share with default settings', async () => { + const dto = { fileId: 'file-123' }; + const created = mockShareFactory.create({ fileId: 'file-123' }); + shareService.create.mockResolvedValue(created as any); + + const result = await controller.create(mockUser, dto); + + expect(shareService.create).toHaveBeenCalledWith('test-user-id', { + fileId: 'file-123', + folderId: undefined, + accessLevel: undefined, + password: undefined, + maxDownloads: undefined, + expiresAt: undefined, + }); + expect(result).toEqual(created); + }); + + it('should convert expiresInDays to a Date object', async () => { + const dto = { + fileId: 'file-123', + accessLevel: 'download' as const, + expiresInDays: 7, + }; + const created = mockShareFactory.create({ fileId: 'file-123' }); + shareService.create.mockResolvedValue(created as any); + + const beforeCall = Date.now(); + await controller.create(mockUser, dto); + const afterCall = Date.now(); + + const callArgs = shareService.create.mock.calls[0]; + expect(callArgs[0]).toBe('test-user-id'); + const expiresAt = callArgs[1].expiresAt as Date; + expect(expiresAt).toBeInstanceOf(Date); + + const expectedMin = beforeCall + 7 * 24 * 60 * 60 * 1000; + const expectedMax = afterCall + 7 * 24 * 60 * 60 * 1000; + expect(expiresAt.getTime()).toBeGreaterThanOrEqual(expectedMin); + expect(expiresAt.getTime()).toBeLessThanOrEqual(expectedMax); + }); + + it('should create a share with password and max downloads', async () => { + const dto = { + fileId: 'file-123', + password: 'secret123', + maxDownloads: 5, + }; + const created = mockShareFactory.create({ + fileId: 'file-123', + password: 'secret123', + maxDownloads: 5, + }); + shareService.create.mockResolvedValue(created as any); + + const result = await controller.create(mockUser, dto); + + expect(shareService.create).toHaveBeenCalledWith('test-user-id', { + fileId: 'file-123', + folderId: undefined, + accessLevel: undefined, + password: 'secret123', + maxDownloads: 5, + expiresAt: undefined, + }); + expect(result).toEqual(created); + }); + + it('should create a folder share', async () => { + const dto = { folderId: 'folder-123', accessLevel: 'view' as const }; + const created = mockShareFactory.create({ + folderId: 'folder-123', + shareType: 'folder' as const, + }); + shareService.create.mockResolvedValue(created as any); + + const result = await controller.create(mockUser, dto); + + expect(shareService.create).toHaveBeenCalledWith('test-user-id', { + fileId: undefined, + folderId: 'folder-123', + accessLevel: 'view', + password: undefined, + maxDownloads: undefined, + expiresAt: undefined, + }); + expect(result).toEqual(created); + }); + }); + + describe('delete', () => { + it('should delete a share and return success', async () => { + shareService.delete.mockResolvedValue(undefined); + + const result = await controller.delete(mockUser, 'share-123'); + + expect(shareService.delete).toHaveBeenCalledWith('test-user-id', 'share-123'); + expect(result).toEqual({ success: true }); + }); + }); +}); diff --git a/apps/storage/apps/backend/src/tag/tag.controller.spec.ts b/apps/storage/apps/backend/src/tag/tag.controller.spec.ts new file mode 100644 index 000000000..9f649b6df --- /dev/null +++ b/apps/storage/apps/backend/src/tag/tag.controller.spec.ts @@ -0,0 +1,123 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { JwtAuthGuard } from '@manacore/shared-nestjs-auth'; +import { TagController } from './tag.controller'; +import { TagService } from './tag.service'; +import { mockTagFactory } from '../__tests__/utils/mock-factories'; + +describe('TagController', () => { + let controller: TagController; + let tagService: jest.Mocked; + + const mockUser = { userId: 'test-user-id', email: 'test@example.com', role: 'user' }; + + beforeEach(async () => { + const mockTagService = { + findAll: jest.fn(), + create: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + controllers: [TagController], + providers: [{ provide: TagService, useValue: mockTagService }], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(TagController); + tagService = module.get(TagService); + }); + + describe('findAll', () => { + it('should return all tags for user', async () => { + const tags = [ + mockTagFactory.create({ name: 'Work' }), + mockTagFactory.create({ name: 'Personal' }), + mockTagFactory.create({ name: 'Important' }), + ]; + tagService.findAll.mockResolvedValue(tags as any); + + const result = await controller.findAll(mockUser); + + expect(tagService.findAll).toHaveBeenCalledWith('test-user-id'); + expect(result).toEqual(tags); + expect(result).toHaveLength(3); + }); + }); + + describe('create', () => { + it('should create a tag with name and color', async () => { + const dto = { name: 'Urgent', color: '#ff0000' }; + const created = mockTagFactory.create({ name: 'Urgent', color: '#ff0000' }); + tagService.create.mockResolvedValue(created as any); + + const result = await controller.create(mockUser, dto); + + expect(tagService.create).toHaveBeenCalledWith('test-user-id', 'Urgent', '#ff0000'); + expect(result).toEqual(created); + }); + + it('should create a tag with name only (no color)', async () => { + const dto = { name: 'Archive' }; + const created = mockTagFactory.create({ name: 'Archive' }); + tagService.create.mockResolvedValue(created as any); + + const result = await controller.create(mockUser, dto); + + expect(tagService.create).toHaveBeenCalledWith('test-user-id', 'Archive', undefined); + expect(result).toEqual(created); + }); + }); + + describe('update', () => { + it('should update a tag with new name and color', async () => { + const dto = { name: 'Updated Tag', color: '#00ff00' }; + const updated = mockTagFactory.create({ name: 'Updated Tag', color: '#00ff00' }); + tagService.update.mockResolvedValue(updated as any); + + const result = await controller.update(mockUser, 'tag-123', dto); + + expect(tagService.update).toHaveBeenCalledWith('test-user-id', 'tag-123', dto); + expect(result).toEqual(updated); + }); + + it('should update a tag with partial data (name only)', async () => { + const dto = { name: 'Renamed' }; + const updated = mockTagFactory.create({ name: 'Renamed' }); + tagService.update.mockResolvedValue(updated as any); + + const result = await controller.update(mockUser, 'tag-123', dto); + + expect(tagService.update).toHaveBeenCalledWith('test-user-id', 'tag-123', { + name: 'Renamed', + }); + expect(result).toEqual(updated); + }); + + it('should update a tag with partial data (color only)', async () => { + const dto = { color: '#0000ff' }; + const updated = mockTagFactory.create({ color: '#0000ff' }); + tagService.update.mockResolvedValue(updated as any); + + const result = await controller.update(mockUser, 'tag-123', dto); + + expect(tagService.update).toHaveBeenCalledWith('test-user-id', 'tag-123', { + color: '#0000ff', + }); + expect(result).toEqual(updated); + }); + }); + + describe('delete', () => { + it('should delete a tag and return success', async () => { + tagService.delete.mockResolvedValue(undefined); + + const result = await controller.delete(mockUser, 'tag-123'); + + expect(tagService.delete).toHaveBeenCalledWith('test-user-id', 'tag-123'); + expect(result).toEqual({ success: true }); + }); + }); +}); diff --git a/apps/storage/apps/backend/src/trash/trash.controller.spec.ts b/apps/storage/apps/backend/src/trash/trash.controller.spec.ts new file mode 100644 index 000000000..6d782e023 --- /dev/null +++ b/apps/storage/apps/backend/src/trash/trash.controller.spec.ts @@ -0,0 +1,109 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { JwtAuthGuard } from '@manacore/shared-nestjs-auth'; +import { TrashController } from './trash.controller'; +import { TrashService } from './trash.service'; +import { mockFileFactory, mockFolderFactory } from '../__tests__/utils/mock-factories'; + +describe('TrashController', () => { + let controller: TrashController; + let trashService: jest.Mocked; + + const mockUser = { userId: 'test-user-id', email: 'test@example.com', role: 'user' }; + + beforeEach(async () => { + const mockTrashService = { + findAll: jest.fn(), + restoreFile: jest.fn(), + restoreFolder: jest.fn(), + permanentlyDeleteFile: jest.fn(), + permanentlyDeleteFolder: jest.fn(), + emptyTrash: jest.fn(), + }; + + const module: TestingModule = await Test.createTestingModule({ + controllers: [TrashController], + providers: [{ provide: TrashService, useValue: mockTrashService }], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ canActivate: () => true }) + .compile(); + + controller = module.get(TrashController); + trashService = module.get(TrashService); + }); + + describe('findAll', () => { + it('should return all trashed items for user', async () => { + const trashedItems = { + files: mockFileFactory.createMany(2, { isDeleted: true }), + folders: mockFolderFactory.createMany(1, { isDeleted: true }), + }; + trashService.findAll.mockResolvedValue(trashedItems as any); + + const result = await controller.findAll(mockUser); + + expect(trashService.findAll).toHaveBeenCalledWith('test-user-id'); + expect(result).toEqual(trashedItems); + }); + }); + + describe('restore', () => { + it('should call restoreFile when type is file', async () => { + const restoredFile = mockFileFactory.create({ isDeleted: false }); + trashService.restoreFile.mockResolvedValue(restoredFile as any); + + const result = await controller.restore(mockUser, 'file-123', 'file'); + + expect(trashService.restoreFile).toHaveBeenCalledWith('test-user-id', 'file-123'); + expect(trashService.restoreFolder).not.toHaveBeenCalled(); + expect(result).toEqual(restoredFile); + }); + + it('should call restoreFolder when type is folder', async () => { + const restoredFolder = mockFolderFactory.create({ isDeleted: false }); + trashService.restoreFolder.mockResolvedValue(restoredFolder as any); + + const result = await controller.restore(mockUser, 'folder-123', 'folder'); + + expect(trashService.restoreFolder).toHaveBeenCalledWith('test-user-id', 'folder-123'); + expect(trashService.restoreFile).not.toHaveBeenCalled(); + expect(result).toEqual(restoredFolder); + }); + }); + + describe('permanentlyDelete', () => { + it('should permanently delete a file and return success', async () => { + trashService.permanentlyDeleteFile.mockResolvedValue(undefined); + + const result = await controller.permanentlyDelete(mockUser, 'file-123', 'file'); + + expect(trashService.permanentlyDeleteFile).toHaveBeenCalledWith('test-user-id', 'file-123'); + expect(trashService.permanentlyDeleteFolder).not.toHaveBeenCalled(); + expect(result).toEqual({ success: true }); + }); + + it('should permanently delete a folder and return success', async () => { + trashService.permanentlyDeleteFolder.mockResolvedValue(undefined); + + const result = await controller.permanentlyDelete(mockUser, 'folder-123', 'folder'); + + expect(trashService.permanentlyDeleteFolder).toHaveBeenCalledWith( + 'test-user-id', + 'folder-123' + ); + expect(trashService.permanentlyDeleteFile).not.toHaveBeenCalled(); + expect(result).toEqual({ success: true }); + }); + }); + + describe('emptyTrash', () => { + it('should empty all trash and return success', async () => { + trashService.emptyTrash.mockResolvedValue(undefined); + + const result = await controller.emptyTrash(mockUser); + + expect(trashService.emptyTrash).toHaveBeenCalledWith('test-user-id'); + expect(result).toEqual({ success: true }); + }); + }); +}); diff --git a/apps/storage/apps/web/src/routes/offline/+page.svelte b/apps/storage/apps/web/src/routes/offline/+page.svelte index 987682508..d075bbc25 100644 --- a/apps/storage/apps/web/src/routes/offline/+page.svelte +++ b/apps/storage/apps/web/src/routes/offline/+page.svelte @@ -59,26 +59,27 @@ {#if isOnline} Du wirst gleich weitergeleitet... {:else} - Storage benötigt eine Internetverbindung für Cloud-Dateien. + Storage benötigt eine Internetverbindung für Cloud-Dateien. Kürzlich besuchte Seiten sind + möglicherweise noch verfügbar. {/if}

{#if !isOnline}
- - - - - Zur Startseite - +