Initial commit for first version of project
This commit is contained in:
218
GasTankARTests/DocuframeTests.swift
Normal file
218
GasTankARTests/DocuframeTests.swift
Normal file
@ -0,0 +1,218 @@
|
||||
//
|
||||
// DocuframeTests.swift
|
||||
// GasTankARTests
|
||||
//
|
||||
// Created by Dennis Nemec on 28.08.26.
|
||||
//
|
||||
|
||||
import Testing
|
||||
import Foundation
|
||||
import SwiftData
|
||||
@testable import GasTankAR
|
||||
|
||||
// MARK: - Test-Doubles
|
||||
|
||||
/// Konfigurierbarer Mock des ``DocuframeService`` (Actor → threadsicher & Sendable),
|
||||
/// der Aufrufe zählt und wahlweise einen Fehler wirft.
|
||||
private actor MockDocuframeService: DocuframeService {
|
||||
enum Behavior: Sendable {
|
||||
case success
|
||||
case failure(DocuframeError)
|
||||
}
|
||||
|
||||
let behavior: Behavior
|
||||
private(set) var uploadCallCount = 0
|
||||
private(set) var sendEmailCallCount = 0
|
||||
private(set) var lastPhotoCount = 0
|
||||
private(set) var lastLeadID: String?
|
||||
|
||||
init(behavior: Behavior = .success) {
|
||||
self.behavior = behavior
|
||||
}
|
||||
|
||||
func uploadPhotos(_ photos: [DocuframePhoto], projectLeadID: String) async throws {
|
||||
uploadCallCount += 1
|
||||
record(photos: photos, leadID: projectLeadID)
|
||||
try applyBehavior()
|
||||
}
|
||||
|
||||
func sendEmail(_ photos: [DocuframePhoto], projectLeadID: String) async throws {
|
||||
sendEmailCallCount += 1
|
||||
record(photos: photos, leadID: projectLeadID)
|
||||
try applyBehavior()
|
||||
}
|
||||
|
||||
private func record(photos: [DocuframePhoto], leadID: String) {
|
||||
lastPhotoCount = photos.count
|
||||
lastLeadID = leadID
|
||||
}
|
||||
|
||||
private func applyBehavior() throws {
|
||||
if case .failure(let error) = behavior { throw error }
|
||||
}
|
||||
}
|
||||
|
||||
/// Hält Container und Fotos für die Dauer eines Tests am Leben.
|
||||
@MainActor
|
||||
private struct TestEnvironment {
|
||||
let container: ModelContainer
|
||||
let photos: [ProjectPhoto]
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func makeEnvironment(photoCount: Int) throws -> TestEnvironment {
|
||||
let container = try ModelContainer(
|
||||
for: Project.self,
|
||||
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
|
||||
)
|
||||
let context = container.mainContext
|
||||
let project = Project(leadID: "LEAD-1", customerName: "Test", creationDate: Date(timeIntervalSince1970: 0))
|
||||
context.insert(project)
|
||||
|
||||
var photos: [ProjectPhoto] = []
|
||||
for index in 0..<photoCount {
|
||||
let photo = ProjectPhoto(
|
||||
creationDate: Date(timeIntervalSince1970: TimeInterval(index)),
|
||||
imageData: Data([UInt8(index)]),
|
||||
project: project
|
||||
)
|
||||
context.insert(photo)
|
||||
photos.append(photo)
|
||||
}
|
||||
return TestEnvironment(container: container, photos: photos)
|
||||
}
|
||||
|
||||
// MARK: - Fehler-Mapping
|
||||
|
||||
struct DocuframeErrorTests {
|
||||
@Test func mapsHTTPStatusCodesToErrors() {
|
||||
#expect(DocuframeError.from(statusCode: 200) == nil)
|
||||
#expect(DocuframeError.from(statusCode: 302) == nil)
|
||||
#expect(DocuframeError.from(statusCode: 400) == .clientError(statusCode: 400))
|
||||
#expect(DocuframeError.from(statusCode: 404) == .clientError(statusCode: 404))
|
||||
#expect(DocuframeError.from(statusCode: 500) == .serverError(statusCode: 500))
|
||||
#expect(DocuframeError.from(statusCode: 503) == .serverError(statusCode: 503))
|
||||
}
|
||||
|
||||
@Test func httpResponseValidationThrowsForClientAndServerErrors() throws {
|
||||
let url = URL(string: "https://docuframe.example.com")!
|
||||
|
||||
let success = try #require(HTTPURLResponse(url: url, statusCode: 204, httpVersion: nil, headerFields: nil))
|
||||
#expect(throws: Never.self) { try success.validateDocuframeStatus() }
|
||||
|
||||
let clientError = try #require(HTTPURLResponse(url: url, statusCode: 400, httpVersion: nil, headerFields: nil))
|
||||
#expect(throws: DocuframeError.clientError(statusCode: 400)) { try clientError.validateDocuframeStatus() }
|
||||
|
||||
let serverError = try #require(HTTPURLResponse(url: url, statusCode: 500, httpVersion: nil, headerFields: nil))
|
||||
#expect(throws: DocuframeError.serverError(statusCode: 500)) { try serverError.validateDocuframeStatus() }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ViewModel
|
||||
|
||||
@MainActor
|
||||
struct DocuframeViewModelTests {
|
||||
|
||||
// MARK: Auswahl
|
||||
|
||||
@Test func togglingSelectionTracksPhotos() throws {
|
||||
let env = try makeEnvironment(photoCount: 2)
|
||||
let viewModel = DocuframeViewModel(service: MockDocuframeService())
|
||||
|
||||
#expect(!viewModel.hasSelection)
|
||||
|
||||
viewModel.toggleSelection(env.photos[0])
|
||||
#expect(viewModel.isSelected(env.photos[0]))
|
||||
#expect(viewModel.selectionCount == 1)
|
||||
|
||||
viewModel.toggleSelection(env.photos[0])
|
||||
#expect(!viewModel.isSelected(env.photos[0]))
|
||||
#expect(!viewModel.hasSelection)
|
||||
}
|
||||
|
||||
// MARK: Erfolg
|
||||
|
||||
@Test func uploadSuccessSetsSuccessResult() async throws {
|
||||
let env = try makeEnvironment(photoCount: 2)
|
||||
let mock = MockDocuframeService(behavior: .success)
|
||||
let viewModel = DocuframeViewModel(service: mock)
|
||||
env.photos.forEach(viewModel.toggleSelection)
|
||||
|
||||
await viewModel.upload(from: env.photos, leadID: "LEAD-1")
|
||||
|
||||
#expect(viewModel.isProcessing == false)
|
||||
let result = try #require(viewModel.result)
|
||||
#expect(!result.isError)
|
||||
#expect(result.title == "Erledigt")
|
||||
#expect(result.message == "2 Foto(s) in DOCUframe hochgeladen.")
|
||||
|
||||
let uploadCount = await mock.uploadCallCount
|
||||
let photoCount = await mock.lastPhotoCount
|
||||
let leadID = await mock.lastLeadID
|
||||
#expect(uploadCount == 1)
|
||||
#expect(photoCount == 2)
|
||||
#expect(leadID == "LEAD-1")
|
||||
}
|
||||
|
||||
@Test func sendEmailSuccessSetsSuccessResult() async throws {
|
||||
let env = try makeEnvironment(photoCount: 1)
|
||||
let mock = MockDocuframeService(behavior: .success)
|
||||
let viewModel = DocuframeViewModel(service: mock)
|
||||
viewModel.toggleSelection(env.photos[0])
|
||||
|
||||
await viewModel.sendEmail(from: env.photos, leadID: "LEAD-1")
|
||||
|
||||
let result = try #require(viewModel.result)
|
||||
#expect(!result.isError)
|
||||
#expect(result.message == "1 Foto(s) per E-Mail versendet.")
|
||||
let sendCount = await mock.sendEmailCallCount
|
||||
#expect(sendCount == 1)
|
||||
}
|
||||
|
||||
// MARK: HTTP-Fehler
|
||||
|
||||
@Test func uploadServerErrorSetsFailureResult() async throws {
|
||||
let env = try makeEnvironment(photoCount: 1)
|
||||
let mock = MockDocuframeService(behavior: .failure(.serverError(statusCode: 500)))
|
||||
let viewModel = DocuframeViewModel(service: mock)
|
||||
viewModel.toggleSelection(env.photos[0])
|
||||
|
||||
await viewModel.upload(from: env.photos, leadID: "LEAD-1")
|
||||
|
||||
#expect(viewModel.isProcessing == false)
|
||||
let result = try #require(viewModel.result)
|
||||
#expect(result.isError)
|
||||
#expect(result.title == "Fehler")
|
||||
#expect(result.message == DocuframeError.serverError(statusCode: 500).localizedDescription)
|
||||
}
|
||||
|
||||
@Test func sendEmailClientErrorSetsFailureResult() async throws {
|
||||
let env = try makeEnvironment(photoCount: 1)
|
||||
let mock = MockDocuframeService(behavior: .failure(.clientError(statusCode: 400)))
|
||||
let viewModel = DocuframeViewModel(service: mock)
|
||||
viewModel.toggleSelection(env.photos[0])
|
||||
|
||||
await viewModel.sendEmail(from: env.photos, leadID: "LEAD-1")
|
||||
|
||||
let result = try #require(viewModel.result)
|
||||
#expect(result.isError)
|
||||
#expect(result.message == DocuframeError.clientError(statusCode: 400).localizedDescription)
|
||||
}
|
||||
|
||||
// MARK: Keine Auswahl
|
||||
|
||||
@Test func withoutSelectionReportsErrorAndSkipsService() async throws {
|
||||
let env = try makeEnvironment(photoCount: 2)
|
||||
let mock = MockDocuframeService()
|
||||
let viewModel = DocuframeViewModel(service: mock)
|
||||
|
||||
await viewModel.upload(from: env.photos, leadID: "LEAD-1")
|
||||
|
||||
let result = try #require(viewModel.result)
|
||||
#expect(result.isError)
|
||||
#expect(result.message == DocuframeError.noPhotosSelected.localizedDescription)
|
||||
|
||||
let uploadCount = await mock.uploadCallCount
|
||||
#expect(uploadCount == 0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user