Files
MangaReader/ios-app/Tests/StorageServiceTests.swift
renato97 b474182dd9 Initial commit: MangaReader iOS App
 Features:
- App iOS completa para leer manga sin publicidad
- Scraper con WKWebView para manhwaweb.com
- Sistema de descargas offline
- Lector con zoom y navegación
- Favoritos y progreso de lectura
- Compatible con iOS 15+ y Sideloadly/3uTools

📦 Contenido:
- Backend Node.js con Puppeteer (opcional)
- App iOS con SwiftUI
- Scraper de capítulos e imágenes
- Sistema de almacenamiento local
- Testing completo
- Documentación exhaustiva

🧪 Prueba: Capítulo 789 de One Piece descargado exitosamente
  - 21 páginas descargadas
  - 4.68 MB total
  - URLs verificadas y funcionales

🎉 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-04 15:34:18 +01:00

687 lines
20 KiB
Swift

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)
}
}
}
}