diff --git a/.github/workflows/main-safety.yml b/.github/workflows/main-safety.yml index 42df5f0..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: "5.10.0" - + swift-version: "6.2.0" + - name: Get swift version run: swift --version - + - name: Build run: swift build -v diff --git a/Package.resolved b/Package.resolved index ce0c505..0368453 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,22 +1,13 @@ { - "originHash" : "1c76daf8512f795aeae2b70e86a3eb59775052746b038b6f4ae27b0a910cdbc9", + "originHash" : "a48f514a2d67223cc528eabf3c815eb59669fa7009d38ebd8fa8e40a7002daed", "pins" : [ { "identity" : "harmonize", "kind" : "remoteSourceControl", "location" : "https://github.com/perrystreetsoftware/Harmonize.git", "state" : { - "revision" : "e8d20b57d4b77bdc80e6fbf2ac182fc704771050", - "version" : "0.1.0" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections.git", - "state" : { - "revision" : "6675bc0ff86e61436e615df6fc5174e043e57924", - "version" : "1.4.1" + "revision" : "1279fbf4d87c9b9bd230e91911194145959e6d54", + "version" : "0.9.0" } }, { @@ -24,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 cda276b..792f55c 100644 --- a/Package.swift +++ b/Package.swift @@ -10,29 +10,48 @@ let package = Package( .macOS(.v14), ], 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( 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"), @@ -41,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"), @@ -59,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/Authentication-Kit/AutenticationManager.swift b/Sources/AuthenticationKit/AutenticationManager.swift similarity index 84% rename from Sources/Authentication-Kit/AutenticationManager.swift rename to Sources/AuthenticationKit/AutenticationManager.swift index e9347ec..890e025 100644 --- a/Sources/Authentication-Kit/AutenticationManager.swift +++ b/Sources/AuthenticationKit/AutenticationManager.swift @@ -1,5 +1,5 @@ - import Foundation +import FoundationKit public actor AutenticationManager { @@ -9,15 +9,12 @@ public actor AutenticationManager { 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) { + public init(keychain: KeychainService, databaseAPI: DatabaseAPI) { self.keychain = keychain - self.baseURL = baseURL + self.baseURL = databaseAPI.baseURL / "authentication" } public func getValidAccessToken() async throws -> String { @@ -26,14 +23,13 @@ public actor AutenticationManager { 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 { 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 { 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 { 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 { 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 { 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 { 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/Authentication-Kit/AuthenticationBuilder.swift b/Sources/AuthenticationKit/AuthenticationBuilder.swift similarity index 97% rename from Sources/Authentication-Kit/AuthenticationBuilder.swift rename to Sources/AuthenticationKit/AuthenticationBuilder.swift index 4194206..d03be3b 100644 --- a/Sources/Authentication-Kit/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 { diff --git a/Sources/Coffee-Kit/Webservice/DatabaseAPI.swift b/Sources/AuthenticationKit/DatabaseAPI.swift similarity index 100% rename from Sources/Coffee-Kit/Webservice/DatabaseAPI.swift rename to Sources/AuthenticationKit/DatabaseAPI.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 diff --git a/Sources/AuthenticationKit/WebserviceProvider.swift b/Sources/AuthenticationKit/WebserviceProvider.swift new file mode 100644 index 0000000..d6d1026 --- /dev/null +++ b/Sources/AuthenticationKit/WebserviceProvider.swift @@ -0,0 +1,21 @@ +// +// 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/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/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/WebserviceProvider.swift b/Sources/Coffee-Kit/Webservice/WebserviceProvider.swift deleted file mode 100644 index 2b5c6f7..0000000 --- a/Sources/Coffee-Kit/Webservice/WebserviceProvider.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// Webservice.swift -// Coffee Lover -// -// Created by Christoph Rohde on 20.10.24. -// - -import Foundation -import Authentication_Kit - -public struct WebserviceProvider{ - public let databaseAPI: DatabaseAPI - public let authManager: AutenticationManager? - - public init(inMode databaseAPI: consuming DatabaseAPI, authManager: AutenticationManager? = 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/Coffee-Kit/Webservice/Cache/Cache.swift b/Sources/FoundationKit/Cache/Cache.swift similarity index 99% rename from Sources/Coffee-Kit/Webservice/Cache/Cache.swift rename to Sources/FoundationKit/Cache/Cache.swift index da05ec5..c435dc5 100644 --- a/Sources/Coffee-Kit/Webservice/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 { diff --git a/Sources/Coffee-Kit/Extensions/LoggerExtension.swift b/Sources/FoundationKit/Extensions/LoggerExtension.swift similarity index 100% rename from Sources/Coffee-Kit/Extensions/LoggerExtension.swift rename to Sources/FoundationKit/Extensions/LoggerExtension.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/Services/WebsocketConnection.swift b/Sources/FoundationKit/WebsocketConnection.swift similarity index 99% rename from Sources/Coffee-Kit/Webservice/Services/WebsocketConnection.swift rename to Sources/FoundationKit/WebsocketConnection.swift index 62da679..cbc3f2c 100644 --- a/Sources/Coffee-Kit/Webservice/Services/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/Coffee-Kit/Webservice/Manager/ImageManager.swift b/Sources/ImageKit/ImageManager.swift similarity index 95% rename from Sources/Coffee-Kit/Webservice/Manager/ImageManager.swift rename to Sources/ImageKit/ImageManager.swift index 9549db3..6f2fb80 100644 --- a/Sources/Coffee-Kit/Webservice/Manager/ImageManager.swift +++ b/Sources/ImageKit/ImageManager.swift @@ -6,6 +6,8 @@ // import Foundation +import AuthenticationKit +import ProductKit @Observable public final class ImageManager { // MARK: - Properties diff --git a/Sources/Coffee-Kit/Webservice/Services/ImageService.swift b/Sources/ImageKit/ImageService.swift similarity index 81% rename from Sources/Coffee-Kit/Webservice/Services/ImageService.swift rename to Sources/ImageKit/ImageService.swift index 5135e7c..41abb1f 100644 --- a/Sources/Coffee-Kit/Webservice/Services/ImageService.swift +++ b/Sources/ImageKit/ImageService.swift @@ -6,6 +6,9 @@ // import Foundation +import FoundationKit +import AuthenticationKit +import ProductKit public enum ImageServiceError: Error { case imageNotFound @@ -15,7 +18,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 +49,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/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 99% rename from Sources/Coffee-Kit/DAO/Order/Order.swift rename to Sources/OrderKit/Order/Order.swift index 5117b33..2de73a0 100644 --- a/Sources/Coffee-Kit/DAO/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/Coffee-Kit/DAO/Order/OrderBuilder.swift b/Sources/OrderKit/Order/OrderBuilder.swift similarity index 99% rename from Sources/Coffee-Kit/DAO/Order/OrderBuilder.swift rename to Sources/OrderKit/Order/OrderBuilder.swift index 3c8aaac..ae8c071 100644 --- a/Sources/Coffee-Kit/DAO/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/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 98% rename from Sources/Coffee-Kit/DAO/Order/OrderProduct.swift rename to Sources/OrderKit/Order/OrderProduct.swift index 75639a1..72a27f6 100644 --- a/Sources/Coffee-Kit/DAO/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/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 96% rename from Sources/Coffee-Kit/Webservice/Manager/OrderManager.swift rename to Sources/OrderKit/OrderManager.swift index 04dc8c2..5747195 100644 --- a/Sources/Coffee-Kit/Webservice/Manager/OrderManager.swift +++ b/Sources/OrderKit/OrderManager.swift @@ -7,6 +7,9 @@ import Foundation import OSLog +import FoundationKit +import AuthenticationKit +import ProductKit @Observable public final class OrderManager { @@ -23,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/Coffee-Kit/Webservice/Services/OrderService.swift b/Sources/OrderKit/OrderService.swift similarity index 85% rename from Sources/Coffee-Kit/Webservice/Services/OrderService.swift rename to Sources/OrderKit/OrderService.swift index 6bc3b98..e5b2383 100644 --- a/Sources/Coffee-Kit/Webservice/Services/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 @@ -15,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 - 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 @@ -38,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)") @@ -66,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) @@ -103,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/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/CoffeeColor.swift b/Sources/ProductKit/Enumerations/CoffeeColor.swift similarity index 100% rename from Sources/Coffee-Kit/Enumerations/CoffeeColor.swift rename to Sources/ProductKit/Enumerations/CoffeeColor.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 86% rename from Sources/Coffee-Kit/Webservice/Manager/MenuManager.swift rename to Sources/ProductKit/MenuManager.swift index 4c12262..5f0dd03 100644 --- a/Sources/Coffee-Kit/Webservice/Manager/MenuManager.swift +++ b/Sources/ProductKit/MenuManager.swift @@ -6,6 +6,7 @@ // import Foundation +import AuthenticationKit @Observable public final class MenuManager { @@ -19,14 +20,9 @@ import Foundation // 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/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 90% rename from Sources/Coffee-Kit/DAO/Products/Product.swift rename to Sources/ProductKit/Products/Product.swift index 9ae3950..fd18a4f 100644 --- a/Sources/Coffee-Kit/DAO/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 imageURL: URL) -> URL { + let newImageName = imageName.replacing(".png", with: ".heic") + return imageURL / category / newImageName } } diff --git a/Sources/Coffee-Kit/Webservice/Services/ProductService.swift b/Sources/ProductKit/Products/ProductService.swift similarity index 89% rename from Sources/Coffee-Kit/Webservice/Services/ProductService.swift rename to Sources/ProductKit/Products/ProductService.swift index 4b1a510..a9c0839 100644 --- a/Sources/Coffee-Kit/Webservice/Services/ProductService.swift +++ b/Sources/ProductKit/Products/ProductService.swift @@ -6,7 +6,8 @@ // import Foundation -import Authentication_Kit +import FoundationKit +import AuthenticationKit public struct ProductService { @@ -15,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) } @@ -35,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 { @@ -65,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/Tests/Coffee-KitTests/CacheTests/CacheTests.swift b/Tests/Coffee-KitTests/CacheTests/CacheTests.swift index fdba63c..3789a44 100644 --- a/Tests/Coffee-KitTests/CacheTests/CacheTests.swift +++ b/Tests/Coffee-KitTests/CacheTests/CacheTests.swift @@ -5,11 +5,16 @@ // Created by Christoph Rohde on 16.05.25. // -@testable import Coffee_Kit import Foundation import XCTest import OSLog +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit +@MainActor final class CacheTests: XCTestCase { private let log = Logger(subsystem: "Coffee-Kit Tests", category: "CacheTests") @@ -29,7 +34,12 @@ final class CacheTests: XCTestCase { // MARK: - Properties func testCaching() async throws { - let productService = await ProductService(databaseAPI: .dev) + try skipUnlessAPITestsEnabled() + 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" @@ -51,7 +61,12 @@ final class CacheTests: XCTestCase { } func testCacheMemoryLimit() async throws { - let productService = await ProductService(databaseAPI: .dev) + try skipUnlessAPITestsEnabled() + 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 @@ -70,7 +85,12 @@ final class CacheTests: XCTestCase { } func testCacheInitilisationWithKeyList() async throws { - let productService = await ProductService(databaseAPI: .dev) + try skipUnlessAPITestsEnabled() + 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 { @@ -90,8 +110,14 @@ final class CacheTests: XCTestCase { } func testDataCaching() async throws { - let imageService = await ImageService(databaseAPI: .dev) - let imageCache = await imageService.imageCache + try skipUnlessAPITestsEnabled() + 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) @@ -102,8 +128,13 @@ final class CacheTests: XCTestCase { } - func testFetchingPerformance() async { - let productService = await ProductService(databaseAPI: .dev) + func testFetchingPerformance() async throws { + try skipUnlessAPITestsEnabled() + 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/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 deleted file mode 100644 index 53b1db4..0000000 --- a/Tests/Coffee-KitTests/ExtensionsTests/LoggerExtensionTests.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// LoggerExtensionTests.swift -// Coffee-Kit -// -// Created by Christoph Rohde on 18.04.25. -// - -@testable import Coffee_Kit -import Foundation -import OSLog -import XCTest - -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/ManagerTests/MenuManagerTests.swift b/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift index 11ff42a..35eb34d 100644 --- a/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift +++ b/Tests/Coffee-KitTests/ManagerTests/MenuManagerTests.swift @@ -7,14 +7,23 @@ import Foundation import XCTest - -@testable import Coffee_Kit +import FoundationKit +import AuthenticationKit +import ProductKit +import OrderKit +import ImageKit @MainActor final class MenuManagerTests: XCTestCase { - let menuManager = MenuManager(from: WebserviceProvider(inMode: .dev)) - func testItemSequence() { + func testItemSequence() throws { + try skipUnlessAPITestsEnabled() + + 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/MemorySaftyTest/WeakSelfTests.swift b/Tests/Coffee-KitTests/MemorySaftyTest/WeakSelfTests.swift index b1ec5cd..e52e028 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() @@ -33,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 e2d7bb2..1c16197 100644 --- a/Tests/Coffee-KitTests/OrderTests.swift +++ b/Tests/Coffee-KitTests/OrderTests.swift @@ -5,37 +5,44 @@ // 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 { 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) + 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 } @@ -77,10 +84,18 @@ final class OrderTests: XCTestCase { } func testFetchOrderById() async throws { - let orderId = "059621D5-C191-45A9-AB5B-B4B414E9DB17" - let databaseAPI = DatabaseAPI.dev - let webservice = WebserviceProvider(inMode: databaseAPI) - let orderService = OrderService(databaseAPI: webservice.databaseAPI) + try skipUnlessAPITestsEnabled() + 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) @@ -89,9 +104,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.uuidString, 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 b9b0e4d..5ba01f1 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 { @@ -16,12 +19,20 @@ final class ProductServiceTests: XCTestCase { // MARK: - Properties #if DEBUG - let productService = ProductService(databaseAPI: .dev) - func testLoadAllIds() async { + + func testLoadAllIds() async throws { + try skipUnlessAPITestsEnabled() + 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() @@ -34,7 +45,14 @@ 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) + 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 @@ -50,7 +68,14 @@ 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) + let webserviceProvider = WebserviceProvider(inMode: databaseAPI, autheticationManager: authenticationManager) + let productService = ProductService(webserviceProvider: webserviceProvider) + guard let products = try? await productService .loadAll() .collect(into: [Result]()) diff --git a/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift b/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift index f9b48ae..3ca56ed 100644 --- a/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift +++ b/Tests/Coffee-KitTests/ServiceTests/WebsocketConnectionTest.swift @@ -5,13 +5,18 @@ // 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 { 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.") + } +}