this repo has no description
at oauth 190 lines 6.4 kB view raw
1// 2// ATErrorTests.swift 3// CoreATProtocol 4// 5// Created by Claude on 2026-01-02. 6// 7 8import Testing 9import Foundation 10@testable import CoreATProtocol 11 12@Suite("AT Error Tests") 13struct ATErrorTests { 14 15 // MARK: - ErrorMessage Tests 16 17 @Test("ErrorMessage parses from JSON") 18 func testErrorMessageParsing() throws { 19 let json = """ 20 { 21 "error": "ExpiredToken", 22 "message": "The access token has expired" 23 } 24 """.data(using: .utf8)! 25 26 let message = try JSONDecoder().decode(ErrorMessage.self, from: json) 27 28 #expect(message.error == "ExpiredToken") 29 #expect(message.message == "The access token has expired") 30 #expect(message.errorType == .expiredToken) 31 } 32 33 @Test("ErrorMessage handles unknown error types") 34 func testUnknownErrorType() throws { 35 let json = """ 36 { 37 "error": "SomeNewError", 38 "message": "An unknown error occurred" 39 } 40 """.data(using: .utf8)! 41 42 let message = try JSONDecoder().decode(ErrorMessage.self, from: json) 43 44 #expect(message.error == "SomeNewError") 45 #expect(message.errorType == nil) 46 } 47 48 @Test("ErrorMessage handles missing message field") 49 func testMissingMessage() throws { 50 let json = """ 51 { 52 "error": "InvalidRequest" 53 } 54 """.data(using: .utf8)! 55 56 let message = try JSONDecoder().decode(ErrorMessage.self, from: json) 57 58 #expect(message.error == "InvalidRequest") 59 #expect(message.message == nil) 60 } 61 62 // MARK: - AtErrorType Tests 63 64 @Test("All error types have descriptions") 65 func testErrorTypeDescriptions() { 66 for errorType in AtErrorType.allCases { 67 #expect(!errorType.description.isEmpty, "\(errorType) should have a description") 68 } 69 } 70 71 @Test("Error types decode correctly") 72 func testErrorTypeDecoding() throws { 73 let testCases: [(String, AtErrorType)] = [ 74 ("\"AuthenticationRequired\"", .authenticationRequired), 75 ("\"ExpiredToken\"", .expiredToken), 76 ("\"RateLimitExceeded\"", .rateLimitExceeded), 77 ("\"RecordNotFound\"", .recordNotFound), 78 ("\"BlobTooLarge\"", .blobTooLarge) 79 ] 80 81 for (json, expected) in testCases { 82 let data = json.data(using: .utf8)! 83 let decoded = try JSONDecoder().decode(AtErrorType.self, from: data) 84 #expect(decoded == expected) 85 } 86 } 87 88 // MARK: - AtError Tests 89 90 @Test("AtError.requiresReauthentication identifies auth errors") 91 func testRequiresReauthentication() { 92 let expiredTokenError = AtError.message(ErrorMessage(error: "ExpiredToken", message: nil)) 93 #expect(expiredTokenError.requiresReauthentication == true) 94 95 let authRequiredError = AtError.message(ErrorMessage(error: "AuthenticationRequired", message: nil)) 96 #expect(authRequiredError.requiresReauthentication == true) 97 98 let notFoundError = AtError.message(ErrorMessage(error: "NotFound", message: nil)) 99 #expect(notFoundError.requiresReauthentication == false) 100 101 let unauthorized = AtError.network(NetworkError.statusCode(.unauthorized, data: Data())) 102 #expect(unauthorized.requiresReauthentication == true) 103 104 let serverError = AtError.network(NetworkError.statusCode(.internalServerError, data: Data())) 105 #expect(serverError.requiresReauthentication == false) 106 } 107 108 @Test("AtError.isRetryable identifies retryable errors") 109 func testIsRetryable() { 110 let rateLimitError = AtError.message(ErrorMessage(error: "RateLimitExceeded", message: nil)) 111 #expect(rateLimitError.isRetryable == true) 112 113 let serverError = AtError.network(NetworkError.statusCode(.internalServerError, data: Data())) 114 #expect(serverError.isRetryable == true) 115 116 let badRequestError = AtError.network(NetworkError.statusCode(.badRequest, data: Data())) 117 #expect(badRequestError.isRetryable == false) 118 119 let notFoundError = AtError.message(ErrorMessage(error: "NotFound", message: nil)) 120 #expect(notFoundError.isRetryable == false) 121 } 122 123 // MARK: - RateLimitInfo Tests 124 125 @Test("RateLimitInfo parses from headers") 126 func testRateLimitParsing() { 127 // Create a mock response with rate limit headers 128 let url = URL(string: "https://example.com")! 129 let headers = [ 130 "RateLimit-Limit": "100", 131 "RateLimit-Remaining": "50", 132 "RateLimit-Reset": "1704067200" 133 ] 134 135 let response = HTTPURLResponse( 136 url: url, 137 statusCode: 200, 138 httpVersion: nil, 139 headerFields: headers 140 )! 141 142 let rateLimitInfo = RateLimitInfo.from(response: response) 143 144 #expect(rateLimitInfo != nil) 145 #expect(rateLimitInfo?.limit == 100) 146 #expect(rateLimitInfo?.remaining == 50) 147 #expect(rateLimitInfo?.resetTimestamp == 1704067200) 148 } 149 150 @Test("RateLimitInfo returns nil for missing headers") 151 func testRateLimitMissingHeaders() { 152 let url = URL(string: "https://example.com")! 153 let response = HTTPURLResponse( 154 url: url, 155 statusCode: 200, 156 httpVersion: nil, 157 headerFields: [:] 158 )! 159 160 let rateLimitInfo = RateLimitInfo.from(response: response) 161 #expect(rateLimitInfo == nil) 162 } 163 164 @Test("RateLimitInfo calculates time until reset") 165 func testTimeUntilReset() { 166 let futureReset = Date().timeIntervalSince1970 + 300 // 5 minutes from now 167 let info = RateLimitInfo(limit: 100, remaining: 0, resetTimestamp: futureReset) 168 169 #expect(info.timeUntilReset > 0) 170 #expect(info.timeUntilReset <= 300) 171 } 172 173 // MARK: - OAuthError Tests 174 175 @Test("OAuthError has localized descriptions") 176 func testOAuthErrorDescriptions() { 177 let errors: [OAuthError] = [ 178 .accessTokenExpired, 179 .refreshTokenMissing, 180 .dpopRequired, 181 .storageFailed(reason: "Test reason"), 182 .subjectMismatch(expected: "did:plc:a", received: "did:plc:b") 183 ] 184 185 for error in errors { 186 #expect(error.errorDescription != nil, "\(error) should have a description") 187 #expect(!error.errorDescription!.isEmpty) 188 } 189 } 190}