import XCTest import UIKit @testable import MangaReader /// Tests para el StorageService /// Tests para guardar/cargar favoritos, progreso de lectura y capítulos descargados final class StorageServiceTests: XCTestCase { var storageService: StorageService! var mockUserDefaults: UserDefaults! // MARK: - Setup & Teardown override func setUp() async throws { try await super.setUp() // Crear un UserDefaults aislado para los tests mockUserDefaults = UserDefaults(suiteName: "test_manga_reader_\(UUID().uuidString)")! // Inyectar el mock UserDefaults si fuera posible (requiere modificación del StorageService) // Por ahora, limpiaremos UserDefaults después de cada test // Limpiar UserDefaults antes de cada test UserDefaults.standard.removeObject(forKey: "favoriteMangas") UserDefaults.standard.removeObject(forKey: "readingProgress") UserDefaults.standard.removeObject(forKey: "downloadedChaptersMetadata") storageService = StorageService.shared } override func tearDown() async throws { // Limpiar después de los tests UserDefaults.standard.removeObject(forKey: "favoriteMangas") UserDefaults.standard.removeObject(forKey: "readingProgress") // Limpiar archivos de test storageService.clearAllDownloads() try await super.tearDown() } // MARK: - Favorites Tests func testSaveFavorite() { // Given let mangaSlug = "solo-leveling" // When storageService.saveFavorite(mangaSlug: mangaSlug) // Then let favorites = storageService.getFavorites() XCTAssertTrue(favorites.contains(mangaSlug)) XCTAssertEqual(favorites.count, 1) } func testSaveMultipleFavorites() { // Given let slugs = ["solo-leveling", "tower-of-god", "the-beginning-after-the-end"] // When slugs.forEach { storageService.saveFavorite(mangaSlug: $0) } // Then let favorites = storageService.getFavorites() XCTAssertEqual(favorites.count, 3) XCTAssertTrue(favorites.contains("solo-leveling")) XCTAssertTrue(favorites.contains("tower-of-god")) XCTAssertTrue(favorites.contains("the-beginning-after-the-end")) } func testSaveDuplicateFavorite() { // Given let mangaSlug = "solo-leveling" // When storageService.saveFavorite(mangaSlug: mangaSlug) storageService.saveFavorite(mangaSlug: mangaSlug) // Intentar guardar duplicado // Then let favorites = storageService.getFavorites() XCTAssertEqual(favorites.count, 1, "Duplicate favorites should not be added") XCTAssertEqual(favorites.first, mangaSlug) } func testRemoveFavorite() { // Given let mangaSlug = "solo-leveling" storageService.saveFavorite(mangaSlug: mangaSlug) // When storageService.removeFavorite(mangaSlug: mangaSlug) // Then let favorites = storageService.getFavorites() XCTAssertFalse(favorites.contains(mangaSlug)) XCTAssertEqual(favorites.count, 0) } func testRemoveNonExistentFavorite() { // Given storageService.saveFavorite(mangaSlug: "manga1") storageService.saveFavorite(mangaSlug: "manga2") // When storageService.removeFavorite(mangaSlug: "non-existent") // Then let favorites = storageService.getFavorites() XCTAssertEqual(favorites.count, 2, "Removing non-existent favorite should not affect others") } func testIsFavorite() { // Given let favoriteSlug = "solo-leveling" let nonFavoriteSlug = "tower-of-god" storageService.saveFavorite(mangaSlug: favoriteSlug) // When & Then XCTAssertTrue(storageService.isFavorite(mangaSlug: favoriteSlug)) XCTAssertFalse(storageService.isFavorite(mangaSlug: nonFavoriteSlug)) } func testGetFavoritesWhenEmpty() { // When let favorites = storageService.getFavorites() // Then XCTAssertTrue(favorites.isEmpty) XCTAssertEqual(favorites.count, 0) } // MARK: - Reading Progress Tests func testSaveReadingProgress() { // Given let progress = ReadingProgress( mangaSlug: "solo-leveling", chapterNumber: 50, pageNumber: 10, timestamp: Date() ) // When storageService.saveReadingProgress(progress) // Then let retrievedProgress = storageService.getReadingProgress( mangaSlug: "solo-leveling", chapterNumber: 50 ) XCTAssertNotNil(retrievedProgress) XCTAssertEqual(retrievedProgress?.mangaSlug, "solo-leveling") XCTAssertEqual(retrievedProgress?.chapterNumber, 50) XCTAssertEqual(retrievedProgress?.pageNumber, 10) } func testSaveMultipleReadingProgress() { // Given let progress1 = ReadingProgress( mangaSlug: "manga1", chapterNumber: 1, pageNumber: 5, timestamp: Date() ) let progress2 = ReadingProgress( mangaSlug: "manga1", chapterNumber: 2, pageNumber: 15, timestamp: Date() ) let progress3 = ReadingProgress( mangaSlug: "manga2", chapterNumber: 1, pageNumber: 20, timestamp: Date() ) // When storageService.saveReadingProgress(progress1) storageService.saveReadingProgress(progress2) storageService.saveReadingProgress(progress3) // Then let allProgress = storageService.getAllReadingProgress() XCTAssertEqual(allProgress.count, 3) } func testUpdateExistingReadingProgress() { // Given let initialProgress = ReadingProgress( mangaSlug: "solo-leveling", chapterNumber: 50, pageNumber: 10, timestamp: Date(timeIntervalSince1970: 1609459200) ) let updatedProgress = ReadingProgress( mangaSlug: "solo-leveling", chapterNumber: 50, pageNumber: 25, timestamp: Date(timeIntervalSince1970: 1609459300) ) // When storageService.saveReadingProgress(initialProgress) storageService.saveReadingProgress(updatedProgress) // Then let retrieved = storageService.getReadingProgress( mangaSlug: "solo-leveling", chapterNumber: 50 ) XCTAssertEqual(retrieved?.pageNumber, 25, "Progress should be updated") XCTAssertEqual(retrieved?.timestamp.timeIntervalSince1970, 1609459300, accuracy: 0.001) } func testGetReadingProgressWhenNotExists() { // When let progress = storageService.getReadingProgress( mangaSlug: "non-existent", chapterNumber: 999 ) // Then XCTAssertNil(progress) } func testGetLastReadChapter() { // Given let oldDate = Date(timeIntervalSince1970: 1609459200) let newDate = Date(timeIntervalSince1970: 1609459300) let progress1 = ReadingProgress( mangaSlug: "manga1", chapterNumber: 1, pageNumber: 10, timestamp: oldDate ) let progress2 = ReadingProgress( mangaSlug: "manga1", chapterNumber: 2, pageNumber: 5, timestamp: newDate ) // When storageService.saveReadingProgress(progress1) storageService.saveReadingProgress(progress2) let lastRead = storageService.getLastReadChapter(mangaSlug: "manga1") // Then XCTAssertNotNil(lastRead) XCTAssertEqual(lastRead?.chapterNumber, 2, "Should return the most recent chapter") XCTAssertEqual(lastRead?.timestamp.timeIntervalSince1970, 1609459300, accuracy: 0.001) } func testGetLastReadChapterWhenNoProgress() { // When let lastRead = storageService.getLastReadChapter(mangaSlug: "non-existent") // Then XCTAssertNil(lastRead) } func testGetAllReadingProgressWhenEmpty() { // When let allProgress = storageService.getAllReadingProgress() // Then XCTAssertTrue(allProgress.isEmpty) } // MARK: - Downloaded Chapters Tests func testSaveDownloadedChapter() { // Given let pages = [ MangaPage(url: "page1.jpg", index: 0), MangaPage(url: "page2.jpg", index: 1) ] let downloadedChapter = DownloadedChapter( mangaSlug: "solo-leveling", mangaTitle: "Solo Leveling", chapterNumber: 50, pages: pages, downloadedAt: Date() ) // When storageService.saveDownloadedChapter(downloadedChapter) // Then let retrieved = storageService.getDownloadedChapter( mangaSlug: "solo-leveling", chapterNumber: 50 ) XCTAssertNotNil(retrieved) XCTAssertEqual(retrieved?.mangaSlug, "solo-leveling") XCTAssertEqual(retrieved?.chapterNumber, 50) XCTAssertEqual(retrieved?.pages.count, 2) } func testIsChapterDownloaded() { // Given let chapter = DownloadedChapter( mangaSlug: "test-manga", mangaTitle: "Test", chapterNumber: 10, pages: [], downloadedAt: Date() ) // When storageService.saveDownloadedChapter(chapter) // Then XCTAssertTrue(storageService.isChapterDownloaded( mangaSlug: "test-manga", chapterNumber: 10 )) XCTAssertFalse(storageService.isChapterDownloaded( mangaSlug: "test-manga", chapterNumber: 11 )) } func testGetDownloadedChapters() { // Given let chapter1 = DownloadedChapter( mangaSlug: "manga1", mangaTitle: "Manga 1", chapterNumber: 1, pages: [], downloadedAt: Date() ) let chapter2 = DownloadedChapter( mangaSlug: "manga2", mangaTitle: "Manga 2", chapterNumber: 5, pages: [], downloadedAt: Date() ) // When storageService.saveDownloadedChapter(chapter1) storageService.saveDownloadedChapter(chapter2) // Then let downloaded = storageService.getDownloadedChapters() XCTAssertEqual(downloaded.count, 2) } func testDeleteDownloadedChapter() { // Given let chapter = DownloadedChapter( mangaSlug: "test-manga", mangaTitle: "Test", chapterNumber: 10, pages: [], downloadedAt: Date() ) storageService.saveDownloadedChapter(chapter) XCTAssertTrue(storageService.isChapterDownloaded(mangaSlug: "test-manga", chapterNumber: 10)) // When storageService.deleteDownloadedChapter(mangaSlug: "test-manga", chapterNumber: 10) // Then XCTAssertFalse(storageService.isChapterDownloaded(mangaSlug: "test-manga", chapterNumber: 10)) } func testDeleteNonExistentDownloadedChapter() { // When - No debería lanzar error storageService.deleteDownloadedChapter(mangaSlug: "non-existent", chapterNumber: 999) // Then - Simplemente no hace nada let downloaded = storageService.getDownloadedChapters() XCTAssertTrue(downloaded.isEmpty) } // MARK: - Image Caching Tests func testSaveAndLoadImage() async throws { // Given let testImage = createTestImage(size: CGSize(width: 800, height: 1200)) // When let savedURL = try await storageService.saveImage( testImage, mangaSlug: "test-manga", chapterNumber: 1, pageIndex: 0 ) // Then XCTAssertTrue(FileManager.default.fileExists(atPath: savedURL.path)) XCTAssertEqual(savedURL.lastPathComponent, "page_0.jpg") let loadedImage = storageService.loadImage( mangaSlug: "test-manga", chapterNumber: 1, pageIndex: 0 ) XCTAssertNotNil(loadedImage) } func testLoadNonExistentImage() { // When let image = storageService.loadImage( mangaSlug: "non-existent", chapterNumber: 999, pageIndex: 999 ) // Then XCTAssertNil(image) } func testGetImageURL() async throws { // Given let testImage = createTestImage(size: CGSize(width: 800, height: 1200)) // When let savedURL = try await storageService.saveImage( testImage, mangaSlug: "test-manga", chapterNumber: 1, pageIndex: 0 ) let retrievedURL = storageService.getImageURL( mangaSlug: "test-manga", chapterNumber: 1, pageIndex: 0 ) // Then XCTAssertNotNil(retrievedURL) XCTAssertEqual(retrievedURL, savedURL) } func testGetImageURLForNonExistentImage() { // When let url = storageService.getImageURL( mangaSlug: "test", chapterNumber: 1, pageIndex: 0 ) // Then XCTAssertNil(url) } // MARK: - Storage Management Tests func testGetStorageSize() async throws { // Given let image1 = createTestImage(size: CGSize(width: 800, height: 1200)) let image2 = createTestImage(size: CGSize(width: 800, height: 1200)) // When _ = try await storageService.saveImage(image1, mangaSlug: "test", chapterNumber: 1, pageIndex: 0) _ = try await storageService.saveImage(image2, mangaSlug: "test", chapterNumber: 1, pageIndex: 1) let size = storageService.getStorageSize() // Then XCTAssertGreaterThan(size, 0, "Storage size should be greater than 0") } func testClearAllDownloads() async throws { // Given let image = createTestImage(size: CGSize(width: 800, height: 1200)) _ = try await storageService.saveImage(image, mangaSlug: "test", chapterNumber: 1, pageIndex: 0) let chapter = DownloadedChapter( mangaSlug: "test", mangaTitle: "Test", chapterNumber: 1, pages: [], downloadedAt: Date() ) storageService.saveDownloadedChapter(chapter) // When storageService.clearAllDownloads() // Then let downloaded = storageService.getDownloadedChapters() XCTAssertTrue(downloaded.isEmpty) let size = storageService.getStorageSize() XCTAssertEqual(size, 0, "Storage size should be 0 after clearing") } func testFormatFileSize() { // Given let bytes: Int64 = 1536000 // ~1.5 MB // When let formatted = storageService.formatFileSize(bytes) // Then XCTAssertTrue(formatted.contains("MB") || formatted.contains("KB")) } func testFormatFileSizeWithVariousSizes() { let tests: [(Int64, String)] = [ (500, "B"), // Bytes (1024, "KB"), // 1 KB (1048576, "MB"), // 1 MB (1073741824, "GB") // 1 GB ] for (size, expectedUnit) in { let formatted = storageService.formatFileSize(size) XCTAssertTrue( formatted.contains(expectedUnit), "Expected \(expectedUnit) in formatted string: \(formatted)" ) } } // MARK: - Directory Management Tests func testGetChapterDirectory() { // When let directory = storageService.getChapterDirectory(mangaSlug: "test-manga", chapterNumber: 10) // Then XCTAssertTrue(directory.path.contains("test-manga")) XCTAssertTrue(directory.path.contains("Chapter10")) } func testChapterDirectoryCreation() async throws { // Given let image = createTestImage(size: CGSize(width: 100, height: 100)) // When let savedURL = try await storageService.saveImage( image, mangaSlug: "new-manga", chapterNumber: 1, pageIndex: 0 ) // Then let directory = savedURL.deletingLastPathComponent() XCTAssertTrue(FileManager.default.fileExists(atPath: directory.path)) } // MARK: - Edge Cases Tests func testSaveFavoriteWithEmptySlug() { // When storageService.saveFavorite(mangaSlug: "") // Then let favorites = storageService.getFavorites() XCTAssertTrue(favorites.contains(""), "Empty slug should be saved") } func testSaveFavoriteWithSpecialCharacters() { // Given let specialSlug = "manga-with-special-chars-áéíóú-ñ-@#$" // When storageService.saveFavorite(mangaSlug: specialSlug) // Then XCTAssertTrue(storageService.isFavorite(mangaSlug: specialSlug)) } func testReadingProgressWithZeroPage() { // Given let progress = ReadingProgress( mangaSlug: "test", chapterNumber: 1, pageNumber: 0, timestamp: Date() ) // When storageService.saveReadingProgress(progress) // Then let retrieved = storageService.getReadingProgress(mangaSlug: "test", chapterNumber: 1) XCTAssertEqual(retrieved?.pageNumber, 0) } func testDownloadedChapterWithZeroChapterNumber() { // Given let chapter = DownloadedChapter( mangaSlug: "test", mangaTitle: "Test", chapterNumber: 0, pages: [], downloadedAt: Date() ) // When storageService.saveDownloadedChapter(chapter) // Then XCTAssertTrue(storageService.isChapterDownloaded(mangaSlug: "test", chapterNumber: 0)) } func testConcurrentImageSave() async throws { // Given let images = (0..<10).map { _ in createTestImage(size: CGSize(width: 800, height: 1200)) } // When - Guardar imágenes concurrentemente await withTaskGroup(of: URL.self) { group in for (index, image) in images.enumerated() { group.addTask { try! await self.storageService.saveImage( image, mangaSlug: "concurrent-test", chapterNumber: 1, pageIndex: index ) } } } // Then - Verificar que todas se guardaron for index in 0..<10 { let image = storageService.loadImage( mangaSlug: "concurrent-test", chapterNumber: 1, pageIndex: index ) XCTAssertNotNil(image, "Image at index \(index) should exist") } } // MARK: - Helper Methods private func createTestImage(size: CGSize) -> UIImage { let renderer = UIGraphicsImageRenderer(size: size) return renderer.image { context in UIColor.blue.setFill() context.fill(CGRect(origin: .zero, size: size)) } } // MARK: - Performance Tests func testSaveManyFavoritesPerformance() { measure { for i in 0..<1000 { storageService.saveFavorite(mangaSlug: "manga-\(i)") } } } func testSaveManyReadingProgressPerformance() { let progresses = (0..<100).map { i in ReadingProgress( mangaSlug: "manga-\(i % 10)", chapterNumber: i, pageNumber: i * 2, timestamp: Date() ) } measure { for progress in progresses { storageService.saveReadingProgress(progress) } } } }