From 16b48b1dfd4bd1f479a005b0566d205f640111ad Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Fri, 1 May 2026 17:03:09 +0200 Subject: [PATCH 01/12] Separate in sub targets --- Package.resolved | 11 +-- Sources/Coffee-Kit/Coffee_Kit.swift | 2 - .../Coffee-Kit/Enumerations/CoffeeColor.swift | 29 ------ .../Extensions/AsyncSequenceExtension.swift | 24 ----- .../Extensions/LoggerExtension.swift | 44 --------- .../Webservice/Services/CakeService.swift | 94 ------------------- .../Services/Login/AuthViewModel.swift | 88 ----------------- .../Services/Login/LoginError.swift | 53 ----------- .../Services/Login/LoginStatus.swift | 81 ---------------- .../Cache/Cache.swift | 0 .../DatabaseAPI.swift | 0 .../Extensions/URLExtension.swift | 0 .../Errors => FoundationKit}/FetchError.swift | 0 .../WebserviceProvider.swift | 0 .../WebsocketConnection.swift | 0 .../Manager => ImageKit}/ImageManager.swift | 0 .../Services => ImageKit}/ImageService.swift | 0 .../Order/Errors/OrderBuilderError.swift | 0 .../DAO => OrderKit}/Order/Order.swift | 0 .../DAO => OrderKit}/Order/OrderBuilder.swift | 0 .../DAO => OrderKit}/Order/OrderItem.swift | 0 .../DAO => OrderKit}/Order/OrderProduct.swift | 0 .../DAO => OrderKit}/Order/OrderStatus.swift | 0 .../Order/PaymentStatus.swift | 0 .../Manager => OrderKit}/OrderManager.swift | 0 .../Services => OrderKit}/OrderService.swift | 0 .../PaymentOption.swift | 0 .../MenuCategory.swift | 0 .../Manager => ProductKit}/MenuManager.swift | 0 .../Products}/Metadata.swift | 0 .../DAO => ProductKit}/Products/Product.swift | 0 .../Products}/ProductService.swift | 0 32 files changed, 1 insertion(+), 425 deletions(-) delete mode 100644 Sources/Coffee-Kit/Coffee_Kit.swift delete mode 100644 Sources/Coffee-Kit/Enumerations/CoffeeColor.swift delete mode 100644 Sources/Coffee-Kit/Extensions/AsyncSequenceExtension.swift delete mode 100644 Sources/Coffee-Kit/Extensions/LoggerExtension.swift delete mode 100644 Sources/Coffee-Kit/Webservice/Services/CakeService.swift delete mode 100644 Sources/Coffee-Kit/Webservice/Services/Login/AuthViewModel.swift delete mode 100644 Sources/Coffee-Kit/Webservice/Services/Login/LoginError.swift delete mode 100644 Sources/Coffee-Kit/Webservice/Services/Login/LoginStatus.swift rename Sources/{Coffee-Kit/Webservice => FoundationKit}/Cache/Cache.swift (100%) rename Sources/{Coffee-Kit/Webservice => FoundationKit}/DatabaseAPI.swift (100%) rename Sources/{Coffee-Kit => FoundationKit}/Extensions/URLExtension.swift (100%) rename Sources/{Coffee-Kit/Errors => FoundationKit}/FetchError.swift (100%) rename Sources/{Coffee-Kit/Webservice => FoundationKit}/WebserviceProvider.swift (100%) rename Sources/{Coffee-Kit/Webservice/Services => FoundationKit}/WebsocketConnection.swift (100%) rename Sources/{Coffee-Kit/Webservice/Manager => ImageKit}/ImageManager.swift (100%) rename Sources/{Coffee-Kit/Webservice/Services => ImageKit}/ImageService.swift (100%) rename Sources/{Coffee-Kit/DAO => OrderKit}/Order/Errors/OrderBuilderError.swift (100%) rename Sources/{Coffee-Kit/DAO => OrderKit}/Order/Order.swift (100%) rename Sources/{Coffee-Kit/DAO => OrderKit}/Order/OrderBuilder.swift (100%) rename Sources/{Coffee-Kit/DAO => OrderKit}/Order/OrderItem.swift (100%) rename Sources/{Coffee-Kit/DAO => OrderKit}/Order/OrderProduct.swift (100%) rename Sources/{Coffee-Kit/DAO => OrderKit}/Order/OrderStatus.swift (100%) rename Sources/{Coffee-Kit/DAO => OrderKit}/Order/PaymentStatus.swift (100%) rename Sources/{Coffee-Kit/Webservice/Manager => OrderKit}/OrderManager.swift (100%) rename Sources/{Coffee-Kit/Webservice/Services => OrderKit}/OrderService.swift (100%) rename Sources/{Coffee-Kit/Enumerations => OrderKit}/PaymentOption.swift (100%) rename Sources/{Coffee-Kit/Enumerations => ProductKit}/MenuCategory.swift (100%) rename Sources/{Coffee-Kit/Webservice/Manager => ProductKit}/MenuManager.swift (100%) rename Sources/{Coffee-Kit/DAO => ProductKit/Products}/Metadata.swift (100%) rename Sources/{Coffee-Kit/DAO => ProductKit}/Products/Product.swift (100%) rename Sources/{Coffee-Kit/Webservice/Services => ProductKit/Products}/ProductService.swift (100%) diff --git a/Package.resolved b/Package.resolved index ce0c505..53ff66f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "1c76daf8512f795aeae2b70e86a3eb59775052746b038b6f4ae27b0a910cdbc9", + "originHash" : "cc1dbce14c240c4bdc95b54f00155cee6052a519ff4b4d5dd91105804a9ef7f1", "pins" : [ { "identity" : "harmonize", @@ -10,15 +10,6 @@ "version" : "0.1.0" } }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections.git", - "state" : { - "revision" : "6675bc0ff86e61436e615df6fc5174e043e57924", - "version" : "1.4.1" - } - }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", diff --git a/Sources/Coffee-Kit/Coffee_Kit.swift b/Sources/Coffee-Kit/Coffee_Kit.swift deleted file mode 100644 index 08b22b8..0000000 --- a/Sources/Coffee-Kit/Coffee_Kit.swift +++ /dev/null @@ -1,2 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book diff --git a/Sources/Coffee-Kit/Enumerations/CoffeeColor.swift b/Sources/Coffee-Kit/Enumerations/CoffeeColor.swift deleted file mode 100644 index e5d842d..0000000 --- a/Sources/Coffee-Kit/Enumerations/CoffeeColor.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// CoffeeColor.swift -// Coffee-Kit -// -// Created by Christoph Rohde on 05.07.25. -// - -public enum CoffeeColor: String, CaseIterable, Sendable { - case coffeeBrownLight - case coffeeBrownDark - case coffeeAccent - - public func getRGB() -> (red: Double, green: Double, blue: Double) { - switch self { - case .coffeeBrownLight: - // #8B4513 - return (0.545, 0.271, 0.075) // Light coffee brown - // return (0.8, 0.52, 0.36) // Light coffee brown - case .coffeeBrownDark: - // #5A3319 - return (0.353, 0.2, 0.098) // Dark coffee brown -// return (0.4, 0.26, 0.18) // Dark coffee brown - case .coffeeAccent: - // #F5F5DC - return (0.961, 0.961, 0.863) // Accent color for coffee -// return (0.9, 0.75, 0.5) // Accent color for coffee - } - } -} diff --git a/Sources/Coffee-Kit/Extensions/AsyncSequenceExtension.swift b/Sources/Coffee-Kit/Extensions/AsyncSequenceExtension.swift deleted file mode 100644 index f8f5b16..0000000 --- a/Sources/Coffee-Kit/Extensions/AsyncSequenceExtension.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// AsyncSequence.swift -// Coffee-Kit -// -// Created by Christoph Rohde on 15.05.25. -// - -import Foundation - -// MARK: - AsyncSequence - - - -extension AsyncSequence { - func collect( - into initialValue: C = C() - ) async throws -> C where C.Element == Element { - var result = initialValue - for try await element in self { - result.append(element) - } - return result - } -} diff --git a/Sources/Coffee-Kit/Extensions/LoggerExtension.swift b/Sources/Coffee-Kit/Extensions/LoggerExtension.swift deleted file mode 100644 index 0251feb..0000000 --- a/Sources/Coffee-Kit/Extensions/LoggerExtension.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// OSLogExtension.swift -// Coffee-Kit -// -// Created by Christoph Rohde on 18.04.25. -// -import Foundation -import OSLog - -public extension Logger { - func trackTask(called name: String, task: () async throws -> Void) async { - let startTime = Date() - let duration: TimeInterval - - do { - try await task() - duration = Date().timeIntervalSince(startTime) - - // split duration into minutes, seconds and milliseconds - let minutes = Int(duration) / 60 - let seconds = Int(duration) % 60 - let milliseconds = Int((duration - Double(minutes * 60 + seconds)) * 1000) - - self.info("Task '\(name)' successful completed in \(minutes) minutes, \(seconds) seconds and \(milliseconds) milliseconds") - } catch { - duration = Date().timeIntervalSince(startTime) - - // split duration into minutes, seconds and milliseconds - let minutes = Int(duration) / 60 - let seconds = Int(duration) % 60 - let milliseconds = Int((duration - Double(minutes * 60 + seconds)) * 1000) - self.error("Task '\(name)' failed in \(minutes) minutes, \(seconds) seconds and \(milliseconds) milliseconds") - self.error("Error: \(error.localizedDescription)") - } - } - - func logExecutionTime(_ log: String, block: () async -> Void) async { - let start = DispatchTime.now() - await block() - let end = DispatchTime.now() - let diff = Double(end.uptimeNanoseconds - start.uptimeNanoseconds) / 1000000000 - print(log, String(format: "%.3f", diff)) - } -} diff --git a/Sources/Coffee-Kit/Webservice/Services/CakeService.swift b/Sources/Coffee-Kit/Webservice/Services/CakeService.swift deleted file mode 100644 index 3d1d435..0000000 --- a/Sources/Coffee-Kit/Webservice/Services/CakeService.swift +++ /dev/null @@ -1,94 +0,0 @@ -// -// CakeService.swift -// Coffee-Kit -// -// Created by Christoph Rohde on 22.01.25. -// - -import Foundation - - -public struct CakeService { - // MARK: Properties - - let cakeURL: URL - - // MARK: Initializer - - public init(databaseAPI: borrowing DatabaseAPI) { - self.cakeURL = databaseAPI.baseURL / "cake" - } - - // MARK: Methods - - public func getIds() async throws -> [String] { - let cakeIdsUrls = cakeURL / "ids" - let (data, response) = try await URLSession.shared.data(from: cakeIdsUrls) - - guard let drinkIds = try? JSONDecoder().decode([String].self, from: data) else { - print(response) - print(""" - Error in \(#file) - \t\(#function) \(#line):\(#column) - \tStatus code: \((response as? HTTPURLResponse)?.statusCode ?? 0) - """) - throw FetchError.decodingError - } - - return drinkIds - } - - public func load(by id: consuming String) async throws -> Product { - let cakeByIdUrl = cakeURL / "id" / id - - let (data, response) = try await URLSession.shared.data(from: cakeByIdUrl) - - guard let coffee = try? JSONDecoder().decode(Product.self, from: data) else { - print(response) - - let stacktrace = Thread.callStackSymbols.joined(separator: "\n") - print(stacktrace) - print(""" - Error in \(#file) - \t\(#function) \(#line):\(#column) - \tStatus code: \((response as? HTTPURLResponse)?.statusCode ?? 0) - """) - throw FetchError.decodingError - } - - return coffee - } - - public func load(by ids: [String]) async -> AsyncThrowingStream { - return AsyncThrowingStream { continuation in - Task { - do { - for id in ids { - let cakeModel = try await load(by: id) - continuation.yield(cakeModel) - } - continuation.finish() - } catch { - continuation.finish(throwing: error) - } - } - } - } - - public func loadAll() async -> AsyncStream> { - return AsyncStream> { continuation in - Task { - do { - let ids = try await getIds() - for id in ids { - let cakeModel = try await load(by: id) - continuation.yield(.success(cakeModel)) - } - } catch { - continuation.yield(.failure(error)) - } - continuation.finish() - } - } - } -} diff --git a/Sources/Coffee-Kit/Webservice/Services/Login/AuthViewModel.swift b/Sources/Coffee-Kit/Webservice/Services/Login/AuthViewModel.swift deleted file mode 100644 index ec2a27d..0000000 --- a/Sources/Coffee-Kit/Webservice/Services/Login/AuthViewModel.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// AuthModel.swift -// Coffee Lover -// -// Created by Christoph Rohde on 05.07.25. -// - -import Foundation -import Combine - -@MainActor -public final class AuthViewModel: ObservableObject { - @Published var email = "" - @Published var password = "" - @Published var confirmPassword = "" - @Published var errorMessage: String? - @Published var registrationSuccessful = false // Für die Registrierung - - // MARK: - Login-Logik - - func login() async { - errorMessage = nil // Vorherige Fehlermeldungen löschen - - guard !email.isEmpty && !password.isEmpty else { - errorMessage = "Bitte E-Mail und Passwort eingeben." - return - } - - // Simuliere einen Netzwerkaufruf zum Backend - try? await Task.sleep(nanoseconds: 1 * 1_000_000_000) // Simuliere 1 Sekunde Verzögerung - - // Hier würde der tatsächliche API-Aufruf zu deinem Backend erfolgen. - // Das Backend würde die Authentifizierung überprüfen (Passwort salzen, pfeffern, hashen und vergleichen). - // Wenn erfolgreich, würde es ein Authentifizierungstoken zurückgeben. - // Andernfalls eine Fehlermeldung. - - // Simuliere Erfolg oder Misserfolg - if email == "test@example.com" && password == "password123" { - print("Login erfolgreich!") - // Hier würdest du den Benutzerstatus in deiner App aktualisieren (z.B. User Defaults, KeyChain, etc.) - // Und zur Hauptansicht navigieren. - } else { - DispatchQueue.main.async { - self.errorMessage = "Ungültige E-Mail oder Passwort." - } - } - } - - // MARK: - Registrierungs-Logik - - func register() async { - errorMessage = nil // Vorherige Fehlermeldungen löschen - registrationSuccessful = false - - guard !email.isEmpty && !password.isEmpty && !confirmPassword.isEmpty else { - errorMessage = "Bitte alle Felder ausfüllen." - return - } - - guard password == confirmPassword else { - errorMessage = "Passwörter stimmen nicht überein." - return - } - - // Simuliere einen Netzwerkaufruf zum Backend - try? await Task.sleep(nanoseconds: 1 * 1_000_000_000) // Simuliere 1 Sekunde Verzögerung - - // Hier würde der tatsächliche API-Aufruf zu deinem Backend erfolgen. - // Das Backend würde: - // 1. Das Passwort sicher verarbeiten (salzen, pfeffern, hashen). - // 2. Den neuen Benutzer in der Datenbank speichern. - // 3. Eine Bestätigungs-E-Mail an die angegebene E-Mail-Adresse senden. - // 4. Bei Erfolg einen Erfolgsstatus zurückgeben oder bei Misserfolg eine Fehlermeldung. - - // Simuliere Erfolg oder Misserfolg - if email.contains("@") && password.count >= 6 { // Einfache Validierung - DispatchQueue.main.async { - self.registrationSuccessful = true - self.errorMessage = "Registrierung erfolgreich! Bitte überprüfe deine E-Mial für die Bestätigung." - // Hier könntest du auch zur LoginView zurückkehren. - } - } else { - DispatchQueue.main.async { - self.errorMessage = "Registrierung fehlgeschlagen. Bitte überprüfe deine Eingaben." - } - } - } -} diff --git a/Sources/Coffee-Kit/Webservice/Services/Login/LoginError.swift b/Sources/Coffee-Kit/Webservice/Services/Login/LoginError.swift deleted file mode 100644 index cf830fb..0000000 --- a/Sources/Coffee-Kit/Webservice/Services/Login/LoginError.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// LoginError.swift -// Coffee-Kit -// -// Created by Christoph Rohde on 13.07.25. -// - -import Foundation - -public enum LoginError: LocalizedError { - case weakPassword - case invalidCredentials - case serverError(String) - case noInternetConnection - case invalidSession -} - -extension LoginError: CustomStringConvertible { - public var description: String { - switch self { - case .weakPassword: - return "The password provided is too weak." - case .invalidCredentials: - return "Invalid credentials provided." - case .serverError(let message): - return "Server error: \(message)" - case .noInternetConnection: - return "No internet connection available." - case .invalidSession: - return "The session is invalid or has expired." - - } - } -} - -extension LoginError: CustomDebugStringConvertible { - public var debugDescription: String { - switch self { - case .weakPassword: - return "LoginError.weakPassword" - case .invalidCredentials: - return "LoginError.invalidCredentials" - case .serverError(let message): - return "LoginError.serverError(\(message))" - case .noInternetConnection: - return "LoginError.noInternetConnection" - case .invalidSession: - return "LoginError.invalidSession" - } - } -} - -extension LoginError: Sendable {} diff --git a/Sources/Coffee-Kit/Webservice/Services/Login/LoginStatus.swift b/Sources/Coffee-Kit/Webservice/Services/Login/LoginStatus.swift deleted file mode 100644 index c179b08..0000000 --- a/Sources/Coffee-Kit/Webservice/Services/Login/LoginStatus.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// LoginStatus.swift -// Coffee-Kit -// -// Created by Christoph Rohde on 13.07.25. -// - -import Foundation -import Authentication_Kit - -@frozen -public enum LoginStatus { - case idle - case loading - case loggedIn(User) - case error(LoginError) - case loggedOut -} - -// MARK: - Sendable -extension LoginStatus: Sendable {} - -// MARK: - CustomStringConvertible -extension LoginStatus: CustomStringConvertible { - public var description: String { - switch self { - case .idle: - return "Idle" - case .loading: - return "Loading" - case .loggedIn(let user): - return "Logged in as \(user.name)" - case .error(let error): - return "Error: \(error.localizedDescription)" - case .loggedOut: - return "Logged out" - } - } -} - -// MARK: - Equatable -extension LoginStatus: Equatable { - public static func == (lhs: LoginStatus, rhs: LoginStatus) -> Bool { - switch (lhs, rhs) { - case (.idle, .idle), (.loading, .loading), (.loggedOut, .loggedOut): - return true - case (.loggedIn(let user1), .loggedIn(let user2)): - return user1.id == user2.id - case (.error(let error1), .error(let error2)): - return error1.localizedDescription == error2.localizedDescription - default: - return false - } - } -} - -// MARK: - Hashable -extension LoginStatus: Hashable { - public func hash(into hasher: inout Hasher) { - switch self { - case .idle: - hasher.combine(0) - case .loading: - hasher.combine(1) - case .loggedIn(let user): - hasher.combine(2) - hasher.combine(user.id) - case .error(let error): - hasher.combine(3) - hasher.combine(error.localizedDescription) - case .loggedOut: - hasher.combine(4) - } - } -} - - - - - - diff --git a/Sources/Coffee-Kit/Webservice/Cache/Cache.swift b/Sources/FoundationKit/Cache/Cache.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/Cache/Cache.swift rename to Sources/FoundationKit/Cache/Cache.swift diff --git a/Sources/Coffee-Kit/Webservice/DatabaseAPI.swift b/Sources/FoundationKit/DatabaseAPI.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/DatabaseAPI.swift rename to Sources/FoundationKit/DatabaseAPI.swift diff --git a/Sources/Coffee-Kit/Extensions/URLExtension.swift b/Sources/FoundationKit/Extensions/URLExtension.swift similarity index 100% rename from Sources/Coffee-Kit/Extensions/URLExtension.swift rename to Sources/FoundationKit/Extensions/URLExtension.swift diff --git a/Sources/Coffee-Kit/Errors/FetchError.swift b/Sources/FoundationKit/FetchError.swift similarity index 100% rename from Sources/Coffee-Kit/Errors/FetchError.swift rename to Sources/FoundationKit/FetchError.swift diff --git a/Sources/Coffee-Kit/Webservice/WebserviceProvider.swift b/Sources/FoundationKit/WebserviceProvider.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/WebserviceProvider.swift rename to Sources/FoundationKit/WebserviceProvider.swift diff --git a/Sources/Coffee-Kit/Webservice/Services/WebsocketConnection.swift b/Sources/FoundationKit/WebsocketConnection.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/Services/WebsocketConnection.swift rename to Sources/FoundationKit/WebsocketConnection.swift diff --git a/Sources/Coffee-Kit/Webservice/Manager/ImageManager.swift b/Sources/ImageKit/ImageManager.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/Manager/ImageManager.swift rename to Sources/ImageKit/ImageManager.swift diff --git a/Sources/Coffee-Kit/Webservice/Services/ImageService.swift b/Sources/ImageKit/ImageService.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/Services/ImageService.swift rename to Sources/ImageKit/ImageService.swift diff --git a/Sources/Coffee-Kit/DAO/Order/Errors/OrderBuilderError.swift b/Sources/OrderKit/Order/Errors/OrderBuilderError.swift similarity index 100% rename from Sources/Coffee-Kit/DAO/Order/Errors/OrderBuilderError.swift rename to Sources/OrderKit/Order/Errors/OrderBuilderError.swift diff --git a/Sources/Coffee-Kit/DAO/Order/Order.swift b/Sources/OrderKit/Order/Order.swift similarity index 100% rename from Sources/Coffee-Kit/DAO/Order/Order.swift rename to Sources/OrderKit/Order/Order.swift diff --git a/Sources/Coffee-Kit/DAO/Order/OrderBuilder.swift b/Sources/OrderKit/Order/OrderBuilder.swift similarity index 100% rename from Sources/Coffee-Kit/DAO/Order/OrderBuilder.swift rename to Sources/OrderKit/Order/OrderBuilder.swift diff --git a/Sources/Coffee-Kit/DAO/Order/OrderItem.swift b/Sources/OrderKit/Order/OrderItem.swift similarity index 100% rename from Sources/Coffee-Kit/DAO/Order/OrderItem.swift rename to Sources/OrderKit/Order/OrderItem.swift diff --git a/Sources/Coffee-Kit/DAO/Order/OrderProduct.swift b/Sources/OrderKit/Order/OrderProduct.swift similarity index 100% rename from Sources/Coffee-Kit/DAO/Order/OrderProduct.swift rename to Sources/OrderKit/Order/OrderProduct.swift diff --git a/Sources/Coffee-Kit/DAO/Order/OrderStatus.swift b/Sources/OrderKit/Order/OrderStatus.swift similarity index 100% rename from Sources/Coffee-Kit/DAO/Order/OrderStatus.swift rename to Sources/OrderKit/Order/OrderStatus.swift diff --git a/Sources/Coffee-Kit/DAO/Order/PaymentStatus.swift b/Sources/OrderKit/Order/PaymentStatus.swift similarity index 100% rename from Sources/Coffee-Kit/DAO/Order/PaymentStatus.swift rename to Sources/OrderKit/Order/PaymentStatus.swift diff --git a/Sources/Coffee-Kit/Webservice/Manager/OrderManager.swift b/Sources/OrderKit/OrderManager.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/Manager/OrderManager.swift rename to Sources/OrderKit/OrderManager.swift diff --git a/Sources/Coffee-Kit/Webservice/Services/OrderService.swift b/Sources/OrderKit/OrderService.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/Services/OrderService.swift rename to Sources/OrderKit/OrderService.swift diff --git a/Sources/Coffee-Kit/Enumerations/PaymentOption.swift b/Sources/OrderKit/PaymentOption.swift similarity index 100% rename from Sources/Coffee-Kit/Enumerations/PaymentOption.swift rename to Sources/OrderKit/PaymentOption.swift diff --git a/Sources/Coffee-Kit/Enumerations/MenuCategory.swift b/Sources/ProductKit/MenuCategory.swift similarity index 100% rename from Sources/Coffee-Kit/Enumerations/MenuCategory.swift rename to Sources/ProductKit/MenuCategory.swift diff --git a/Sources/Coffee-Kit/Webservice/Manager/MenuManager.swift b/Sources/ProductKit/MenuManager.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/Manager/MenuManager.swift rename to Sources/ProductKit/MenuManager.swift diff --git a/Sources/Coffee-Kit/DAO/Metadata.swift b/Sources/ProductKit/Products/Metadata.swift similarity index 100% rename from Sources/Coffee-Kit/DAO/Metadata.swift rename to Sources/ProductKit/Products/Metadata.swift diff --git a/Sources/Coffee-Kit/DAO/Products/Product.swift b/Sources/ProductKit/Products/Product.swift similarity index 100% rename from Sources/Coffee-Kit/DAO/Products/Product.swift rename to Sources/ProductKit/Products/Product.swift diff --git a/Sources/Coffee-Kit/Webservice/Services/ProductService.swift b/Sources/ProductKit/Products/ProductService.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/Services/ProductService.swift rename to Sources/ProductKit/Products/ProductService.swift From a04dd31dcfca5b9ffef01824dee621e9c61f0611 Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Fri, 1 May 2026 17:12:29 +0200 Subject: [PATCH 02/12] Add sub targets --- Package.swift | 20 +++++++++++++++---- .../AutenticationManager.swift | 0 .../AuthenticationBuilder.swift | 0 .../Errors/AuthenticationError.swift | 0 .../Errors/LoginError.swift | 0 .../JWTValidator.swift | 0 .../KeychainService.swift | 0 .../LoginResponce.swift | 0 .../LoginStatus.swift | 0 .../User.swift | 0 10 files changed, 16 insertions(+), 4 deletions(-) rename Sources/{Authentication-Kit => AuthenticationKit}/AutenticationManager.swift (100%) rename Sources/{Authentication-Kit => AuthenticationKit}/AuthenticationBuilder.swift (100%) rename Sources/{Authentication-Kit => AuthenticationKit}/Errors/AuthenticationError.swift (100%) rename Sources/{Authentication-Kit => AuthenticationKit}/Errors/LoginError.swift (100%) rename Sources/{Authentication-Kit => AuthenticationKit}/JWTValidator.swift (100%) rename Sources/{Authentication-Kit => AuthenticationKit}/KeychainService.swift (100%) rename Sources/{Authentication-Kit => AuthenticationKit}/LoginResponce.swift (100%) rename Sources/{Authentication-Kit => AuthenticationKit}/LoginStatus.swift (100%) rename Sources/{Authentication-Kit => AuthenticationKit}/User.swift (100%) diff --git a/Package.swift b/Package.swift index cda276b..4b43243 100644 --- a/Package.swift +++ b/Package.swift @@ -12,13 +12,25 @@ let package = Package( products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( - name: "Coffee-Kit", - targets: ["Coffee-Kit"] + name: "FoundationKit", + targets: ["FoundationKit"] ), .library( - name: "Authentication-Kit", - targets: ["Authentication-Kit"] + name: "AuthenticationKit", + targets: ["AuthenticationKit"] + ), + .library( + name: "ImageKit", + targets: ["ImageKit"] ), + .library( + name: "OrderKit", + targets: ["OrderKit"] + ), + .library( + name: "ProductKit", + targets: ["ProductKit"] + ) ], dependencies: [ .package( diff --git a/Sources/Authentication-Kit/AutenticationManager.swift b/Sources/AuthenticationKit/AutenticationManager.swift similarity index 100% rename from Sources/Authentication-Kit/AutenticationManager.swift rename to Sources/AuthenticationKit/AutenticationManager.swift diff --git a/Sources/Authentication-Kit/AuthenticationBuilder.swift b/Sources/AuthenticationKit/AuthenticationBuilder.swift similarity index 100% rename from Sources/Authentication-Kit/AuthenticationBuilder.swift rename to Sources/AuthenticationKit/AuthenticationBuilder.swift diff --git a/Sources/Authentication-Kit/Errors/AuthenticationError.swift b/Sources/AuthenticationKit/Errors/AuthenticationError.swift similarity index 100% rename from Sources/Authentication-Kit/Errors/AuthenticationError.swift rename to Sources/AuthenticationKit/Errors/AuthenticationError.swift diff --git a/Sources/Authentication-Kit/Errors/LoginError.swift b/Sources/AuthenticationKit/Errors/LoginError.swift similarity index 100% rename from Sources/Authentication-Kit/Errors/LoginError.swift rename to Sources/AuthenticationKit/Errors/LoginError.swift diff --git a/Sources/Authentication-Kit/JWTValidator.swift b/Sources/AuthenticationKit/JWTValidator.swift similarity index 100% rename from Sources/Authentication-Kit/JWTValidator.swift rename to Sources/AuthenticationKit/JWTValidator.swift diff --git a/Sources/Authentication-Kit/KeychainService.swift b/Sources/AuthenticationKit/KeychainService.swift similarity index 100% rename from Sources/Authentication-Kit/KeychainService.swift rename to Sources/AuthenticationKit/KeychainService.swift diff --git a/Sources/Authentication-Kit/LoginResponce.swift b/Sources/AuthenticationKit/LoginResponce.swift similarity index 100% rename from Sources/Authentication-Kit/LoginResponce.swift rename to Sources/AuthenticationKit/LoginResponce.swift diff --git a/Sources/Authentication-Kit/LoginStatus.swift b/Sources/AuthenticationKit/LoginStatus.swift similarity index 100% rename from Sources/Authentication-Kit/LoginStatus.swift rename to Sources/AuthenticationKit/LoginStatus.swift diff --git a/Sources/Authentication-Kit/User.swift b/Sources/AuthenticationKit/User.swift similarity index 100% rename from Sources/Authentication-Kit/User.swift rename to Sources/AuthenticationKit/User.swift From a0183cb10d848958c6d042d4c9a34e120f9d8235 Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Sat, 2 May 2026 12:53:58 +0200 Subject: [PATCH 03/12] Make sub targets compilable --- Package.resolved | 10 +- Package.swift | 66 ++++++++++--- .../AutenticationManager.swift | 4 +- Sources/FoundationKit/Cache/Cache.swift | 2 +- .../Extensions/AsyncSequenceExtension.swift | 24 +++++ .../Extensions/LoggerExtension.swift | 44 +++++++++ .../FoundationKit/WebserviceProvider.swift | 20 ++-- .../FoundationKit/WebsocketConnection.swift | 2 +- Sources/ImageKit/ImageManager.swift | 2 + Sources/ImageKit/ImageService.swift | 11 ++- Sources/OrderKit/Order/Order.swift | 1 + Sources/OrderKit/Order/OrderBuilder.swift | 1 + Sources/OrderKit/Order/OrderProduct.swift | 1 + Sources/OrderKit/OrderManager.swift | 2 + Sources/OrderKit/OrderService.swift | 6 +- .../OrderKit/WebserviceProvider+Order.swift | 9 ++ .../ProductKit/Enumerations/CoffeeColor.swift | 29 ++++++ Sources/ProductKit/MenuManager.swift | 1 + Sources/ProductKit/Products/CakeService.swift | 95 +++++++++++++++++++ Sources/ProductKit/Products/Product.swift | 6 +- .../ProductKit/Products/ProductService.swift | 3 +- .../WebserviceProvider+Product.swift | 13 +++ .../CacheTests/CacheTests.swift | 7 +- Tests/Coffee-KitTests/Coffee_LoverTests.swift | 6 +- .../LoggerExtensionTests.swift | 7 +- .../ManagerTests/MenuManagerTests.swift | 8 +- .../MemorySaftyTest/WeakSelfTests.swift | 6 +- Tests/Coffee-KitTests/OrderTests.swift | 11 ++- .../ServiceTests/ProductServiceTests.swift | 7 +- .../WebsocketConnectionTest.swift | 6 +- 30 files changed, 353 insertions(+), 57 deletions(-) create mode 100644 Sources/FoundationKit/Extensions/AsyncSequenceExtension.swift create mode 100644 Sources/FoundationKit/Extensions/LoggerExtension.swift create mode 100644 Sources/OrderKit/WebserviceProvider+Order.swift create mode 100644 Sources/ProductKit/Enumerations/CoffeeColor.swift create mode 100644 Sources/ProductKit/Products/CakeService.swift create mode 100644 Sources/ProductKit/WebserviceProvider+Product.swift diff --git a/Package.resolved b/Package.resolved index 53ff66f..0368453 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "cc1dbce14c240c4bdc95b54f00155cee6052a519ff4b4d5dd91105804a9ef7f1", + "originHash" : "a48f514a2d67223cc528eabf3c815eb59669fa7009d38ebd8fa8e40a7002daed", "pins" : [ { "identity" : "harmonize", "kind" : "remoteSourceControl", "location" : "https://github.com/perrystreetsoftware/Harmonize.git", "state" : { - "revision" : "e8d20b57d4b77bdc80e6fbf2ac182fc704771050", - "version" : "0.1.0" + "revision" : "1279fbf4d87c9b9bd230e91911194145959e6d54", + "version" : "0.9.0" } }, { @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-syntax.git", "state" : { - "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", - "version" : "509.1.1" + "revision" : "f99ae8aa18f0cf0d53481901f88a0991dc3bd4a2", + "version" : "601.0.1" } }, { diff --git a/Package.swift b/Package.swift index 4b43243..792f55c 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,6 @@ let package = Package( .macOS(.v14), ], products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "FoundationKit", targets: ["FoundationKit"] @@ -35,16 +34,24 @@ let package = Package( dependencies: [ .package( url: "https://github.com/perrystreetsoftware/Harmonize.git", - from: "0.1.0" + from: "0.9.0" ) ], targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. .target( - name: "Coffee-Kit", + name: "FoundationKit", + dependencies: [], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency"), + .enableUpcomingFeature("NonisolatedNonsendingByDefault"), + .enableUpcomingFeature("InferIsolatedConformances"), + .defaultIsolation(MainActor.self) + ] + ), + .target( + name: "AuthenticationKit", dependencies: [ - "Authentication-Kit" + "FoundationKit" ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency"), @@ -53,17 +60,25 @@ let package = Package( .defaultIsolation(MainActor.self) ] ), - .testTarget( - name: "Coffee-KitTests", + .target( + name: "ProductKit", dependencies: [ - "Coffee-Kit", - .product(name: "Harmonize", package: "Harmonize"), + "FoundationKit", + "AuthenticationKit" + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency"), + .enableUpcomingFeature("NonisolatedNonsendingByDefault"), + .enableUpcomingFeature("InferIsolatedConformances"), + .defaultIsolation(MainActor.self) ] ), - .target( - name: "Authentication-Kit", + name: "OrderKit", dependencies: [ + "FoundationKit", + "AuthenticationKit", + "ProductKit" ], swiftSettings: [ .enableExperimentalFeature("StrictConcurrency"), @@ -71,6 +86,31 @@ let package = Package( .enableUpcomingFeature("InferIsolatedConformances"), .defaultIsolation(MainActor.self) ] - ) + ), + .target( + name: "ImageKit", + dependencies: [ + "FoundationKit", + "AuthenticationKit", + "ProductKit" + ], + swiftSettings: [ + .enableExperimentalFeature("StrictConcurrency"), + .enableUpcomingFeature("NonisolatedNonsendingByDefault"), + .enableUpcomingFeature("InferIsolatedConformances"), + .defaultIsolation(MainActor.self) + ] + ), + .testTarget( + name: "Coffee-KitTests", + dependencies: [ + "FoundationKit", + "AuthenticationKit", + "ProductKit", + "OrderKit", + "ImageKit", + .product(name: "Harmonize", package: "Harmonize"), + ] + ), ] ) diff --git a/Sources/AuthenticationKit/AutenticationManager.swift b/Sources/AuthenticationKit/AutenticationManager.swift index e9347ec..0d09f26 100644 --- a/Sources/AuthenticationKit/AutenticationManager.swift +++ b/Sources/AuthenticationKit/AutenticationManager.swift @@ -1,8 +1,8 @@ - import Foundation +import FoundationKit -public actor AutenticationManager { +public actor AutenticationManager: Authenticating { private let keychain: KeychainService private let account = "currentUser" diff --git a/Sources/FoundationKit/Cache/Cache.swift b/Sources/FoundationKit/Cache/Cache.swift index da05ec5..c435dc5 100644 --- a/Sources/FoundationKit/Cache/Cache.swift +++ b/Sources/FoundationKit/Cache/Cache.swift @@ -18,7 +18,7 @@ public actor Cache( + into initialValue: C = C() + ) async throws -> C where C.Element == Element { + var result = initialValue + for try await element in self { + result.append(element) + } + return result + } +} diff --git a/Sources/FoundationKit/Extensions/LoggerExtension.swift b/Sources/FoundationKit/Extensions/LoggerExtension.swift new file mode 100644 index 0000000..0251feb --- /dev/null +++ b/Sources/FoundationKit/Extensions/LoggerExtension.swift @@ -0,0 +1,44 @@ +// +// OSLogExtension.swift +// Coffee-Kit +// +// Created by Christoph Rohde on 18.04.25. +// +import Foundation +import OSLog + +public extension Logger { + func trackTask(called name: String, task: () async throws -> Void) async { + let startTime = Date() + let duration: TimeInterval + + do { + try await task() + duration = Date().timeIntervalSince(startTime) + + // split duration into minutes, seconds and milliseconds + let minutes = Int(duration) / 60 + let seconds = Int(duration) % 60 + let milliseconds = Int((duration - Double(minutes * 60 + seconds)) * 1000) + + self.info("Task '\(name)' successful completed in \(minutes) minutes, \(seconds) seconds and \(milliseconds) milliseconds") + } catch { + duration = Date().timeIntervalSince(startTime) + + // split duration into minutes, seconds and milliseconds + let minutes = Int(duration) / 60 + let seconds = Int(duration) % 60 + let milliseconds = Int((duration - Double(minutes * 60 + seconds)) * 1000) + self.error("Task '\(name)' failed in \(minutes) minutes, \(seconds) seconds and \(milliseconds) milliseconds") + self.error("Error: \(error.localizedDescription)") + } + } + + func logExecutionTime(_ log: String, block: () async -> Void) async { + let start = DispatchTime.now() + await block() + let end = DispatchTime.now() + let diff = Double(end.uptimeNanoseconds - start.uptimeNanoseconds) / 1000000000 + print(log, String(format: "%.3f", diff)) + } +} diff --git a/Sources/FoundationKit/WebserviceProvider.swift b/Sources/FoundationKit/WebserviceProvider.swift index 2b5c6f7..4fa5b95 100644 --- a/Sources/FoundationKit/WebserviceProvider.swift +++ b/Sources/FoundationKit/WebserviceProvider.swift @@ -6,23 +6,17 @@ // import Foundation -import Authentication_Kit -public struct WebserviceProvider{ +public protocol Authenticating: Sendable { + func authenticate(_ request: inout URLRequest) async +} + +public struct WebserviceProvider { public let databaseAPI: DatabaseAPI - public let authManager: AutenticationManager? + public let authManager: (any Authenticating)? - public init(inMode databaseAPI: consuming DatabaseAPI, authManager: AutenticationManager? = nil) { + public init(inMode databaseAPI: consuming DatabaseAPI, authManager: (any Authenticating)? = nil) { self.databaseAPI = databaseAPI self.authManager = authManager } - - public var orderService: OrderService { - return OrderService(databaseAPI: databaseAPI, authManager: authManager) - } - - public var productService: ProductService { - return ProductService(databaseAPI: databaseAPI, authManager: authManager) - } - } diff --git a/Sources/FoundationKit/WebsocketConnection.swift b/Sources/FoundationKit/WebsocketConnection.swift index 62da679..cbc3f2c 100644 --- a/Sources/FoundationKit/WebsocketConnection.swift +++ b/Sources/FoundationKit/WebsocketConnection.swift @@ -96,7 +96,7 @@ public struct WebsocketConnection: Sendable { } } - func close() { + public func close() { webSocketTask.cancel(with: .normalClosure, reason: nil) } } diff --git a/Sources/ImageKit/ImageManager.swift b/Sources/ImageKit/ImageManager.swift index 9549db3..71eb23b 100644 --- a/Sources/ImageKit/ImageManager.swift +++ b/Sources/ImageKit/ImageManager.swift @@ -6,6 +6,8 @@ // import Foundation +import FoundationKit +import ProductKit @Observable public final class ImageManager { // MARK: - Properties diff --git a/Sources/ImageKit/ImageService.swift b/Sources/ImageKit/ImageService.swift index 5135e7c..d24a10b 100644 --- a/Sources/ImageKit/ImageService.swift +++ b/Sources/ImageKit/ImageService.swift @@ -6,6 +6,8 @@ // import Foundation +import FoundationKit +import ProductKit public enum ImageServiceError: Error { case imageNotFound @@ -15,7 +17,7 @@ public enum ImageServiceError: Error { public struct ImageService { private let imageUrl: URL private let urlSession: URLSession - private(set) var imageCache: Cache + public private(set) var imageCache: Cache public init(databaseAPI: borrowing DatabaseAPI) { let urlSessionConfiguration = URLSessionConfiguration.default @@ -46,16 +48,15 @@ public struct ImageService { @concurrent @Sendable public func getImageData(for product: borrowing Product) async throws -> Data { - let newImageName = product.imageName.replacing(".png", with: ".heic") + let productImageUrl = product.imageUrl(relativeTo: imageUrl) - if let cachedImage = await imageCache.get(key: newImageName) { + if let cachedImage = await imageCache.get(key: productImageUrl.lastPathComponent) { return cachedImage } - let productImageUrl = await imageUrl / product.category / newImageName let imageData = try await fetchImageData(from: productImageUrl) - await imageCache.set(key: newImageName, value: imageData) + await imageCache.set(key: productImageUrl.lastPathComponent, value: imageData) return imageData } } diff --git a/Sources/OrderKit/Order/Order.swift b/Sources/OrderKit/Order/Order.swift index 5117b33..2de73a0 100644 --- a/Sources/OrderKit/Order/Order.swift +++ b/Sources/OrderKit/Order/Order.swift @@ -5,6 +5,7 @@ // Created by Christoph Rohde on 04.12.24. // import Foundation +import ProductKit nonisolated public struct Order { public let id: UUID diff --git a/Sources/OrderKit/Order/OrderBuilder.swift b/Sources/OrderKit/Order/OrderBuilder.swift index 3c8aaac..ae8c071 100644 --- a/Sources/OrderKit/Order/OrderBuilder.swift +++ b/Sources/OrderKit/Order/OrderBuilder.swift @@ -6,6 +6,7 @@ // import Foundation +import ProductKit @Observable public class OrderBuilder { // MARK: - Properties diff --git a/Sources/OrderKit/Order/OrderProduct.swift b/Sources/OrderKit/Order/OrderProduct.swift index 75639a1..72a27f6 100644 --- a/Sources/OrderKit/Order/OrderProduct.swift +++ b/Sources/OrderKit/Order/OrderProduct.swift @@ -6,6 +6,7 @@ // import Foundation +import ProductKit @Observable public final class OrderProduct { // shold be @Observable and non sendable diff --git a/Sources/OrderKit/OrderManager.swift b/Sources/OrderKit/OrderManager.swift index 04dc8c2..290aed8 100644 --- a/Sources/OrderKit/OrderManager.swift +++ b/Sources/OrderKit/OrderManager.swift @@ -7,6 +7,8 @@ import Foundation import OSLog +import FoundationKit +import ProductKit @Observable public final class OrderManager { diff --git a/Sources/OrderKit/OrderService.swift b/Sources/OrderKit/OrderService.swift index 6bc3b98..b5a5c60 100644 --- a/Sources/OrderKit/OrderService.swift +++ b/Sources/OrderKit/OrderService.swift @@ -7,7 +7,9 @@ import Foundation import OSLog -import Authentication_Kit +import FoundationKit +import ProductKit +import AuthenticationKit public struct OrderService { // MARK: - Properties @@ -19,7 +21,7 @@ public struct OrderService { // MARK: - Initializer - init(databaseAPI: borrowing DatabaseAPI, authManager: AutenticationManager? = nil) { + public init(databaseAPI: borrowing DatabaseAPI, authManager: AutenticationManager? = nil) { let urlSessionConfiguration = URLSessionConfiguration.default urlSessionConfiguration.timeoutIntervalForRequest = 14 diff --git a/Sources/OrderKit/WebserviceProvider+Order.swift b/Sources/OrderKit/WebserviceProvider+Order.swift new file mode 100644 index 0000000..1548522 --- /dev/null +++ b/Sources/OrderKit/WebserviceProvider+Order.swift @@ -0,0 +1,9 @@ +import Foundation +import FoundationKit +import AuthenticationKit + +public extension WebserviceProvider { + var orderService: OrderService { + return OrderService(databaseAPI: databaseAPI, authManager: authManager as? AutenticationManager) + } +} diff --git a/Sources/ProductKit/Enumerations/CoffeeColor.swift b/Sources/ProductKit/Enumerations/CoffeeColor.swift new file mode 100644 index 0000000..e5d842d --- /dev/null +++ b/Sources/ProductKit/Enumerations/CoffeeColor.swift @@ -0,0 +1,29 @@ +// +// CoffeeColor.swift +// Coffee-Kit +// +// Created by Christoph Rohde on 05.07.25. +// + +public enum CoffeeColor: String, CaseIterable, Sendable { + case coffeeBrownLight + case coffeeBrownDark + case coffeeAccent + + public func getRGB() -> (red: Double, green: Double, blue: Double) { + switch self { + case .coffeeBrownLight: + // #8B4513 + return (0.545, 0.271, 0.075) // Light coffee brown + // return (0.8, 0.52, 0.36) // Light coffee brown + case .coffeeBrownDark: + // #5A3319 + return (0.353, 0.2, 0.098) // Dark coffee brown +// return (0.4, 0.26, 0.18) // Dark coffee brown + case .coffeeAccent: + // #F5F5DC + return (0.961, 0.961, 0.863) // Accent color for coffee +// return (0.9, 0.75, 0.5) // Accent color for coffee + } + } +} diff --git a/Sources/ProductKit/MenuManager.swift b/Sources/ProductKit/MenuManager.swift index 4c12262..15b95e3 100644 --- a/Sources/ProductKit/MenuManager.swift +++ b/Sources/ProductKit/MenuManager.swift @@ -6,6 +6,7 @@ // import Foundation +import FoundationKit @Observable public final class MenuManager { diff --git a/Sources/ProductKit/Products/CakeService.swift b/Sources/ProductKit/Products/CakeService.swift new file mode 100644 index 0000000..4e5ead4 --- /dev/null +++ b/Sources/ProductKit/Products/CakeService.swift @@ -0,0 +1,95 @@ +// +// CakeService.swift +// Coffee-Kit +// +// Created by Christoph Rohde on 22.01.25. +// + +import Foundation +import FoundationKit + + +public struct CakeService { + // MARK: Properties + + let cakeURL: URL + + // MARK: Initializer + + public init(databaseAPI: borrowing DatabaseAPI) { + self.cakeURL = databaseAPI.baseURL / "cake" + } + + // MARK: Methods + + public func getIds() async throws -> [String] { + let cakeIdsUrls = cakeURL / "ids" + let (data, response) = try await URLSession.shared.data(from: cakeIdsUrls) + + guard let drinkIds = try? JSONDecoder().decode([String].self, from: data) else { + print(response) + print(""" + Error in \(#file) + \t\(#function) \(#line):\(#column) + \tStatus code: \((response as? HTTPURLResponse)?.statusCode ?? 0) + """) + throw FetchError.decodingError + } + + return drinkIds + } + + public func load(by id: consuming String) async throws -> Product { + let cakeByIdUrl = cakeURL / "id" / id + + let (data, response) = try await URLSession.shared.data(from: cakeByIdUrl) + + guard let coffee = try? JSONDecoder().decode(Product.self, from: data) else { + print(response) + + let stacktrace = Thread.callStackSymbols.joined(separator: "\n") + print(stacktrace) + print(""" + Error in \(#file) + \t\(#function) \(#line):\(#column) + \tStatus code: \((response as? HTTPURLResponse)?.statusCode ?? 0) + """) + throw FetchError.decodingError + } + + return coffee + } + + public func load(by ids: [String]) async -> AsyncThrowingStream { + return AsyncThrowingStream { continuation in + Task { + do { + for id in ids { + let cakeModel = try await load(by: id) + continuation.yield(cakeModel) + } + continuation.finish() + } catch { + continuation.finish(throwing: error) + } + } + } + } + + public func loadAll() async -> AsyncStream> { + return AsyncStream> { continuation in + Task { + do { + let ids = try await getIds() + for id in ids { + let cakeModel = try await load(by: id) + continuation.yield(.success(cakeModel)) + } + } catch { + continuation.yield(.failure(error)) + } + continuation.finish() + } + } + } +} diff --git a/Sources/ProductKit/Products/Product.swift b/Sources/ProductKit/Products/Product.swift index 9ae3950..5a23568 100644 --- a/Sources/ProductKit/Products/Product.swift +++ b/Sources/ProductKit/Products/Product.swift @@ -6,6 +6,7 @@ // import Foundation +import FoundationKit // MARK: - Main Struct @@ -39,8 +40,9 @@ nonisolated public extension Product { // MARK: - Computed Properties public extension Product { - var imageUrl: URL? { - URL(string: "http://127.0.0.1:8080/test/images/\(category)/\(imageName)") + func imageUrl(relativeTo baseURL: URL) -> URL { + let newImageName = imageName.replacing(".png", with: ".heic") + return baseURL / "Images" / category / newImageName } } diff --git a/Sources/ProductKit/Products/ProductService.swift b/Sources/ProductKit/Products/ProductService.swift index 4b1a510..8b0f7e5 100644 --- a/Sources/ProductKit/Products/ProductService.swift +++ b/Sources/ProductKit/Products/ProductService.swift @@ -6,7 +6,8 @@ // import Foundation -import Authentication_Kit +import FoundationKit +import AuthenticationKit public struct ProductService { diff --git a/Sources/ProductKit/WebserviceProvider+Product.swift b/Sources/ProductKit/WebserviceProvider+Product.swift new file mode 100644 index 0000000..d2a228a --- /dev/null +++ b/Sources/ProductKit/WebserviceProvider+Product.swift @@ -0,0 +1,13 @@ +import Foundation +import FoundationKit +import AuthenticationKit + +public extension WebserviceProvider { + var productService: ProductService { + return ProductService(databaseAPI: databaseAPI, authManager: authManager as? AutenticationManager) + } + + var cakeService: CakeService { + return CakeService(databaseAPI: databaseAPI) + } +} diff --git a/Tests/Coffee-KitTests/CacheTests/CacheTests.swift b/Tests/Coffee-KitTests/CacheTests/CacheTests.swift index fdba63c..47f4284 100644 --- a/Tests/Coffee-KitTests/CacheTests/CacheTests.swift +++ b/Tests/Coffee-KitTests/CacheTests/CacheTests.swift @@ -5,10 +5,15 @@ // Created by Christoph Rohde on 16.05.25. // -@testable import Coffee_Kit +import CoffeeKit import Foundation import XCTest import OSLog +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit final class CacheTests: XCTestCase { diff --git a/Tests/Coffee-KitTests/Coffee_LoverTests.swift b/Tests/Coffee-KitTests/Coffee_LoverTests.swift index 6f80769..6fa1a13 100644 --- a/Tests/Coffee-KitTests/Coffee_LoverTests.swift +++ b/Tests/Coffee-KitTests/Coffee_LoverTests.swift @@ -5,9 +5,13 @@ // Created by Christoph Rohde on 20.10.24. // -@testable import Coffee_Kit import Foundation import XCTest +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit final class Coffee_LoverTests: XCTestCase { func testDecodingProduct() throws { diff --git a/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift b/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift index 53b1db4..315ed58 100644 --- a/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift +++ b/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift @@ -5,10 +5,15 @@ // Created by Christoph Rohde on 18.04.25. // -@testable import Coffee_Kit +import CoffeeKit import Foundation import OSLog import XCTest +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit public final class LoggerExtensionTests: XCTestCase { enum TestError: Error { diff --git a/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift b/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift index 11ff42a..f833f10 100644 --- a/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift +++ b/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift @@ -5,10 +5,14 @@ // Created by Christoph Rohde on 15.05.25. // +import CoffeeKit import Foundation import XCTest - -@testable import Coffee_Kit +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit @MainActor final class MenuManagerTests: XCTestCase { diff --git a/Tests/Coffee-KitTests/MemorySaftyTest/WeakSelfTests.swift b/Tests/Coffee-KitTests/MemorySaftyTest/WeakSelfTests.swift index b1ec5cd..033e8f9 100644 --- a/Tests/Coffee-KitTests/MemorySaftyTest/WeakSelfTests.swift +++ b/Tests/Coffee-KitTests/MemorySaftyTest/WeakSelfTests.swift @@ -5,11 +5,15 @@ // Created by Christoph Rohde on 08.05.25. // -@testable import Coffee_Kit import Foundation import Harmonize import OSLog import XCTest +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit final class WeakSelfTests: XCTestCase { let logger = Logger() diff --git a/Tests/Coffee-KitTests/OrderTests.swift b/Tests/Coffee-KitTests/OrderTests.swift index e2d7bb2..6e19894 100644 --- a/Tests/Coffee-KitTests/OrderTests.swift +++ b/Tests/Coffee-KitTests/OrderTests.swift @@ -5,11 +5,15 @@ // Created by Christoph Rohde on 30.12.24. // -@testable import Coffee_Kit import Combine import Foundation import OSLog import XCTest +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit @MainActor final class OrderTests: XCTestCase { @@ -77,7 +81,8 @@ final class OrderTests: XCTestCase { } func testFetchOrderById() async throws { - let orderId = "059621D5-C191-45A9-AB5B-B4B414E9DB17" + let orderIdString = "059621D5-C191-45A9-AB5B-B4B414E9DB17" + let orderId = UUID(uuidString: orderIdString)! let databaseAPI = DatabaseAPI.dev let webservice = WebserviceProvider(inMode: databaseAPI) let orderService = OrderService(databaseAPI: webservice.databaseAPI) @@ -91,7 +96,7 @@ final class OrderTests: XCTestCase { for _ in 0 ..< 10 { let order = try await orderService.getOrder(by: orderId) XCTAssertNotNil(order, "Order should not be nil") - XCTAssertEqual(order.id.uuidString, orderId, "Order ID should match") + XCTAssertEqual(order.id, orderId, "Order ID should match") } let duration = Date().timeIntervalSince(start) diff --git a/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift b/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift index b9b0e4d..c7560c7 100644 --- a/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift +++ b/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift @@ -7,8 +7,11 @@ import Foundation import XCTest -@testable import Coffee_Kit - +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit @MainActor final class ProductServiceTests: XCTestCase { diff --git a/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift b/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift index f9b48ae..8c3428e 100644 --- a/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift +++ b/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift @@ -5,9 +5,13 @@ // Created by Christoph Rohde on 25.10.25. // -@testable import Coffee_Kit import Foundation import XCTest +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit @MainActor final class WebsocketConnectionTest: XCTestCase { From d957aeae4778a81b98c53d8c86fe1cd0c4f52741 Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Sun, 3 May 2026 14:24:41 +0200 Subject: [PATCH 04/12] Move WebseviceProvidor and DatabaseAPI to AuthticationKit --- .../AutenticationManager.swift | 26 ++++++++----------- .../DatabaseAPI.swift | 0 .../WebserviceProvider.swift | 18 +++++++++++++ .../FoundationKit/WebserviceProvider.swift | 22 ---------------- 4 files changed, 29 insertions(+), 37 deletions(-) rename Sources/{FoundationKit => AuthenticationKit}/DatabaseAPI.swift (100%) create mode 100644 Sources/AuthenticationKit/WebserviceProvider.swift delete mode 100644 Sources/FoundationKit/WebserviceProvider.swift diff --git a/Sources/AuthenticationKit/AutenticationManager.swift b/Sources/AuthenticationKit/AutenticationManager.swift index 0d09f26..bce9db7 100644 --- a/Sources/AuthenticationKit/AutenticationManager.swift +++ b/Sources/AuthenticationKit/AutenticationManager.swift @@ -2,17 +2,14 @@ import Foundation import FoundationKit -public actor AutenticationManager: Authenticating { +public actor AutenticationManager { private let keychain: KeychainService private let account = "currentUser" private let accessTokenService = "CoffeeLover.AccessToken" private let refreshTokenService = "CoffeeLover.RefreshToken" private let userService = "CoffeeLover.User" - private let baseURL: URL - - // ARCHITECTURE TRICK: Hier speichern wir den aktuell laufenden Refresh-Call private var refreshTask: Task? public init(keychain: KeychainService, baseURL: URL) { @@ -26,14 +23,13 @@ public actor AutenticationManager: Authenticating { return try await refreshSession() } - // JWT Validierung ohne try! do { let expired = try await JWTValidator.isExpired(token: currentToken) if !expired { return currentToken } } catch { - print("⚠️ Token validation failed: \(error). Attempting refresh...") + print("Token validation failed: \(error). Attempting refresh...") } return try await refreshSession() @@ -72,7 +68,7 @@ public actor AutenticationManager: Authenticating { let userDecoder = JSONDecoder() return try userDecoder.decode(User.self, from: data) } catch { - print("ℹ️ No user data found in keychain.") + print("No user data found in keychain.") return nil } } @@ -83,9 +79,9 @@ public actor AutenticationManager: Authenticating { try await keychain.delete(account: account, service: accessTokenService) try await keychain.delete(account: account, service: refreshTokenService) try await keychain.delete(account: account, service: userService) - print("👤 User logged out and tokens/data cleared.") + print("User logged out and tokens/data cleared.") } catch { - print("⚠️ Error during logout: \(error)") + print("Error during logout: \(error)") } } @@ -105,16 +101,16 @@ public actor AutenticationManager: Authenticating { do { _ = try await keychain.read(account: account, service: refreshTokenService) } catch { - print("ℹ️ No session active (Keychain error: \(error)). Skipping authentication header for \(request.url?.absoluteString ?? "unknown URL").") + print("No session active (Keychain error: \(error)). Skipping authentication header for \(request.url?.absoluteString ?? "unknown URL").") return } do { let token = try await getValidAccessToken() request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - print("✅ Added Authorization header to \(request.url?.absoluteString ?? "unknown URL").") + print("Added Authorization header to \(request.url?.absoluteString ?? "unknown URL").") } catch { - print("⚠️ Authentication failed for \(request.url?.absoluteString ?? "unknown URL"): \(error)") + print("Authentication failed for \(request.url?.absoluteString ?? "unknown URL"): \(error)") } } @@ -122,7 +118,7 @@ public actor AutenticationManager: Authenticating { private func refreshSession() async throws -> String { if let existingTask = refreshTask { - print("⏳ Refresh already in progress. Attaching to the existing task...") + print("Refresh already in progress. Attaching to the existing task...") return try await existingTask.value } @@ -143,7 +139,7 @@ public actor AutenticationManager: Authenticating { request.httpBody = try? JSONEncoder().encode(["refreshToken": refreshToken]) do { - print("🚀 Sending refresh call to backend...") + print("Sending refresh call to backend...") let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { @@ -158,7 +154,7 @@ public actor AutenticationManager: Authenticating { try await storeTokens(accessToken: tokenResponse.accessToken, refreshToken: tokenResponse.refreshToken) - print("✅ Refresh successful! New Access Token stored.") + print("Refresh successful! New Access Token stored.") return tokenResponse.accessToken } catch let error as URLError { diff --git a/Sources/FoundationKit/DatabaseAPI.swift b/Sources/AuthenticationKit/DatabaseAPI.swift similarity index 100% rename from Sources/FoundationKit/DatabaseAPI.swift rename to Sources/AuthenticationKit/DatabaseAPI.swift diff --git a/Sources/AuthenticationKit/WebserviceProvider.swift b/Sources/AuthenticationKit/WebserviceProvider.swift new file mode 100644 index 0000000..ba1418c --- /dev/null +++ b/Sources/AuthenticationKit/WebserviceProvider.swift @@ -0,0 +1,18 @@ +// +// Webservice.swift +// Coffee Lover +// +// Created by Christoph Rohde on 20.10.24. +// + +import Foundation + +public struct WebserviceProvider { + public let databaseAPI: DatabaseAPI + public let autheticationManager: AutenticationManager + + public init(inMode databaseAPI: consuming DatabaseAPI, autheticationManager: AutenticationManager) { + self.databaseAPI = databaseAPI + self.autheticationManager = autheticationManager + } +} diff --git a/Sources/FoundationKit/WebserviceProvider.swift b/Sources/FoundationKit/WebserviceProvider.swift deleted file mode 100644 index 4fa5b95..0000000 --- a/Sources/FoundationKit/WebserviceProvider.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Webservice.swift -// Coffee Lover -// -// Created by Christoph Rohde on 20.10.24. -// - -import Foundation - -public protocol Authenticating: Sendable { - func authenticate(_ request: inout URLRequest) async -} - -public struct WebserviceProvider { - public let databaseAPI: DatabaseAPI - public let authManager: (any Authenticating)? - - public init(inMode databaseAPI: consuming DatabaseAPI, authManager: (any Authenticating)? = nil) { - self.databaseAPI = databaseAPI - self.authManager = authManager - } -} From f256c92ec4b64b9b04cfb2fa248d3b9a508e5602 Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Sun, 3 May 2026 14:58:06 +0200 Subject: [PATCH 05/12] Make AuthenticationManager non optional --- .../WebserviceProvider.swift | 5 +- Sources/ImageKit/ImageManager.swift | 2 +- Sources/ImageKit/ImageService.swift | 1 + Sources/OrderKit/OrderManager.swift | 3 +- Sources/OrderKit/OrderService.swift | 27 ++---- .../OrderKit/WebserviceProvider+Order.swift | 9 -- Sources/ProductKit/MenuManager.swift | 9 +- Sources/ProductKit/Products/CakeService.swift | 95 ------------------- .../ProductKit/Products/ProductService.swift | 19 ++-- .../WebserviceProvider+Product.swift | 13 --- 10 files changed, 25 insertions(+), 158 deletions(-) delete mode 100644 Sources/OrderKit/WebserviceProvider+Order.swift delete mode 100644 Sources/ProductKit/Products/CakeService.swift delete mode 100644 Sources/ProductKit/WebserviceProvider+Product.swift diff --git a/Sources/AuthenticationKit/WebserviceProvider.swift b/Sources/AuthenticationKit/WebserviceProvider.swift index ba1418c..d6d1026 100644 --- a/Sources/AuthenticationKit/WebserviceProvider.swift +++ b/Sources/AuthenticationKit/WebserviceProvider.swift @@ -11,7 +11,10 @@ public struct WebserviceProvider { public let databaseAPI: DatabaseAPI public let autheticationManager: AutenticationManager - public init(inMode databaseAPI: consuming DatabaseAPI, autheticationManager: AutenticationManager) { + public init( + inMode databaseAPI: consuming DatabaseAPI, + autheticationManager: AutenticationManager + ) { self.databaseAPI = databaseAPI self.autheticationManager = autheticationManager } diff --git a/Sources/ImageKit/ImageManager.swift b/Sources/ImageKit/ImageManager.swift index 71eb23b..6f2fb80 100644 --- a/Sources/ImageKit/ImageManager.swift +++ b/Sources/ImageKit/ImageManager.swift @@ -6,7 +6,7 @@ // import Foundation -import FoundationKit +import AuthenticationKit import ProductKit @Observable public final class ImageManager { diff --git a/Sources/ImageKit/ImageService.swift b/Sources/ImageKit/ImageService.swift index d24a10b..41abb1f 100644 --- a/Sources/ImageKit/ImageService.swift +++ b/Sources/ImageKit/ImageService.swift @@ -7,6 +7,7 @@ import Foundation import FoundationKit +import AuthenticationKit import ProductKit public enum ImageServiceError: Error { diff --git a/Sources/OrderKit/OrderManager.swift b/Sources/OrderKit/OrderManager.swift index 290aed8..5747195 100644 --- a/Sources/OrderKit/OrderManager.swift +++ b/Sources/OrderKit/OrderManager.swift @@ -8,6 +8,7 @@ import Foundation import OSLog import FoundationKit +import AuthenticationKit import ProductKit @Observable @@ -25,7 +26,7 @@ public final class OrderManager { public init(from webservice: WebserviceProvider) { self.webservice = webservice - self.orderService = webservice.orderService + self.orderService = OrderService(webserviceProvider: webservice) } public var containsOrder: Bool { diff --git a/Sources/OrderKit/OrderService.swift b/Sources/OrderKit/OrderService.swift index b5a5c60..e5b2383 100644 --- a/Sources/OrderKit/OrderService.swift +++ b/Sources/OrderKit/OrderService.swift @@ -17,17 +17,17 @@ public struct OrderService { private let logger = Logger(subsystem: "com.CodebyCR.coffeeKit", category: "OrderService") private let orderUrl: URL private let urlSession: URLSession - private let authManager: AutenticationManager? + private let authManager: AutenticationManager // MARK: - Initializer - public init(databaseAPI: borrowing DatabaseAPI, authManager: AutenticationManager? = nil) { + public init(webserviceProvider: borrowing WebserviceProvider) { let urlSessionConfiguration = URLSessionConfiguration.default urlSessionConfiguration.timeoutIntervalForRequest = 14 self.urlSession = URLSession(configuration: urlSessionConfiguration) - self.orderUrl = databaseAPI.baseURL / "order" - self.authManager = authManager + self.orderUrl = webserviceProvider.databaseAPI.baseURL / "order" + self.authManager = webserviceProvider.autheticationManager } // MARK: - Methods @@ -40,10 +40,7 @@ public struct OrderService { request.httpBody = requestData request.setValue("application/json", forHTTPHeaderField: "Content-Type") - // Authentication - if let authManager = authManager { - await authManager.authenticate(&request) - } + await authManager.authenticate(&request) logger.debug("Post to \(createOrderURL)") @@ -68,11 +65,8 @@ public struct OrderService { public func getOrder(by id: UUID) async throws -> Order { let orderByIdUrl = orderUrl / "id" / "\(id)" var request = URLRequest(url: orderByIdUrl) - - // Authentication - if let authManager = authManager { - await authManager.authenticate(&request) - } + + await authManager.authenticate(&request) let (data, response) = try await urlSession.data(for: request) @@ -105,11 +99,8 @@ public struct OrderService { let unixTimestamp = Int(lastOrderDate.timeIntervalSince1970) let orderHistoryUrl = orderUrl / "history" / "\(unixTimestamp)" var request = URLRequest(url: orderHistoryUrl) - - // Authentication - if let authManager = authManager { - await authManager.authenticate(&request) - } + + await authManager.authenticate(&request) let (data, response) = try await urlSession.data(for: request) diff --git a/Sources/OrderKit/WebserviceProvider+Order.swift b/Sources/OrderKit/WebserviceProvider+Order.swift deleted file mode 100644 index 1548522..0000000 --- a/Sources/OrderKit/WebserviceProvider+Order.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation -import FoundationKit -import AuthenticationKit - -public extension WebserviceProvider { - var orderService: OrderService { - return OrderService(databaseAPI: databaseAPI, authManager: authManager as? AutenticationManager) - } -} diff --git a/Sources/ProductKit/MenuManager.swift b/Sources/ProductKit/MenuManager.swift index 15b95e3..5f0dd03 100644 --- a/Sources/ProductKit/MenuManager.swift +++ b/Sources/ProductKit/MenuManager.swift @@ -6,7 +6,7 @@ // import Foundation -import FoundationKit +import AuthenticationKit @Observable public final class MenuManager { @@ -20,14 +20,9 @@ import FoundationKit // MARK: - Initializer - public init() { - self.webservice = WebserviceProvider(inMode: .dev) - self.productService = ProductService(databaseAPI: webservice.databaseAPI) - } - public init(from webservice: WebserviceProvider) { self.webservice = webservice - self.productService = webservice.productService + self.productService = ProductService(webserviceProvider: webservice) } // MARK: - Methods diff --git a/Sources/ProductKit/Products/CakeService.swift b/Sources/ProductKit/Products/CakeService.swift deleted file mode 100644 index 4e5ead4..0000000 --- a/Sources/ProductKit/Products/CakeService.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// CakeService.swift -// Coffee-Kit -// -// Created by Christoph Rohde on 22.01.25. -// - -import Foundation -import FoundationKit - - -public struct CakeService { - // MARK: Properties - - let cakeURL: URL - - // MARK: Initializer - - public init(databaseAPI: borrowing DatabaseAPI) { - self.cakeURL = databaseAPI.baseURL / "cake" - } - - // MARK: Methods - - public func getIds() async throws -> [String] { - let cakeIdsUrls = cakeURL / "ids" - let (data, response) = try await URLSession.shared.data(from: cakeIdsUrls) - - guard let drinkIds = try? JSONDecoder().decode([String].self, from: data) else { - print(response) - print(""" - Error in \(#file) - \t\(#function) \(#line):\(#column) - \tStatus code: \((response as? HTTPURLResponse)?.statusCode ?? 0) - """) - throw FetchError.decodingError - } - - return drinkIds - } - - public func load(by id: consuming String) async throws -> Product { - let cakeByIdUrl = cakeURL / "id" / id - - let (data, response) = try await URLSession.shared.data(from: cakeByIdUrl) - - guard let coffee = try? JSONDecoder().decode(Product.self, from: data) else { - print(response) - - let stacktrace = Thread.callStackSymbols.joined(separator: "\n") - print(stacktrace) - print(""" - Error in \(#file) - \t\(#function) \(#line):\(#column) - \tStatus code: \((response as? HTTPURLResponse)?.statusCode ?? 0) - """) - throw FetchError.decodingError - } - - return coffee - } - - public func load(by ids: [String]) async -> AsyncThrowingStream { - return AsyncThrowingStream { continuation in - Task { - do { - for id in ids { - let cakeModel = try await load(by: id) - continuation.yield(cakeModel) - } - continuation.finish() - } catch { - continuation.finish(throwing: error) - } - } - } - } - - public func loadAll() async -> AsyncStream> { - return AsyncStream> { continuation in - Task { - do { - let ids = try await getIds() - for id in ids { - let cakeModel = try await load(by: id) - continuation.yield(.success(cakeModel)) - } - } catch { - continuation.yield(.failure(error)) - } - continuation.finish() - } - } - } -} diff --git a/Sources/ProductKit/Products/ProductService.swift b/Sources/ProductKit/Products/ProductService.swift index 8b0f7e5..a9c0839 100644 --- a/Sources/ProductKit/Products/ProductService.swift +++ b/Sources/ProductKit/Products/ProductService.swift @@ -16,18 +16,18 @@ public struct ProductService { let productURL: URL let urlSession: URLSession private(set) var menuCache = Cache() - private let authManager: AutenticationManager? + private let authManager: AutenticationManager // MARK: Initializer - public init(databaseAPI: DatabaseAPI, authManager: AutenticationManager? = nil) { + public init(webserviceProvider: borrowing WebserviceProvider) { let urlSessionConfiguration = URLSessionConfiguration.default // urlSessionConfiguration.timeoutIntervalForRequest = 14 // urlSessionConfiguration.requestCachePolicy = .returnCacheDataElseLoad self.urlSession = URLSession(configuration: urlSessionConfiguration) - self.productURL = databaseAPI.baseURL / "coffee" - self.authManager = authManager + self.productURL = webserviceProvider.databaseAPI.baseURL / "coffee" + self.authManager = webserviceProvider.autheticationManager print(productURL) } @@ -36,11 +36,7 @@ public struct ProductService { @Sendable public func getIds() async throws -> [String] { let productIdsUrl = productURL / "ids" var request = URLRequest(url: productIdsUrl) - - if let authManager = authManager { - await authManager.authenticate(&request) - } - + await authManager.authenticate(&request) let (data, response) = try await urlSession.data(for: request) guard let productIds = try? JSONDecoder().decode([String].self, from: data) else { @@ -66,10 +62,7 @@ public struct ProductService { } var request = URLRequest(url: coffeeByIdUrl) - - if let authManager = authManager { - await authManager.authenticate(&request) - } + await authManager.authenticate(&request) let (data, response) = try await urlSession.data(for: request) diff --git a/Sources/ProductKit/WebserviceProvider+Product.swift b/Sources/ProductKit/WebserviceProvider+Product.swift deleted file mode 100644 index d2a228a..0000000 --- a/Sources/ProductKit/WebserviceProvider+Product.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation -import FoundationKit -import AuthenticationKit - -public extension WebserviceProvider { - var productService: ProductService { - return ProductService(databaseAPI: databaseAPI, authManager: authManager as? AutenticationManager) - } - - var cakeService: CakeService { - return CakeService(databaseAPI: databaseAPI) - } -} From 8de6a7af513e2f0a0772ce8f7af1918737116b99 Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Sun, 3 May 2026 18:23:22 +0200 Subject: [PATCH 06/12] Fix initilizier inconsistencies --- Sources/AuthenticationKit/AutenticationManager.swift | 4 ++-- Sources/AuthenticationKit/AuthenticationBuilder.swift | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/AuthenticationKit/AutenticationManager.swift b/Sources/AuthenticationKit/AutenticationManager.swift index bce9db7..890e025 100644 --- a/Sources/AuthenticationKit/AutenticationManager.swift +++ b/Sources/AuthenticationKit/AutenticationManager.swift @@ -12,9 +12,9 @@ public actor AutenticationManager { private let baseURL: URL private var refreshTask: Task? - public init(keychain: KeychainService, baseURL: URL) { + public init(keychain: KeychainService, databaseAPI: DatabaseAPI) { self.keychain = keychain - self.baseURL = baseURL + self.baseURL = databaseAPI.baseURL / "authentication" } public func getValidAccessToken() async throws -> String { diff --git a/Sources/AuthenticationKit/AuthenticationBuilder.swift b/Sources/AuthenticationKit/AuthenticationBuilder.swift index 4194206..d03be3b 100644 --- a/Sources/AuthenticationKit/AuthenticationBuilder.swift +++ b/Sources/AuthenticationKit/AuthenticationBuilder.swift @@ -1,5 +1,6 @@ import Foundation +import FoundationKit public enum CredentialDenyReason { @@ -52,11 +53,10 @@ public final class AuthenticationBuilder { @ObservationIgnored private let session = URLSession.shared public init( - authManager: AutenticationManager, - baseURL: URL + webserviceProvider: WebserviceProvider ) { - self.authManager = authManager - self.baseURL = baseURL + self.authManager = webserviceProvider.autheticationManager + self.baseURL = webserviceProvider.databaseAPI.baseURL / "authentication" } public var isValidEmail: CredentialState { From c36bca89ee9d67f9e103dbeaaec1699069419fdd Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Sun, 3 May 2026 18:32:31 +0200 Subject: [PATCH 07/12] Fix image url path --- Sources/ProductKit/Products/Product.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ProductKit/Products/Product.swift b/Sources/ProductKit/Products/Product.swift index 5a23568..fd18a4f 100644 --- a/Sources/ProductKit/Products/Product.swift +++ b/Sources/ProductKit/Products/Product.swift @@ -40,9 +40,9 @@ nonisolated public extension Product { // MARK: - Computed Properties public extension Product { - func imageUrl(relativeTo baseURL: URL) -> URL { + func imageUrl(relativeTo imageURL: URL) -> URL { let newImageName = imageName.replacing(".png", with: ".heic") - return baseURL / "Images" / category / newImageName + return imageURL / category / newImageName } } From 5c401244c0e63d5d49d42a4a7ae6da519f941c45 Mon Sep 17 00:00:00 2001 From: Christoph Rohde <44606665+CodebyCR@users.noreply.github.com> Date: Sun, 3 May 2026 18:37:21 +0200 Subject: [PATCH 08/12] Update Swift version in GitHub Actions workflow --- .github/workflows/main-safety.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-safety.yml b/.github/workflows/main-safety.yml index 42df5f0..e80c3db 100644 --- a/.github/workflows/main-safety.yml +++ b/.github/workflows/main-safety.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - swift-version: "5.10.0" + swift-version: "6.2.0" - name: Get swift version run: swift --version From 89926bd54fdcb66cf7507adb01413576c93238c8 Mon Sep 17 00:00:00 2001 From: Christoph Rohde <44606665+CodebyCR@users.noreply.github.com> Date: Sun, 3 May 2026 18:43:37 +0200 Subject: [PATCH 09/12] Refactor main safety workflow for Swift project --- .github/workflows/main-safety.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main-safety.yml b/.github/workflows/main-safety.yml index e80c3db..8d419d6 100644 --- a/.github/workflows/main-safety.yml +++ b/.github/workflows/main-safety.yml @@ -1,24 +1,23 @@ # This workflow will build a Swift project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift - -name: Main safety - +name: Main safety on: pull_request: branches: [ "main" ] - jobs: build_and_test: - runs-on: macos-latest steps: - uses: actions/checkout@v4 + + - name: Setup Swift + uses: swift-actions/setup-swift@v2 with: swift-version: "6.2.0" - + - name: Get swift version run: swift --version - + - name: Build run: swift build -v From 8800bfb426829ea794080ad1a68fd458fb1de450 Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Mon, 4 May 2026 19:17:50 +0200 Subject: [PATCH 10/12] Fix tests --- .../CacheTests/CacheTests.swift | 35 +++++++++--- .../LoggerExtensionTests.swift | 1 - .../ManagerTests/MenuManagerTests.swift | 7 ++- Tests/Coffee-KitTests/OrderTests.swift | 56 +++++++++++-------- .../ServiceTests/ProductServiceTests.swift | 21 ++++++- 5 files changed, 85 insertions(+), 35 deletions(-) diff --git a/Tests/Coffee-KitTests/CacheTests/CacheTests.swift b/Tests/Coffee-KitTests/CacheTests/CacheTests.swift index 47f4284..1845b7d 100644 --- a/Tests/Coffee-KitTests/CacheTests/CacheTests.swift +++ b/Tests/Coffee-KitTests/CacheTests/CacheTests.swift @@ -5,7 +5,6 @@ // Created by Christoph Rohde on 16.05.25. // -import CoffeeKit import Foundation import XCTest import OSLog @@ -15,6 +14,7 @@ import ProductKit import OrderKit import ImageKit +@MainActor final class CacheTests: XCTestCase { private let log = Logger(subsystem: "Coffee-Kit Tests", category: "CacheTests") @@ -34,7 +34,11 @@ final class CacheTests: XCTestCase { // MARK: - Properties func testCaching() async throws { - let productService = await ProductService(databaseAPI: .dev) + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let productService = ProductService(webserviceProvider: webserviceProvider) let cache = Cache(memoryLimitInMB: 200) let cappuccinoId = "01dc289a-4bb0-407c-b5a6-a6a868ab0101" @@ -56,7 +60,11 @@ final class CacheTests: XCTestCase { } func testCacheMemoryLimit() async throws { - let productService = await ProductService(databaseAPI: .dev) + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let productService = ProductService(webserviceProvider: webserviceProvider) let cache = Cache() for id in ids { // FIX memorie limit @@ -75,7 +83,11 @@ final class CacheTests: XCTestCase { } func testCacheInitilisationWithKeyList() async throws { - let productService = await ProductService(databaseAPI: .dev) + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let productService = ProductService(webserviceProvider: webserviceProvider) guard let productCache = try? await Cache.create(by: ids, with: productService.load) else { @@ -95,8 +107,13 @@ final class CacheTests: XCTestCase { } func testDataCaching() async throws { - let imageService = await ImageService(databaseAPI: .dev) - let imageCache = await imageService.imageCache + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + + let imageService = ImageService(databaseAPI: webserviceProvider.databaseAPI) + let imageCache = imageService.imageCache let testProduct = Product() let data = try await imageService.getImageData(for: testProduct) @@ -108,7 +125,11 @@ final class CacheTests: XCTestCase { func testFetchingPerformance() async { - let productService = await ProductService(databaseAPI: .dev) + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let productService = ProductService(webserviceProvider: webserviceProvider) let cache = Cache(memoryLimitInMB: 50) let ids = try! await productService.getIds() let measureOptions = XCTMeasureOptions() diff --git a/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift b/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift index 315ed58..39184bd 100644 --- a/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift +++ b/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift @@ -5,7 +5,6 @@ // Created by Christoph Rohde on 18.04.25. // -import CoffeeKit import Foundation import OSLog import XCTest diff --git a/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift b/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift index f833f10..6fb4211 100644 --- a/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift +++ b/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift @@ -5,7 +5,6 @@ // Created by Christoph Rohde on 15.05.25. // -import CoffeeKit import Foundation import XCTest import FoundationKit @@ -16,9 +15,13 @@ import ImageKit @MainActor final class MenuManagerTests: XCTestCase { - let menuManager = MenuManager(from: WebserviceProvider(inMode: .dev)) func testItemSequence() { + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let menuManager = MenuManager(from: webserviceProvider) let itemSequence = menuManager.productService XCTAssertNotNil(itemSequence) } diff --git a/Tests/Coffee-KitTests/OrderTests.swift b/Tests/Coffee-KitTests/OrderTests.swift index 6e19894..40c78b0 100644 --- a/Tests/Coffee-KitTests/OrderTests.swift +++ b/Tests/Coffee-KitTests/OrderTests.swift @@ -20,26 +20,28 @@ final class OrderTests: XCTestCase { let logger = Logger(subsystem: "com.CodebyCR.coffeeKit", category: "OrderTests") func testTakingOrder() async { + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) - let testUserId = UUID(uuidString: "03F35975-AF57-4691-811F-4AB872FDB51B")! - let databaseAPI = DatabaseAPI.dev - let webservice = WebserviceProvider(inMode: databaseAPI) - let orderManager = OrderManager(from: webservice) + let testUserId = UUID(uuidString: "03F35975-AF57-4691-811F-4AB872FDB51B")! + let orderManager = OrderManager(from: webserviceProvider) - let orderBuilder = OrderBuilder(for: testUserId) - orderBuilder.addProduct(Product()) - orderBuilder.addProduct(Product()) + let orderBuilder = OrderBuilder(for: testUserId) + orderBuilder.addProduct(Product()) + orderBuilder.addProduct(Product()) - let result = orderManager.takeOrder(from: orderBuilder) + let result = orderManager.takeOrder(from: orderBuilder) - switch result { - case .success(let message): - print("Order result: \(message)") - XCTAssertEqual(message, "Your order will arrive soon.") - case .failure(let error): - print("Order failed with error: \(error)") - XCTAssertNoThrow(error) - } + switch result { + case .success(let message): + print("Order result: \(message)") + XCTAssertEqual(message, "Your order will arrive soon.") + case .failure(let error): + print("Order failed with error: \(error)") + XCTAssertNoThrow(error) + } // Delete test order } @@ -81,11 +83,17 @@ final class OrderTests: XCTestCase { } func testFetchOrderById() async throws { - let orderIdString = "059621D5-C191-45A9-AB5B-B4B414E9DB17" - let orderId = UUID(uuidString: orderIdString)! - let databaseAPI = DatabaseAPI.dev - let webservice = WebserviceProvider(inMode: databaseAPI) - let orderService = OrderService(databaseAPI: webservice.databaseAPI) + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let orderService = OrderService(webserviceProvider: webserviceProvider) + + // Create a new order first to ensure we have a valid ID + let testUserId = UUID(uuidString: "03F35975-AF57-4691-811F-4AB872FDB51B")! + let order = Order() + try await orderService.takeOrder(order) + let orderId = order.id // Warmup _ = try? await orderService.getOrder(by: orderId) @@ -94,9 +102,9 @@ final class OrderTests: XCTestCase { let start = Date() for _ in 0 ..< 10 { - let order = try await orderService.getOrder(by: orderId) - XCTAssertNotNil(order, "Order should not be nil") - XCTAssertEqual(order.id, orderId, "Order ID should match") + let fetchedOrder = try await orderService.getOrder(by: orderId) + XCTAssertNotNil(fetchedOrder, "Order should not be nil") + XCTAssertEqual(fetchedOrder.id, orderId, "Order ID should match") } let duration = Date().timeIntervalSince(start) diff --git a/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift b/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift index c7560c7..82e98f8 100644 --- a/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift +++ b/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift @@ -19,12 +19,19 @@ final class ProductServiceTests: XCTestCase { // MARK: - Properties #if DEBUG - let productService = ProductService(databaseAPI: .dev) + func testLoadAllIds() async { + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let productService = ProductService(webserviceProvider: webserviceProvider) + + guard let ids = try? await productService .getIds() @@ -38,6 +45,12 @@ final class ProductServiceTests: XCTestCase { } func testFetchProductById() async { + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let productService = ProductService(webserviceProvider: webserviceProvider) + let cappuccinoId = "01dc289a-4bb0-407c-b5a6-a6a868ab0101" guard let product = try? await productService @@ -54,6 +67,12 @@ final class ProductServiceTests: XCTestCase { } func testFetchAllProducts() async { + let keychain = DefaultKeychainManager() + let databaseAPI: DatabaseAPI = .dev + let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let productService = ProductService(webserviceProvider: webserviceProvider) + guard let products = try? await productService .loadAll() .collect(into: [Result]()) From b909678edaf86df4781353ef6e1835cf36f78399 Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Mon, 4 May 2026 20:26:41 +0200 Subject: [PATCH 11/12] Skip API dependent tests for ci workflow --- .../CacheTests/CacheTests.swift | 7 +++- .../LoggerExtensionTests.swift | 39 ------------------- .../MemorySaftyTest/WeakSelfTests.swift | 21 ---------- Tests/Coffee-KitTests/OrderTests.swift | 4 +- .../ServiceTests/ProductServiceTests.swift | 9 +++-- .../WebsocketConnectionTest.swift | 1 + .../Coffee-KitTests/XCTestCase+APITests.swift | 19 +++++++++ 7 files changed, 35 insertions(+), 65 deletions(-) delete mode 100644 Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift create mode 100644 Tests/Coffee-KitTests/XCTestCase+APITests.swift diff --git a/Tests/Coffee-KitTests/CacheTests/CacheTests.swift b/Tests/Coffee-KitTests/CacheTests/CacheTests.swift index 1845b7d..3789a44 100644 --- a/Tests/Coffee-KitTests/CacheTests/CacheTests.swift +++ b/Tests/Coffee-KitTests/CacheTests/CacheTests.swift @@ -34,6 +34,7 @@ final class CacheTests: XCTestCase { // MARK: - Properties func testCaching() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) @@ -60,6 +61,7 @@ final class CacheTests: XCTestCase { } func testCacheMemoryLimit() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) @@ -83,6 +85,7 @@ final class CacheTests: XCTestCase { } func testCacheInitilisationWithKeyList() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) @@ -107,6 +110,7 @@ final class CacheTests: XCTestCase { } func testDataCaching() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) @@ -124,7 +128,8 @@ final class CacheTests: XCTestCase { } - func testFetchingPerformance() async { + func testFetchingPerformance() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) diff --git a/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift b/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift deleted file mode 100644 index 39184bd..0000000 --- a/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// LoggerExtensionTests.swift -// Coffee-Kit -// -// Created by Christoph Rohde on 18.04.25. -// - -import Foundation -import OSLog -import XCTest -import FoundationKit -import AuthenticationKit -import ProductKit -import OrderKit -import ImageKit - -public final class LoggerExtensionTests: XCTestCase { - enum TestError: Error { - case exampleThrow - } - - func testTrackSucessfulTask() async { - let logger = Logger() - - await logger.trackTask(called: "SuccessTestTask") { - try await Task.sleep(for: .seconds(1)) - XCTAssertTrue(true) - } - } - - func testTrackFailedTask() async { - let logger = Logger() - - await logger.trackTask(called: "FailedTestTask") { - try await Task.sleep(for: .seconds(1)) - throw TestError.exampleThrow - } - } -} diff --git a/Tests/Coffee-KitTests/MemorySaftyTest/WeakSelfTests.swift b/Tests/Coffee-KitTests/MemorySaftyTest/WeakSelfTests.swift index 033e8f9..e52e028 100644 --- a/Tests/Coffee-KitTests/MemorySaftyTest/WeakSelfTests.swift +++ b/Tests/Coffee-KitTests/MemorySaftyTest/WeakSelfTests.swift @@ -37,25 +37,4 @@ final class WeakSelfTests: XCTestCase { } } - public func testClassNamingConvention() throws { - let message = "Class name violate the naming convention." - - let candidates = Harmonize - .productionCode() - .classes() - .withoutSuffix( - "ViewModel", - "Service", - "Manager", - "Builder" - ) - - if candidates.isNotEmpty { - print("Found \(candidates.count) candidates for naming convention violations.") - } - - for _ in candidates { - XCTAssert(false, message) - } - } } diff --git a/Tests/Coffee-KitTests/OrderTests.swift b/Tests/Coffee-KitTests/OrderTests.swift index 40c78b0..1c16197 100644 --- a/Tests/Coffee-KitTests/OrderTests.swift +++ b/Tests/Coffee-KitTests/OrderTests.swift @@ -19,7 +19,8 @@ import ImageKit final class OrderTests: XCTestCase { let logger = Logger(subsystem: "com.CodebyCR.coffeeKit", category: "OrderTests") - func testTakingOrder() async { + func testTakingOrder() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) @@ -83,6 +84,7 @@ final class OrderTests: XCTestCase { } func testFetchOrderById() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) diff --git a/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift b/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift index 82e98f8..5ba01f1 100644 --- a/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift +++ b/Tests/Coffee-KitTests/ServiceTests/ProductServiceTests.swift @@ -24,7 +24,8 @@ final class ProductServiceTests: XCTestCase { - func testLoadAllIds() async { + func testLoadAllIds() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) @@ -44,7 +45,8 @@ final class ProductServiceTests: XCTestCase { XCTAssertFalse(ids.isEmpty, "Product IDs should not be empty") } - func testFetchProductById() async { + func testFetchProductById() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) @@ -66,7 +68,8 @@ final class ProductServiceTests: XCTestCase { XCTAssertEqual(product.name, "Cappuccino", "Product ID should match") } - func testFetchAllProducts() async { + func testFetchAllProducts() async throws { + try skipUnlessAPITestsEnabled() let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI) diff --git a/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift b/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift index 8c3428e..3ca56ed 100644 --- a/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift +++ b/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift @@ -16,6 +16,7 @@ import ImageKit @MainActor final class WebsocketConnectionTest: XCTestCase { func testWebsocketConnectionOrderStatus() async throws { + try skipUnlessAPITestsEnabled() let url = URL(string: "ws://127.0.0.1:8080/test/order/status/123")! print("Connecting to WebSocket at '\(url)'...") let connection = try WebsocketConnection(url: url) diff --git a/Tests/Coffee-KitTests/XCTestCase+APITests.swift b/Tests/Coffee-KitTests/XCTestCase+APITests.swift new file mode 100644 index 0000000..e0125e8 --- /dev/null +++ b/Tests/Coffee-KitTests/XCTestCase+APITests.swift @@ -0,0 +1,19 @@ +// +// XCTestCase+APITests.swift +// Coffee-Kit +// + +import XCTest + +extension XCTestCase { + /// Returns true if API-dependent tests should be run. + /// This is controlled by the environment variable `RUN_API_TESTS=1`. + var shouldRunAPITests: Bool { + ProcessInfo.processInfo.environment["RUN_API_TESTS"] == "1" + } + + /// Skips the test unless API-dependent tests are enabled. + func skipUnlessAPITestsEnabled() throws { + try XCTSkipUnless(shouldRunAPITests, "Skipping API-dependent test. Set RUN_API_TESTS=1 to enable.") + } +} From 1b37df3804133dfa51c084c55076e35c6be2fed6 Mon Sep 17 00:00:00 2001 From: Christoph Rohde Date: Mon, 4 May 2026 20:43:05 +0200 Subject: [PATCH 12/12] Skip api dependent test if there not available --- Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift b/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift index 6fb4211..35eb34d 100644 --- a/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift +++ b/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift @@ -16,7 +16,9 @@ import ImageKit @MainActor final class MenuManagerTests: XCTestCase { - func testItemSequence() { + func testItemSequence() throws { + try skipUnlessAPITestsEnabled() + let keychain = DefaultKeychainManager() let databaseAPI: DatabaseAPI = .dev let authenticationManager = AutenticationManager(keychain: keychain, databaseAPI: databaseAPI)