import Testing import Foundation @testable import Mobile_Music_Assistant // MARK: - ConnectionState @Suite("MAWebSocketClient – ConnectionState") struct ConnectionStateTests { @Test("Fresh client starts disconnected") func startsDisconnected() { let client = MAWebSocketClient() if case .disconnected = client.connectionState { // pass } else { Issue.record("Expected .disconnected, got \(client.connectionState)") } } @Test("isConnected is false when disconnected") func isConnectedFalseWhenDisconnected() { let client = MAWebSocketClient() #expect(client.isConnected == false) } @Test("ConnectionState descriptions are distinct") func statesAreDistinct() { // Regression: ensure no two states resolve to the same string representation let states: [MAWebSocketClient.ConnectionState] = [ .disconnected, .connecting, .connected, .reconnecting(attempt: 1), .reconnecting(attempt: 2) ] let descriptions = states.map { "\($0)" } let unique = Set(descriptions) #expect(unique.count == descriptions.count) } } // MARK: - Reconnection Backoff @Suite("MAWebSocketClient – Reconnection Backoff") struct ReconnectionBackoffTests { /// Replicates the exact formula used in MAWebSocketClient. private func backoffDelay(attempt: Int) -> Double { let base = 3.0 let maxDelay = 30.0 return min(base * pow(2.0, Double(attempt - 1)), maxDelay) } @Test("Attempt 1 gives 3 seconds") func attempt1() { #expect(backoffDelay(attempt: 1) == 3.0) } @Test("Attempt 2 gives 6 seconds") func attempt2() { #expect(backoffDelay(attempt: 2) == 6.0) } @Test("Attempt 3 gives 12 seconds") func attempt3() { #expect(backoffDelay(attempt: 3) == 12.0) } @Test("Attempt 4 gives 24 seconds") func attempt4() { #expect(backoffDelay(attempt: 4) == 24.0) } @Test("Attempt 5 is capped at 30 seconds") func attempt5Capped() { #expect(backoffDelay(attempt: 5) == 30.0) } @Test("High attempt numbers stay capped at 30 seconds") func highAttemptStaysCapped() { for attempt in 6...20 { #expect(backoffDelay(attempt: attempt) == 30.0) } } @Test("Backoff is strictly increasing up to the cap") func strictlyIncreasing() { var previous = 0.0 for attempt in 1...5 { let delay = backoffDelay(attempt: attempt) #expect(delay > previous) previous = delay } } } // MARK: - AnyCodable @Suite("AnyCodable") struct AnyCodableTests { @Test("Bool encodes and decodes correctly") func boolRoundTrip() throws { let encoded = try JSONEncoder().encode(AnyCodable(true)) let decoded = try JSONDecoder().decode(AnyCodable.self, from: encoded) #expect(try decoded.decode(as: Bool.self) == true) } @Test("Int encodes and decodes correctly") func intRoundTrip() throws { let encoded = try JSONEncoder().encode(AnyCodable(42)) let decoded = try JSONDecoder().decode(AnyCodable.self, from: encoded) #expect(try decoded.decode(as: Int.self) == 42) } @Test("String encodes and decodes correctly") func stringRoundTrip() throws { let encoded = try JSONEncoder().encode(AnyCodable("hello")) let decoded = try JSONDecoder().decode(AnyCodable.self, from: encoded) #expect(try decoded.decode(as: String.self) == "hello") } @Test("Double encodes and decodes correctly") func doubleRoundTrip() throws { let encoded = try JSONEncoder().encode(AnyCodable(3.14)) let decoded = try JSONDecoder().decode(AnyCodable.self, from: encoded) let value = try decoded.decode(as: Double.self) #expect(abs(value - 3.14) < 0.001) } @Test("Array encodes and decodes correctly") func arrayRoundTrip() throws { let arr = AnyCodable(["a", "b", "c"]) let encoded = try JSONEncoder().encode(arr) let decoded = try JSONDecoder().decode(AnyCodable.self, from: encoded) let values = try decoded.decode(as: [AnyCodable].self) #expect(values.count == 3) } @Test("decode(as:) throws for wrong type") func decodeThrowsForWrongType() throws { let encoded = try JSONEncoder().encode(AnyCodable("text")) let decoded = try JSONDecoder().decode(AnyCodable.self, from: encoded) #expect(throws: (any Error).self) { try decoded.decode(as: Int.self) } } @Test("Dictionary decodes keys correctly") func dictRoundTrip() throws { // Build the AnyCodable from raw JSON to avoid double-wrapping issues let json = #"{"key":"value"}"# let decoded = try JSONDecoder().decode(AnyCodable.self, from: Data(json.utf8)) let map = try decoded.decode(as: [String: AnyCodable].self) #expect(try map["key"]?.decode(as: String.self) == "value") } }