mirror of
https://github.com/yattee/yattee.git
synced 2026-02-19 17:29:45 +00:00
494 lines
16 KiB
Swift
494 lines
16 KiB
Swift
//
|
|
// DownloadTests.swift
|
|
// YatteeTests
|
|
//
|
|
// Tests for the download system.
|
|
//
|
|
|
|
import Testing
|
|
import Foundation
|
|
@testable import Yattee
|
|
|
|
// MARK: - Download Model Tests
|
|
|
|
@Suite("Download Model Tests")
|
|
struct DownloadModelTests {
|
|
|
|
@Test("Download initialization from Video")
|
|
func initialization() {
|
|
let video = makeTestVideo()
|
|
let streamURL = URL(string: "https://example.com/stream.mp4")!
|
|
let download = Download(
|
|
video: video,
|
|
quality: "1080p",
|
|
formatID: "137",
|
|
streamURL: streamURL
|
|
)
|
|
|
|
#expect(download.videoID == video.id)
|
|
#expect(download.title == video.title)
|
|
#expect(download.channelName == video.author.name)
|
|
#expect(download.quality == "1080p")
|
|
#expect(download.formatID == "137")
|
|
#expect(download.status == .queued)
|
|
#expect(download.progress == 0)
|
|
#expect(download.priority == .normal)
|
|
#expect(download.autoDelete == false)
|
|
}
|
|
|
|
@Test("Download with high priority")
|
|
func highPriority() {
|
|
let video = makeTestVideo()
|
|
let streamURL = URL(string: "https://example.com/stream.mp4")!
|
|
let download = Download(
|
|
video: video,
|
|
quality: "720p",
|
|
formatID: "136",
|
|
streamURL: streamURL,
|
|
priority: .high
|
|
)
|
|
|
|
#expect(download.priority == .high)
|
|
}
|
|
|
|
@Test("Download with auto-delete enabled")
|
|
func autoDelete() {
|
|
let video = makeTestVideo()
|
|
let streamURL = URL(string: "https://example.com/stream.mp4")!
|
|
let download = Download(
|
|
video: video,
|
|
quality: "720p",
|
|
formatID: "136",
|
|
streamURL: streamURL,
|
|
autoDelete: true
|
|
)
|
|
|
|
#expect(download.autoDelete == true)
|
|
}
|
|
|
|
private func makeTestVideo() -> Video {
|
|
Video(
|
|
id: .global("testDownload"),
|
|
title: "Test Download Video",
|
|
description: "A video to test downloads",
|
|
author: Author(id: "ch1", name: "Test Channel"),
|
|
duration: 600,
|
|
publishedAt: Date(),
|
|
publishedText: "1 day ago",
|
|
viewCount: 10000,
|
|
likeCount: 500,
|
|
thumbnails: [
|
|
Thumbnail(url: URL(string: "https://example.com/thumb.jpg")!, quality: .high)
|
|
],
|
|
isLive: false,
|
|
isUpcoming: false,
|
|
scheduledStartTime: nil
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Download Status Tests
|
|
|
|
@Suite("Download Status Tests")
|
|
struct DownloadStatusTests {
|
|
|
|
@Test("Status raw values")
|
|
func rawValues() {
|
|
#expect(DownloadStatus.queued.rawValue == "queued")
|
|
#expect(DownloadStatus.downloading.rawValue == "downloading")
|
|
#expect(DownloadStatus.paused.rawValue == "paused")
|
|
#expect(DownloadStatus.completed.rawValue == "completed")
|
|
#expect(DownloadStatus.failed.rawValue == "failed")
|
|
}
|
|
|
|
@Test("Status is Codable")
|
|
func codable() throws {
|
|
for status in [DownloadStatus.queued, .downloading, .paused, .completed, .failed] {
|
|
let encoded = try JSONEncoder().encode(status)
|
|
let decoded = try JSONDecoder().decode(DownloadStatus.self, from: encoded)
|
|
#expect(status == decoded)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Download Priority Tests
|
|
|
|
@Suite("Download Priority Tests")
|
|
struct DownloadPriorityTests {
|
|
|
|
@Test("Priority ordering")
|
|
func ordering() {
|
|
#expect(DownloadPriority.low.rawValue < DownloadPriority.normal.rawValue)
|
|
#expect(DownloadPriority.normal.rawValue < DownloadPriority.high.rawValue)
|
|
}
|
|
|
|
@Test("Priority is Codable")
|
|
func codable() throws {
|
|
for priority in [DownloadPriority.low, .normal, .high] {
|
|
let encoded = try JSONEncoder().encode(priority)
|
|
let decoded = try JSONDecoder().decode(DownloadPriority.self, from: encoded)
|
|
#expect(priority == decoded)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - DownloadSortOption Tests
|
|
|
|
@Suite("DownloadSortOption Tests")
|
|
struct DownloadSortOptionTests {
|
|
|
|
@Test("All cases exist")
|
|
func allCases() {
|
|
let cases = DownloadSortOption.allCases
|
|
#expect(cases.contains(.name))
|
|
#expect(cases.contains(.downloadDate))
|
|
#expect(cases.contains(.fileSize))
|
|
#expect(cases.count == 3)
|
|
}
|
|
|
|
@Test("Display names are not empty")
|
|
func displayNames() {
|
|
for option in DownloadSortOption.allCases {
|
|
#expect(!option.displayName.isEmpty)
|
|
}
|
|
}
|
|
|
|
@Test("System images are valid SF Symbols")
|
|
func systemImages() {
|
|
#expect(DownloadSortOption.name.systemImage == "textformat")
|
|
#expect(DownloadSortOption.downloadDate.systemImage == "calendar")
|
|
#expect(DownloadSortOption.fileSize.systemImage == "internaldrive")
|
|
}
|
|
|
|
@Test("Is Codable")
|
|
func codable() throws {
|
|
for option in DownloadSortOption.allCases {
|
|
let encoded = try JSONEncoder().encode(option)
|
|
let decoded = try JSONDecoder().decode(DownloadSortOption.self, from: encoded)
|
|
#expect(option == decoded)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - SortDirection Tests
|
|
|
|
@Suite("SortDirection Tests")
|
|
struct SortDirectionTests {
|
|
|
|
@Test("All cases exist")
|
|
func allCases() {
|
|
let cases = SortDirection.allCases
|
|
#expect(cases.contains(.ascending))
|
|
#expect(cases.contains(.descending))
|
|
#expect(cases.count == 2)
|
|
}
|
|
|
|
@Test("System images are valid")
|
|
func systemImages() {
|
|
#expect(SortDirection.ascending.systemImage == "arrow.up")
|
|
#expect(SortDirection.descending.systemImage == "arrow.down")
|
|
}
|
|
|
|
@Test("Toggle switches direction")
|
|
func toggle() {
|
|
var direction = SortDirection.ascending
|
|
direction.toggle()
|
|
#expect(direction == .descending)
|
|
direction.toggle()
|
|
#expect(direction == .ascending)
|
|
}
|
|
|
|
@Test("Is Codable")
|
|
func codable() throws {
|
|
for direction in SortDirection.allCases {
|
|
let encoded = try JSONEncoder().encode(direction)
|
|
let decoded = try JSONDecoder().decode(SortDirection.self, from: encoded)
|
|
#expect(direction == decoded)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - DownloadSettings Tests
|
|
|
|
@Suite("DownloadSettings Tests")
|
|
@MainActor
|
|
struct DownloadSettingsTests {
|
|
|
|
@Test("Default sort option is downloadDate")
|
|
func defaultSortOption() {
|
|
// Clear existing defaults
|
|
UserDefaults.standard.removeObject(forKey: "downloads.sortOption")
|
|
let settings = DownloadSettings()
|
|
#expect(settings.sortOption == .downloadDate)
|
|
}
|
|
|
|
@Test("Default sort direction is descending")
|
|
func defaultSortDirection() {
|
|
// Clear existing defaults
|
|
UserDefaults.standard.removeObject(forKey: "downloads.sortDirection")
|
|
let settings = DownloadSettings()
|
|
#expect(settings.sortDirection == .descending)
|
|
}
|
|
|
|
@Test("Default groupByChannel is false")
|
|
func defaultGroupByChannel() {
|
|
// Clear existing defaults
|
|
UserDefaults.standard.removeObject(forKey: "downloads.groupByChannel")
|
|
let settings = DownloadSettings()
|
|
#expect(settings.groupByChannel == false)
|
|
}
|
|
|
|
@Test("Sort option persists")
|
|
func sortOptionPersists() {
|
|
UserDefaults.standard.removeObject(forKey: "downloads.sortOption")
|
|
let settings = DownloadSettings()
|
|
settings.sortOption = .name
|
|
|
|
// Create new instance to check persistence
|
|
let settings2 = DownloadSettings()
|
|
#expect(settings2.sortOption == .name)
|
|
}
|
|
|
|
@Test("Sort direction persists")
|
|
func sortDirectionPersists() {
|
|
UserDefaults.standard.removeObject(forKey: "downloads.sortDirection")
|
|
let settings = DownloadSettings()
|
|
settings.sortDirection = .ascending
|
|
|
|
let settings2 = DownloadSettings()
|
|
#expect(settings2.sortDirection == .ascending)
|
|
}
|
|
|
|
@Test("GroupByChannel persists")
|
|
func groupByChannelPersists() {
|
|
UserDefaults.standard.removeObject(forKey: "downloads.groupByChannel")
|
|
let settings = DownloadSettings()
|
|
settings.groupByChannel = true
|
|
|
|
let settings2 = DownloadSettings()
|
|
#expect(settings2.groupByChannel == true)
|
|
}
|
|
}
|
|
|
|
// MARK: - Download Error Tests
|
|
|
|
@Suite("Download Error Tests")
|
|
struct DownloadErrorTests {
|
|
|
|
@Test("Error descriptions are meaningful")
|
|
func errorDescriptions() {
|
|
let notSupported = DownloadError.notSupported
|
|
#expect(notSupported.errorDescription?.contains("not supported") == true)
|
|
|
|
let alreadyDownloading = DownloadError.alreadyDownloading
|
|
#expect(alreadyDownloading.errorDescription?.contains("already downloading") == true)
|
|
|
|
let alreadyDownloaded = DownloadError.alreadyDownloaded
|
|
#expect(alreadyDownloaded.errorDescription?.contains("already been downloaded") == true)
|
|
|
|
let noStream = DownloadError.noStreamAvailable
|
|
#expect(noStream.errorDescription?.contains("stream") == true)
|
|
|
|
let failed = DownloadError.downloadFailed("Network timeout")
|
|
#expect(failed.errorDescription?.contains("Network timeout") == true)
|
|
}
|
|
}
|
|
|
|
// MARK: - DownloadManager Tests
|
|
|
|
@Suite("DownloadManager Tests")
|
|
@MainActor
|
|
struct DownloadManagerTests {
|
|
|
|
init() {
|
|
// Clear UserDefaults for clean test state
|
|
UserDefaults.standard.removeObject(forKey: "activeDownloads")
|
|
UserDefaults.standard.removeObject(forKey: "completedDownloads")
|
|
}
|
|
|
|
@Test("Initial state with clean defaults")
|
|
func initialState() {
|
|
// Clear state before test
|
|
UserDefaults.standard.removeObject(forKey: "activeDownloads")
|
|
UserDefaults.standard.removeObject(forKey: "completedDownloads")
|
|
|
|
let manager = DownloadManager()
|
|
|
|
#expect(manager.activeDownloads.isEmpty)
|
|
#expect(manager.completedDownloads.isEmpty)
|
|
#expect(manager.storageUsed == 0)
|
|
#expect(manager.maxConcurrentDownloads == 2)
|
|
}
|
|
|
|
@Test("isDownloaded returns false for unknown video")
|
|
func isDownloadedUnknown() {
|
|
let manager = DownloadManager()
|
|
let videoID = VideoID.global("unknown")
|
|
|
|
#expect(manager.isDownloaded(videoID) == false)
|
|
}
|
|
|
|
@Test("isDownloading returns false for unknown video")
|
|
func isDownloadingUnknown() {
|
|
let manager = DownloadManager()
|
|
let videoID = VideoID.global("unknown")
|
|
|
|
#expect(manager.isDownloading(videoID) == false)
|
|
}
|
|
|
|
@Test("download(for:) returns nil for unknown video")
|
|
func downloadForUnknown() {
|
|
let manager = DownloadManager()
|
|
let videoID = VideoID.global("unknown")
|
|
|
|
#expect(manager.download(for: videoID) == nil)
|
|
}
|
|
|
|
@Test("localURL returns nil for unknown video")
|
|
func localURLUnknown() {
|
|
let manager = DownloadManager()
|
|
let videoID = VideoID.global("unknown")
|
|
|
|
#expect(manager.localURL(for: videoID) == nil)
|
|
}
|
|
|
|
@Test("Available storage returns non-negative value")
|
|
func availableStorage() {
|
|
let manager = DownloadManager()
|
|
let available = manager.getAvailableStorage()
|
|
|
|
#expect(available >= 0)
|
|
}
|
|
|
|
#if !os(tvOS)
|
|
@Test("Enqueue prevents duplicate downloads")
|
|
func enqueueDuplicatePrevention() async throws {
|
|
// Clear state before test
|
|
UserDefaults.standard.removeObject(forKey: "activeDownloads")
|
|
UserDefaults.standard.removeObject(forKey: "completedDownloads")
|
|
|
|
let manager = DownloadManager()
|
|
let video = makeTestVideo(id: "duplicate")
|
|
let streamURL = URL(string: "https://example.com/video.mp4")!
|
|
|
|
// First enqueue should succeed
|
|
try await manager.enqueue(video, quality: "720p", formatID: "136", streamURL: streamURL)
|
|
#expect(manager.activeDownloads.count == 1)
|
|
|
|
// Second enqueue should throw alreadyDownloading
|
|
do {
|
|
try await manager.enqueue(video, quality: "720p", formatID: "136", streamURL: streamURL)
|
|
Issue.record("Expected alreadyDownloading error")
|
|
} catch DownloadError.alreadyDownloading {
|
|
// Expected
|
|
} catch {
|
|
Issue.record("Unexpected error: \(error)")
|
|
}
|
|
|
|
#expect(manager.activeDownloads.count == 1)
|
|
}
|
|
|
|
@Test("Cancel removes download from queue")
|
|
func cancelRemovesDownload() async throws {
|
|
// Clear state before test
|
|
UserDefaults.standard.removeObject(forKey: "activeDownloads")
|
|
UserDefaults.standard.removeObject(forKey: "completedDownloads")
|
|
|
|
let manager = DownloadManager()
|
|
let video = makeTestVideo(id: "toCancel")
|
|
let streamURL = URL(string: "https://example.com/video.mp4")!
|
|
|
|
try await manager.enqueue(video, quality: "720p", formatID: "136", streamURL: streamURL)
|
|
#expect(manager.activeDownloads.count == 1)
|
|
|
|
let download = manager.activeDownloads.first!
|
|
await manager.cancel(download)
|
|
|
|
#expect(manager.activeDownloads.isEmpty)
|
|
}
|
|
|
|
@Test("Pause changes status")
|
|
func pauseChangesStatus() async throws {
|
|
// Clear state before test
|
|
UserDefaults.standard.removeObject(forKey: "activeDownloads")
|
|
UserDefaults.standard.removeObject(forKey: "completedDownloads")
|
|
|
|
let manager = DownloadManager()
|
|
let video = makeTestVideo(id: "toPause")
|
|
let streamURL = URL(string: "https://example.com/video.mp4")!
|
|
|
|
try await manager.enqueue(video, quality: "720p", formatID: "136", streamURL: streamURL)
|
|
let download = manager.activeDownloads.first!
|
|
|
|
await manager.pause(download)
|
|
|
|
let updated = manager.activeDownloads.first
|
|
#expect(updated?.status == .paused)
|
|
}
|
|
|
|
@Test("Resume changes status from paused to queued")
|
|
func resumeChangesStatus() async throws {
|
|
// Clear state before test
|
|
UserDefaults.standard.removeObject(forKey: "activeDownloads")
|
|
UserDefaults.standard.removeObject(forKey: "completedDownloads")
|
|
|
|
let manager = DownloadManager()
|
|
let video = makeTestVideo(id: "toResume")
|
|
let streamURL = URL(string: "https://example.com/video.mp4")!
|
|
|
|
try await manager.enqueue(video, quality: "720p", formatID: "136", streamURL: streamURL)
|
|
let download = manager.activeDownloads.first!
|
|
|
|
await manager.pause(download)
|
|
#expect(manager.activeDownloads.first?.status == .paused)
|
|
|
|
let pausedDownload = manager.activeDownloads.first!
|
|
await manager.resume(pausedDownload)
|
|
|
|
#expect(manager.activeDownloads.first?.status == .queued || manager.activeDownloads.first?.status == .downloading)
|
|
}
|
|
|
|
@Test("Move in queue reorders downloads")
|
|
func moveInQueue() async throws {
|
|
// Clear state before test
|
|
UserDefaults.standard.removeObject(forKey: "activeDownloads")
|
|
UserDefaults.standard.removeObject(forKey: "completedDownloads")
|
|
|
|
let manager = DownloadManager()
|
|
|
|
// Enqueue multiple downloads
|
|
for i in 1...3 {
|
|
let video = makeTestVideo(id: "move\(i)")
|
|
let streamURL = URL(string: "https://example.com/video\(i).mp4")!
|
|
try await manager.enqueue(video, quality: "720p", formatID: "136", streamURL: streamURL)
|
|
}
|
|
|
|
#expect(manager.activeDownloads.count == 3)
|
|
|
|
// Move last to first
|
|
let lastDownload = manager.activeDownloads.last!
|
|
await manager.moveInQueue(lastDownload, to: 0)
|
|
|
|
#expect(manager.activeDownloads.first?.videoID.videoID == "move3")
|
|
}
|
|
#endif
|
|
|
|
private func makeTestVideo(id: String) -> Video {
|
|
Video(
|
|
id: .global(id),
|
|
title: "Test Video \(id)",
|
|
description: nil,
|
|
author: Author(id: "ch1", name: "Channel"),
|
|
duration: 300,
|
|
publishedAt: nil,
|
|
publishedText: nil,
|
|
viewCount: nil,
|
|
likeCount: nil,
|
|
thumbnails: [],
|
|
isLive: false,
|
|
isUpcoming: false,
|
|
scheduledStartTime: nil
|
|
)
|
|
}
|
|
}
|