Files
MangaReader/ios-app/Tests/IntegrationTests.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

610 lines
20 KiB
Swift

import XCTest
import UIKit
@testable import MangaReader
/// Tests de integración para el flujo completo: scraper -> storage -> view
/// Pruebas de descarga de capítulos y navegación entre vistas
@MainActor
final class IntegrationTests: XCTestCase {
var scraper: ManhwaWebScraper!
var storageService: StorageService!
// MARK: - Setup & Teardown
override func setUp() async throws {
try await super.setUp()
// Limpiar estado antes de cada test
UserDefaults.standard.removeObject(forKey: "favoriteMangas")
UserDefaults.standard.removeObject(forKey: "readingProgress")
scraper = ManhwaWebScraper.shared
storageService = StorageService.shared
storageService.clearAllDownloads()
// Esperar un momento para asegurar limpieza completa
try await Task.sleep(nanoseconds: 100_000_000) // 0.1 segundos
}
override func tearDown() async throws {
// Limpiar después de los tests
storageService.clearAllDownloads()
UserDefaults.standard.removeObject(forKey: "favoriteMangas")
UserDefaults.standard.removeObject(forKey: "readingProgress")
scraper = nil
storageService = nil
try await super.tearDown()
}
// MARK: - Complete Flow Tests: Scraper -> Storage
func testCompleteScrapingAndStorageFlow() async throws {
// Este test simula el flujo completo: scraper -> storage
// 1. Simular datos del scraper (capítulos)
let mockChapters = [
Chapter(number: 10, title: "Chapter 10", url: "url10", slug: "slug10"),
Chapter(number: 9, title: "Chapter 9", url: "url9", slug: "slug9"),
Chapter(number: 8, title: "Chapter 8", url: "url8", slug: "slug8")
]
// 2. Guardar progreso de lectura simulado
let progress = ReadingProgress(
mangaSlug: "test-manga",
chapterNumber: 9,
pageNumber: 5,
timestamp: Date()
)
storageService.saveReadingProgress(progress)
// 3. Verificar que el progreso se guardó
let retrievedProgress = storageService.getReadingProgress(
mangaSlug: "test-manga",
chapterNumber: 9
)
XCTAssertNotNil(retrievedProgress)
XCTAssertEqual(retrievedProgress?.pageNumber, 5)
// 4. Marcar manga como favorito
storageService.saveFavorite(mangaSlug: "test-manga")
// 5. Verificar favoritos
let favorites = storageService.getFavorites()
XCTAssertTrue(favorites.contains("test-manga"))
// 6. Simular guardado de capítulo descargado
let pages = mockChapters[0].number == 10 ? [
MangaPage(url: "page1.jpg", index: 0),
MangaPage(url: "page2.jpg", index: 1)
] : []
let downloadedChapter = DownloadedChapter(
mangaSlug: "test-manga",
mangaTitle: "Test Manga",
chapterNumber: 10,
pages: pages,
downloadedAt: Date()
)
storageService.saveDownloadedChapter(downloadedChapter)
// 7. Verificar capítulo descargado
XCTAssertTrue(storageService.isChapterDownloaded(
mangaSlug: "test-manga",
chapterNumber: 10
))
// 8. Obtener todos los datos relacionados
let allProgress = storageService.getAllReadingProgress()
let lastRead = storageService.getLastReadChapter(mangaSlug: "test-manga")
let downloadedChapters = storageService.getDownloadedChapters()
XCTAssertEqual(allProgress.count, 1)
XCTAssertNotNil(lastRead)
XCTAssertEqual(downloadedChapters.count, 1)
}
func testChapterDownloadFlow() async throws {
// Simular el flujo completo de descarga de un capítulo
// 1. Crear imágenes de prueba
let image1 = createTestImage(color: .red, size: CGSize(width: 800, height: 1200))
let image2 = createTestImage(color: .blue, size: CGSize(width: 800, height: 1200))
let image3 = createTestImage(color: .green, size: CGSize(width: 800, height: 1200))
// 2. Guardar imágenes
let url1 = try await storageService.saveImage(
image1,
mangaSlug: "test-manga",
chapterNumber: 1,
pageIndex: 0
)
let url2 = try await storageService.saveImage(
image2,
mangaSlug: "test-manga",
chapterNumber: 1,
pageIndex: 1
)
let url3 = try await storageService.saveImage(
image3,
mangaSlug: "test-manga",
chapterNumber: 1,
pageIndex: 2
)
// 3. Verificar que las imágenes se guardaron
XCTAssertTrue(FileManager.default.fileExists(atPath: url1.path))
XCTAssertTrue(FileManager.default.fileExists(atPath: url2.path))
XCTAssertTrue(FileManager.default.fileExists(atPath: url3.path))
// 4. Crear objeto de capítulo descargado
let pages = [
MangaPage(url: url1.absoluteString, index: 0, isCached: true),
MangaPage(url: url2.absoluteString, index: 1, isCached: true),
MangaPage(url: url3.absoluteString, index: 2, isCached: true)
]
let downloadedChapter = DownloadedChapter(
mangaSlug: "test-manga",
mangaTitle: "Test Manga",
chapterNumber: 1,
pages: pages,
downloadedAt: Date()
)
// 5. Guardar metadatos del capítulo
storageService.saveDownloadedChapter(downloadedChapter)
// 6. Verificar que el capítulo está marcado como descargado
XCTAssertTrue(storageService.isChapterDownloaded(
mangaSlug: "test-manga",
chapterNumber: 1
))
// 7. Recuperar el capítulo descargado
let retrieved = storageService.getDownloadedChapter(
mangaSlug: "test-manga",
chapterNumber: 1
)
XCTAssertNotNil(retrieved)
XCTAssertEqual(retrieved?.pages.count, 3)
// 8. Cargar imágenes desde disco
let loadedImage1 = storageService.loadImage(
mangaSlug: "test-manga",
chapterNumber: 1,
pageIndex: 0
)
let loadedImage2 = storageService.loadImage(
mangaSlug: "test-manga",
chapterNumber: 1,
pageIndex: 1
)
let loadedImage3 = storageService.loadImage(
mangaSlug: "test-manga",
chapterNumber: 1,
pageIndex: 2
)
XCTAssertNotNil(loadedImage1)
XCTAssertNotNil(loadedImage2)
XCTAssertNotNil(loadedImage3)
}
func testReadingProgressTrackingFlow() async throws {
// Simular el flujo de seguimiento de progreso de lectura
let mangaSlug = "tower-of-god"
// 1. Usuario comienza a leer capítulo 1
let progress1 = ReadingProgress(
mangaSlug: mangaSlug,
chapterNumber: 1,
pageNumber: 0,
timestamp: Date()
)
storageService.saveReadingProgress(progress1)
// 2. Usuario avanza a página 5
let progress2 = ReadingProgress(
mangaSlug: mangaSlug,
chapterNumber: 1,
pageNumber: 5,
timestamp: Date().addingTimeInterval(60) // 1 minuto después
)
storageService.saveReadingProgress(progress2)
// 3. Usuario cambia al capítulo 2
let progress3 = ReadingProgress(
mangaSlug: mangaSlug,
chapterNumber: 2,
pageNumber: 0,
timestamp: Date().addingTimeInterval(120) // 2 minutos después
)
storageService.saveReadingProgress(progress3)
// 4. Usuario lee capítulo 2 hasta página 10
let progress4 = ReadingProgress(
mangaSlug: mangaSlug,
chapterNumber: 2,
pageNumber: 10,
timestamp: Date().addingTimeInterval(300) // 5 minutos después
)
storageService.saveReadingProgress(progress4)
// 5. Verificar progreso del capítulo 1
let ch1Progress = storageService.getReadingProgress(
mangaSlug: mangaSlug,
chapterNumber: 1
)
XCTAssertEqual(ch1Progress?.pageNumber, 5)
// 6. Verificar progreso del capítulo 2
let ch2Progress = storageService.getReadingProgress(
mangaSlug: mangaSlug,
chapterNumber: 2
)
XCTAssertEqual(ch2Progress?.pageNumber, 10)
// 7. Verificar último capítulo leído
let lastRead = storageService.getLastReadChapter(mangaSlug: mangaSlug)
XCTAssertEqual(lastRead?.chapterNumber, 2)
// 8. Verificar que el capítulo se marca como completado
XCTAssertTrue(ch2Progress?.isCompleted ?? false)
}
func testFavoriteManagementFlow() {
// Simular el flujo de gestión de favoritos
let mangaSlugs = [
"solo-leveling",
"tower-of-god",
"the-beginning-after-the-end",
"omniscient-reader"
]
// 1. Agregar varios favoritos
mangaSlugs.forEach { slug in
storageService.saveFavorite(mangaSlug: slug)
}
// 2. Verificar que todos están en favoritos
let favorites = storageService.getFavorites()
XCTAssertEqual(favorites.count, 4)
mangaSlugs.forEach { slug in
XCTAssertTrue(storageService.isFavorite(mangaSlug: slug))
}
// 3. Remover uno
storageService.removeFavorite(mangaSlug: "tower-of-god")
// 4. Verificar que se eliminó
XCTAssertFalse(storageService.isFavorite(mangaSlug: "tower-of-god"))
XCTAssertEqual(storageService.getFavorites().count, 3)
// 5. Intentar agregar duplicado
storageService.saveFavorite(mangaSlug: "solo-leveling")
// 6. Verificar que no se duplicó
let updatedFavorites = storageService.getFavorites()
XCTAssertEqual(updatedFavorites.count, 3)
// 7. Contar ocurrencias
let soloLevelingCount = updatedFavorites.filter { $0 == "solo-leveling" }.count
XCTAssertEqual(soloLevelingCount, 1, "Should only appear once")
}
// MARK: - Multi-Manga Scenarios
func testMultipleMangasProgressTracking() async throws {
// Simular seguimiento de progreso para múltiples mangas
let mangas = [
"solo-leveling",
"tower-of-god",
"the-beginning-after-the-end"
]
// Agregar progreso para cada manga
for (index, manga) in mangas.enumerated() {
let progress = ReadingProgress(
mangaSlug: manga,
chapterNumber: index + 1,
pageNumber: (index + 1) * 10,
timestamp: Date().addingTimeInterval(Double(index * 100))
)
storageService.saveReadingProgress(progress)
}
// Verificar progreso individual
for (index, manga) in mangas.enumerated() {
let progress = storageService.getLastReadChapter(mangaSlug: manga)
XCTAssertNotNil(progress)
XCTAssertEqual(progress?.chapterNumber, index + 1)
}
// Verificar todo el progreso
let allProgress = storageService.getAllReadingProgress()
XCTAssertEqual(allProgress.count, 3)
}
func testMultipleChapterDownloads() async throws {
// Simular descarga de múltiples capítulos de diferentes mangas
let downloads = [
("manga1", 1),
("manga1", 2),
("manga2", 1),
("manga2", 3),
("manga3", 5)
]
// Crear y guardar capítulos descargados
for (manga, chapter) in downloads {
let chapter = DownloadedChapter(
mangaSlug: manga,
mangaTitle: "Manga \(manga)",
chapterNumber: chapter,
pages: [],
downloadedAt: Date()
)
storageService.saveDownloadedChapter(chapter)
}
// Verificar todos los capítulos descargados
let allDownloaded = storageService.getDownloadedChapters()
XCTAssertEqual(allDownloaded.count, 5)
// Verificar capítulos por manga
let manga1Chapters = allDownloaded.filter { $0.mangaSlug == "manga1" }
XCTAssertEqual(manga1Chapters.count, 2)
let manga2Chapters = allDownloaded.filter { $0.mangaSlug == "manga2" }
XCTAssertEqual(manga2Chapters.count, 2)
let manga3Chapters = allDownloaded.filter { $0.mangaSlug == "manga3" }
XCTAssertEqual(manga3Chapters.count, 1)
}
// MARK: - Error Handling Scenarios
func testDownloadFlowWithMissingImages() async throws {
// Simular descarga con imágenes faltantes
// Guardar solo algunas imágenes
let image1 = createTestImage(color: .red, size: CGSize(width: 800, height: 1200))
_ = try await storageService.saveImage(
image1,
mangaSlug: "test-manga",
chapterNumber: 1,
pageIndex: 0
)
// Intentar cargar imagen que no existe
let missingImage = storageService.loadImage(
mangaSlug: "test-manga",
chapterNumber: 1,
pageIndex: 1
)
XCTAssertNil(missingImage, "Missing image should return nil")
// Verificar que la imagen existente sí se carga
let existingImage = storageService.loadImage(
mangaSlug: "test-manga",
chapterNumber: 1,
pageIndex: 0
)
XCTAssertNotNil(existingImage, "Existing image should load successfully")
}
func testStorageCleanupFlow() async throws {
// Simular flujo de limpieza de almacenamiento
// 1. Llenar almacenamiento con datos
for i in 0..<5 {
let image = createTestImage(color: .blue, size: CGSize(width: 800, height: 1200))
_ = try await storageService.saveImage(
image,
mangaSlug: "test",
chapterNumber: i,
pageIndex: 0
)
let chapter = DownloadedChapter(
mangaSlug: "test",
mangaTitle: "Test",
chapterNumber: i,
pages: [],
downloadedAt: Date()
)
storageService.saveDownloadedChapter(chapter)
}
// Verificar que hay datos
XCTAssertGreaterThan(storageService.getStorageSize(), 0)
XCTAssertEqual(storageService.getDownloadedChapters().count, 5)
// 2. Limpiar todo
storageService.clearAllDownloads()
// 3. Verificar que todo se eliminó
XCTAssertEqual(storageService.getStorageSize(), 0)
XCTAssertEqual(storageService.getDownloadedChapters().count, 0)
}
// MARK: - Data Persistence Tests
func testDataPersistenceAcrossOperations() async throws {
// Verificar que los datos persisten a través de múltiples operaciones
// 1. Guardar datos iniciales
storageService.saveFavorite(mangaSlug: "persistent-manga")
let progress1 = ReadingProgress(
mangaSlug: "persistent-manga",
chapterNumber: 1,
pageNumber: 5,
timestamp: Date()
)
storageService.saveReadingProgress(progress1)
// 2. Verificar que existen
XCTAssertTrue(storageService.isFavorite(mangaSlug: "persistent-manga"))
XCTAssertNotNil(storageService.getReadingProgress(
mangaSlug: "persistent-manga",
chapterNumber: 1
))
// 3. Realizar operaciones intermedias
storageService.saveFavorite(mangaSlug: "temp-manga")
storageService.removeFavorite(mangaSlug: "temp-manga")
// 4. Verificar que los datos originales persistieron
XCTAssertTrue(storageService.isFavorite(mangaSlug: "persistent-manga"))
let originalProgress = storageService.getReadingProgress(
mangaSlug: "persistent-manga",
chapterNumber: 1
)
XCTAssertEqual(originalProgress?.pageNumber, 5)
}
// MARK: - Concurrent Operations Tests
func testConcurrentFavoriteOperations() {
// Probar operaciones concurrentes en favoritos
let expectations = (0..<10).map { _ in
XCTestExpectation(description: "Favorite operation")
}
let queue = DispatchQueue.global(qos: .userInitiated)
for i in 0..<10 {
queue.async {
self.storageService.saveFavorite(mangaSlug: "manga-\(i)")
expectations[i].fulfill()
}
}
wait(for: expectations, timeout: 5.0)
let favorites = storageService.getFavorites()
XCTAssertEqual(favorites.count, 10)
}
func testConcurrentProgressOperations() {
// Probar operaciones concurrentes de progreso
let expectations = (0..<10).map { _ in
XCTestExpectation(description: "Progress operation")
}
let queue = DispatchQueue.global(qos: .userInitiated)
for i in 0..<10 {
queue.async {
let progress = ReadingProgress(
mangaSlug: "manga-\(i % 3)",
chapterNumber: i,
pageNumber: i * 2,
timestamp: Date()
)
self.storageService.saveReadingProgress(progress)
expectations[i].fulfill()
}
}
wait(for: expectations, timeout: 5.0)
let allProgress = storageService.getAllReadingProgress()
XCTAssertEqual(allProgress.count, 10)
}
func testConcurrentImageOperations() async throws {
// Probar guardado concurrente de imágenes
await withTaskGroup(of: URL.self) { group in
for i in 0..<20 {
group.addTask {
let image = self.createTestImage(
color: .red,
size: CGSize(width: 800, height: 1200)
)
return try! await self.storageService.saveImage(
image,
mangaSlug: "concurrent-test",
chapterNumber: 1,
pageIndex: i
)
}
}
}
// Verificar que todas las imágenes se guardaron
for i in 0..<20 {
let image = storageService.loadImage(
mangaSlug: "concurrent-test",
chapterNumber: 1,
pageIndex: i
)
XCTAssertNotNil(image, "Image at index \(i) should exist")
}
}
// MARK: - Large Scale Tests
func testLargeScaleFavoriteOperations() {
// Probar con muchos favoritos
let count = 1000
measure {
for i in 0..<count {
storageService.saveFavorite(mangaSlug: "manga-\(i)")
}
}
let favorites = storageService.getFavorites()
XCTAssertEqual(favorites.count, count)
}
func testLargeScaleProgressOperations() {
// Probar con mucho progreso de lectura
let count = 500
measure {
for i in 0..<count {
let progress = ReadingProgress(
mangaSlug: "manga-\(i % 50)",
chapterNumber: i,
pageNumber: i,
timestamp: Date()
)
storageService.saveReadingProgress(progress)
}
}
let allProgress = storageService.getAllReadingProgress()
XCTAssertEqual(allProgress.count, count)
}
// MARK: - Helper Methods
private func createTestImage(color: UIColor, size: CGSize) -> UIImage {
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { context in
color.setFill()
context.fill(CGRect(origin: .zero, size: size))
}
}
}