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

529 lines
15 KiB
Swift

import XCTest
@testable import MangaReader
/// Tests unitarios para DownloadManager
///
/// Estos tests deben agregarse a tu target de tests en Xcode
@MainActor
class DownloadManagerTests: XCTestCase {
var downloadManager: DownloadManager!
var storage: StorageService!
override func setUp() async throws {
downloadManager = DownloadManager.shared
storage = StorageService.shared
// Limpiar estado antes de cada test
downloadManager.cancelAllDownloads()
downloadManager.clearCompletedHistory()
downloadManager.clearFailedHistory()
}
override func tearDown() async throws {
// Limpiar estado después de cada test
downloadManager.cancelAllDownloads()
}
// MARK: - Test: Descarga Individual
func testDownloadSingleChapter() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapter = Chapter(
number: 1,
title: "Chapter 1",
url: "https://example.com/chapter1",
slug: "chapter-1"
)
// When
try await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
// Then
XCTAssertEqual(downloadManager.activeDownloads.count, 0, "No debe haber descargas activas")
XCTAssertEqual(downloadManager.completedDownloads.count, 1, "Debe haber una descarga completada")
XCTAssertTrue(storage.isChapterDownloaded(mangaSlug: mangaSlug, chapterNumber: 1), "El capítulo debe estar descargado")
}
func testDownloadAlreadyDownloadedChapter() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapter = Chapter(
number: 1,
title: "Chapter 1",
url: "https://example.com/chapter1",
slug: "chapter-1"
)
// Descargar por primera vez
try await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
// When & Then - Intentar descargar nuevamente
do {
try await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
XCTFail("Debe lanzar error alreadyDownloaded")
} catch DownloadError.alreadyDownloaded {
// Éxito - Error esperado
} catch {
XCTFail("Error incorrecto: \(error)")
}
}
// MARK: - Test: Descarga Múltiple
func testDownloadMultipleChapters() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapters = [
Chapter(number: 1, title: "Chapter 1", url: "https://example.com/ch1", slug: "ch1"),
Chapter(number: 2, title: "Chapter 2", url: "https://example.com/ch2", slug: "ch2"),
Chapter(number: 3, title: "Chapter 3", url: "https://example.com/ch3", slug: "ch3")
]
// When
await downloadManager.downloadChapters(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapters: chapters
)
// Esperar a que todas terminen
try await Task.sleep(nanoseconds: 10_000_000_000) // 10 segundos
// Then
XCTAssertEqual(downloadManager.completedDownloads.count, 3, "Debe haber 3 descargas completadas")
}
// MARK: - Test: Cancelación
func testCancelDownload() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapter = Chapter(
number: 1,
title: "Chapter 1",
url: "https://example.com/chapter1",
slug: "chapter-1"
)
// Iniciar descarga en background
Task {
try? await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
}
// Esperar un poco para que inicie
try await Task.sleep(nanoseconds: 500_000_000) // 0.5 segundos
// When
guard let task = downloadManager.activeDownloads.first else {
XCTFail("Debe haber una descarga activa")
return
}
downloadManager.cancelDownload(taskId: task.id)
// Then
XCTAssertEqual(downloadManager.activeDownloads.count, 0, "No debe haber descargas activas")
XCTAssertFalse(storage.isChapterDownloaded(mangaSlug: mangaSlug, chapterNumber: 1), "El capítulo no debe estar descargado")
}
func testCancelAllDownloads() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapters = [
Chapter(number: 1, title: "Chapter 1", url: "https://example.com/ch1", slug: "ch1"),
Chapter(number: 2, title: "Chapter 2", url: "https://example.com/ch2", slug: "ch2"),
Chapter(number: 3, title: "Chapter 3", url: "https://example.com/ch3", slug: "ch3")
]
// Iniciar descargas
Task {
await downloadManager.downloadChapters(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapters: chapters
)
}
// Esperar un poco
try await Task.sleep(nanoseconds: 500_000_000) // 0.5 segundos
// When
downloadManager.cancelAllDownloads()
// Then
XCTAssertEqual(downloadManager.activeDownloads.count, 0, "No debe haber descargas activas")
}
// MARK: - Test: Progreso
func testDownloadProgress() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapter = Chapter(
number: 1,
title: "Chapter 1",
url: "https://example.com/chapter1",
slug: "chapter-1"
)
// Expectation para progreso
let progressExpectation = expectation(description: "Progreso actualizado")
// Observer de progreso
let cancellable = downloadManager.$activeDownloads.sink { tasks in
if let task = tasks.first {
if task.progress > 0 && task.progress < 1 {
progressExpectation.fulfill()
}
}
}
// When
Task {
try? await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
}
// Then
await fulfillment(of: [progressExpectation], timeout: 5.0)
cancellable.cancel()
}
// MARK: - Test: Errores
func testDownloadWithInvalidURL() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapter = Chapter(
number: 1,
title: "Chapter 1",
url: "invalid-url",
slug: "invalid-chapter"
)
// When & Then
do {
try await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
XCTFail("Debe lanzar error")
} catch {
// Éxito - Se espera un error
XCTAssertNotNil(error, "Debe haber un error")
}
}
// MARK: - Test: Concurrencia
func testMaxConcurrentDownloads() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapters = (1...10).map { i in
Chapter(number: i, title: "Chapter \(i)", url: "https://example.com/ch\(i)", slug: "ch\(i)")
}
// When
Task {
await downloadManager.downloadChapters(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapters: chapters
)
}
// Esperar un poco
try await Task.sleep(nanoseconds: 1_000_000_000) // 1 segundo
// Then - No debe exceder el máximo de descargas concurrentes
XCTAssertLessThanOrEqual(
downloadManager.activeDownloads.count,
3,
"No debe haber más de 3 descargas activas simultáneas"
)
}
// MARK: - Test: Storage
func testStorageIntegration() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapter = Chapter(
number: 1,
title: "Chapter 1",
url: "https://example.com/chapter1",
slug: "chapter-1"
)
// When
try await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
// Then - Verificar integración con StorageService
XCTAssertTrue(
storage.isChapterDownloaded(mangaSlug: mangaSlug, chapterNumber: 1),
"StorageService debe reportar el capítulo como descargado"
)
XCTAssertNotNil(
storage.getDownloadedChapter(mangaSlug: mangaSlug, chapterNumber: 1),
"StorageService debe retornar metadata del capítulo"
)
let chapterDir = storage.getChapterDirectory(mangaSlug: mangaSlug, chapterNumber: 1)
XCTAssertTrue(
FileManager.default.fileExists(atPath: chapterDir.path),
"El directorio del capítulo debe existir"
)
}
func testClearStorage() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapter = Chapter(
number: 1,
title: "Chapter 1",
url: "https://example.com/chapter1",
slug: "chapter-1"
)
// Descargar capítulo
try await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
// When
storage.deleteDownloadedChapter(mangaSlug: mangaSlug, chapterNumber: 1)
// Then
XCTAssertFalse(
storage.isChapterDownloaded(mangaSlug: mangaSlug, chapterNumber: 1),
"El capítulo no debe estar descargado después de eliminarlo"
)
XCTAssertEqual(
storage.getStorageSize(),
0,
"El tamaño de almacenamiento debe ser 0"
)
}
// MARK: - Test: Estadísticas
func testDownloadStats() async throws {
// Given
let initialStats = downloadManager.downloadStats
// When
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapter = Chapter(
number: 1,
title: "Chapter 1",
url: "https://example.com/chapter1",
slug: "chapter-1"
)
try await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
let finalStats = downloadManager.downloadStats
// Then
XCTAssertEqual(
finalStats.completedDownloads,
initialStats.completedDownloads + 1,
"Las descargas completadas deben incrementar en 1"
)
}
// MARK: - Test: Historiales
func testClearCompletedHistory() async throws {
// Given
let mangaSlug = "test-manga"
let mangaTitle = "Test Manga"
let chapter = Chapter(
number: 1,
title: "Chapter 1",
url: "https://example.com/chapter1",
slug: "chapter-1"
)
try await downloadManager.downloadChapter(
mangaSlug: mangaSlug,
mangaTitle: mangaTitle,
chapter: chapter
)
// When
downloadManager.clearCompletedHistory()
// Then
XCTAssertEqual(
downloadManager.completedDownloads.count,
0,
"El historial de completadas debe estar vacío"
)
}
}
/// Tests para DownloadTask
@MainActor
class DownloadTaskTests: XCTestCase {
func testDownloadTaskInitialization() {
// Given
let task = DownloadTask(
mangaSlug: "test-manga",
mangaTitle: "Test Manga",
chapterNumber: 1,
chapterTitle: "Chapter 1",
imageURLs: ["url1", "url2", "url3"]
)
// Then
XCTAssertEqual(task.mangaSlug, "test-manga")
XCTAssertEqual(task.chapterNumber, 1)
XCTAssertEqual(task.imageURLs.count, 3)
XCTAssertEqual(task.downloadedPages, 0)
XCTAssertEqual(task.progress, 0.0)
}
func testDownloadTaskProgress() {
// Given
let task = DownloadTask(
mangaSlug: "test-manga",
mangaTitle: "Test Manga",
chapterNumber: 1,
chapterTitle: "Chapter 1",
imageURLs: ["url1", "url2", "url3", "url4", "url5"]
)
// When
task.updateProgress(downloaded: 2, total: 5)
// Then
XCTAssertEqual(task.downloadedPages, 2)
XCTAssertEqual(task.progress, 0.4, accuracy: 0.01)
}
func testDownloadTaskCompletion() {
// Given
let task = DownloadTask(
mangaSlug: "test-manga",
mangaTitle: "Test Manga",
chapterNumber: 1,
chapterTitle: "Chapter 1",
imageURLs: ["url1", "url2", "url3"]
)
// When
task.complete()
// Then
XCTAssertTrue(task.state.isCompleted)
}
func testDownloadTaskCancellation() {
// Given
let task = DownloadTask(
mangaSlug: "test-manga",
mangaTitle: "Test Manga",
chapterNumber: 1,
chapterTitle: "Chapter 1",
imageURLs: ["url1", "url2", "url3"]
)
// When
task.cancel()
// Then
XCTAssertTrue(task.isCancelled)
XCTAssertTrue(task.state.isTerminal)
}
}
/// Tests para Extensions
class DownloadExtensionsTests: XCTestCase {
func testDownloadTaskFormattedSize() {
// Given
let task = DownloadTask(
mangaSlug: "test-manga",
mangaTitle: "Test Manga",
chapterNumber: 1,
chapterTitle: "Chapter 1",
imageURLs: Array(repeating: "url", count: 10)
)
// When
let size = task.formattedSize
// Then
XCTAssertFalse(size.isEmpty, "El tamaño formateado no debe estar vacío")
}
func testUIImageOptimization() {
// Given
let imageSize = CGSize(width: 3000, height: 4000)
UIGraphicsBeginImageContextWithOptions(imageSize, false, 1.0)
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.blue.cgColor)
context?.fill(CGRect(origin: .zero, size: imageSize))
let largeImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let image = largeImage else {
XCTFail("No se pudo crear la imagen de prueba")
return
}
// When
let optimizedData = image.optimizedForStorage()
// Then
XCTAssertNotNil(optimizedData, "Debe generar datos optimizados")
XCTAssertTrue(optimizedData!.count > 0, "Los datos no deben estar vacíos")
}
}