import Foundation import Testing import PDFKit @testable import GasTankAR @MainActor struct LeadDetailDownloadTests { private func fixture() async throws -> (DefaultLeadDetailRepository, StubLeadDetailRemoteService, LeadDetail) { let service = StubLeadDetailRemoteService(delay: .zero) let repository = DefaultLeadDetailRepository( remoteService: service, cacheDirectory: FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) ) let detail = try await repository.detail(id: "L-240318", salesCode: "MKR") return (repository, service, detail) } @Test func metadataDoesNotDownloadPDFsAndProgressMatchesDocuments() async throws { let (_, service, detail) = try await fixture() #expect(service.downloadCount == 0) #expect(detail.offers.count == 3) #expect(detail.offers[0].documents.count == 6) #expect(detail.offers[0].signedCount == 2) #expect(detail.offers[1].documents.count == 5) #expect(detail.offers[2].isComplete) #expect(detail.photos.count == 10) } @Test func downloadsActualPDFAndReusesCachedFile() async throws { let (repository, service, detail) = try await fixture() let document = try #require(detail.offers.first?.documents.first) let useCase = DownloadLeadDocumentUseCase(repository: repository) let first = try await useCase.execute(document, leadID: detail.lead.id, salesCode: "MKR") let pdf = try #require(PDFDocument(url: first)) #expect(pdf.pageCount == document.pageCount) #expect(pdf.string?.contains("Beispieldokument") == true) let second = try await useCase.execute(document, leadID: detail.lead.id, salesCode: "MKR") #expect(first == second) #expect(service.downloadCount == 1) try FileManager.default.removeItem(at: first) let third = try await useCase.execute(document, leadID: detail.lead.id, salesCode: "MKR") #expect(FileManager.default.fileExists(atPath: third.path)) #expect(service.downloadCount == 2) } @Test func concurrentDownloadsShareOneRequest() async throws { let (repository, service, detail) = try await fixture() service.delay = .milliseconds(50) let file = try #require(detail.offers.first?.documents.first?.file) let first = Task { try await repository.file(file, leadID: detail.lead.id, salesCode: "MKR") } let second = Task { try await repository.file(file, leadID: detail.lead.id, salesCode: "MKR") } let firstURL = try await first.value let secondURL = try await second.value #expect(firstURL == secondURL) #expect(service.downloadCount == 1) } @Test func cacheIsScopedToUserAndDocumentVersion() async throws { let (repository, service, detail) = try await fixture() let file = try #require(detail.offers.first?.documents.first?.file) let first = try await repository.file(file, leadID: detail.lead.id, salesCode: "MKR") let otherUser = try await repository.file(file, leadID: detail.lead.id, salesCode: "ABC") #expect(first != otherUser) let nextVersion = LeadFileReference(id: file.id, version: "2", kind: .pdf) await #expect(throws: LeadFileError.notFound) { try await repository.file(nextVersion, leadID: detail.lead.id, salesCode: "MKR") } #expect(service.downloadCount == 3) } @Test func invalidPDFIsNotCachedAndRetrySucceeds() async throws { let (repository, service, detail) = try await fixture() let file = try #require(detail.offers.first?.documents.first?.file) service.fileScenario = .invalidFile await #expect(throws: LeadFileError.invalidFile) { try await repository.file(file, leadID: detail.lead.id, salesCode: "MKR") } service.fileScenario = .success let url = try await repository.file(file, leadID: detail.lead.id, salesCode: "MKR") #expect(PDFDocument(url: url) != nil) #expect(service.downloadCount == 2) } @Test func fileErrorsPreserveLoadedDetailAndMapCorrectly() async throws { let (repository, service, detail) = try await fixture() let file = try #require(detail.offers.first?.documents.first?.file) for (scenario, expected): (StubLeadDetailRemoteService.Scenario, LeadError) in [ (.offline, .offline), (.timeout, .timeout), (.http(500), .serviceUnavailable), (.http(401), .unauthorized), (.http(403), .forbidden) ] { service.fileScenario = scenario await #expect(throws: expected) { try await repository.file(file, leadID: detail.lead.id, salesCode: "MKR") } } service.fileScenario = .http(404) await #expect(throws: LeadFileError.notFound) { try await repository.file(file, leadID: detail.lead.id, salesCode: "MKR") } #expect(detail.lead.id == "L-240318") } @Test func treeExpandsCollapsesAndKeepsStateWhenSelectingPDF() async throws { let (repository, _, _) = try await fixture() let model = LeadDetailViewModel(leadID: "L-240318", salesCode: "MKR", repository: repository) await model.load() let detail = try #require(model.detail) #expect(model.expandedOffers == [detail.offers[0].id]) model.toggleAll() #expect(model.isAllExpanded) model.selectedDocument = detail.offers[0].documents[0] model.selectedDocument = nil #expect(model.isAllExpanded) model.toggleAll() #expect(model.expandedOffers.isEmpty) #expect(model.expandedGroups.isEmpty) model.toggleOffer(detail.offers[1].id) #expect(model.expandedOffers == [detail.offers[1].id]) } @Test func anotherLeadKeepsItsOwnIdentityAndEmptyOffers() async throws { let (repository, _, _) = try await fixture() let detail = try await repository.detail(id: "L-240326", salesCode: "MKR") #expect(detail.lead.companyName == "Schreinerei Vogt") #expect(detail.offers.isEmpty) #expect(detail.email == nil) } @Test func photoDownloadProducesAnImage() async throws { let (repository, service, detail) = try await fixture() let photo = try #require(detail.photos.first) let url = try await LoadLeadPhotoUseCase(repository: repository) .execute(photo, leadID: detail.lead.id, salesCode: "MKR") #expect(FileManager.default.fileExists(atPath: url.path)) #expect(service.downloadCount == 1) } }