ForgejoKit/Tests/ForgejoKitTests/ErrorMappingTests.swift
2026-06-15 12:06:27 +02:00

134 lines
5.9 KiB
Swift

@testable import ForgejoKit
import Testing
struct ErrorMappingTests {
@Test func classifiesHTTPStatusCodes() {
#expect(HTTPErrorCategory(statusCode: 401) == .authentication)
#expect(HTTPErrorCategory(statusCode: 403) == .permissionDenied)
#expect(HTTPErrorCategory(statusCode: 404) == .notFound)
#expect(HTTPErrorCategory(statusCode: 500) == .server)
#expect(HTTPErrorCategory(statusCode: 503) == .server)
#expect(HTTPErrorCategory(statusCode: 400) == .other)
}
@Test func exposesServiceErrorStatusAndCategory() {
let forbidden = ServiceError.httpError(statusCode: 403, message: "missing scope")
#expect(forbidden.httpStatusCode == 403)
#expect(forbidden.httpErrorCategory == .permissionDenied)
let serverError = ServiceError.httpError(statusCode: 502)
#expect(serverError.httpStatusCode == 502)
#expect(serverError.httpErrorCategory == .server)
#expect(ServiceError.invalidURL.httpStatusCode == nil)
#expect(ServiceError.invalidURL.httpErrorCategory == nil)
}
@Test func mapsAuthenticationStatusCodes() {
#expect(AuthenticationError.from(statusCode: 401) == .invalidCredentials)
#expect(AuthenticationError.from(statusCode: 401, body: "OTP required") == .otpRequired)
#expect(AuthenticationError.from(statusCode: 403) == .unknownError(statusCode: 403))
#expect(AuthenticationError.from(statusCode: 404) == .serverNotFound)
#expect(AuthenticationError.from(statusCode: 500) == .unknownError(statusCode: 500))
#expect(AuthenticationError.from(statusCode: 418) == .unknownError(statusCode: 418))
}
@Test func exposesAuthenticationStatusAndCategory() {
#expect(AuthenticationError.invalidCredentials.httpStatusCode == 401)
#expect(AuthenticationError.invalidCredentials.httpErrorCategory == .authentication)
#expect(AuthenticationError.serverNotFound.httpStatusCode == 404)
#expect(AuthenticationError.serverNotFound.httpErrorCategory == .notFound)
#expect(AuthenticationError.unknownError(statusCode: 403).httpStatusCode == 403)
#expect(AuthenticationError.unknownError(statusCode: 403).httpErrorCategory == .permissionDenied)
#expect(AuthenticationError.unknownError(statusCode: 502).httpErrorCategory == .server)
#expect(AuthenticationError.certificateError.httpStatusCode == nil)
#expect(AuthenticationError.certificateError.httpErrorCategory == nil)
}
// MARK: - ServiceError descriptions
@Test func serviceErrorDescriptions() {
#expect(ServiceError.noActiveInstance.errorDescription == "No active Forgejo instance")
#expect(ServiceError.invalidURL.errorDescription == "Invalid URL")
#expect(ServiceError.invalidResponse.errorDescription == "Invalid response from server")
#expect(ServiceError.httpError(statusCode: 404).errorDescription == "HTTP error: 404")
#expect(ServiceError.httpError(statusCode: 500).errorDescription == "HTTP error: 500")
#expect(ServiceError.decodingFailed(detail: "missing key").errorDescription == "Decoding failed: missing key")
}
@Test func httpErrorDescriptionUsesForgejoMessageBody() {
let error = ServiceError.httpError(
statusCode: 405,
message: #"{"message":"This pull request has failing status checks"}"#,
)
#expect(error.errorDescription == "HTTP 405: This pull request has failing status checks")
}
@Test func httpErrorDescriptionFallsBackToRawBody() {
let error = ServiceError.httpError(statusCode: 500, message: "database unavailable")
#expect(error.errorDescription == "HTTP 500: database unavailable")
}
@Test func httpErrorDescriptionIgnoresWhitespaceOnlyMessage() {
let error = ServiceError.httpError(statusCode: 503, message: " \n ")
#expect(error.errorDescription == "HTTP error: 503")
}
@Test func httpErrorDescriptionFallsBackWhenJSONMessageFieldIsEmpty() {
let error = ServiceError.httpError(
statusCode: 422,
message: #"{"message":" "}"#,
)
#expect(error.errorDescription == "HTTP error: 422")
}
@Test func httpErrorDescriptionReturnsRawJSONWhenMessageFieldMissing() {
let raw = #"{"errors":["bad request"]}"#
let error = ServiceError.httpError(statusCode: 400, message: raw)
#expect(error.errorDescription == "HTTP 400: \(raw)")
}
// MARK: - Merge error collapsing
@Test func collapseMergeErrorMaps405WithEmptyBodyToNotMergeable() {
let collapsed = PullRequestService.collapseMergeError(
.httpError(statusCode: 405, message: ""),
)
#expect(collapsed == .notMergeable)
}
@Test func collapseMergeErrorMaps409WithEmptyBodyToMergeConflict() {
let collapsed = PullRequestService.collapseMergeError(
.httpError(statusCode: 409, message: " \n "),
)
#expect(collapsed == .mergeConflict)
}
@Test func collapseMergeErrorPreserves405WithBody() {
let original = ServiceError.httpError(
statusCode: 405,
message: #"{"message":"failing status checks"}"#,
)
#expect(PullRequestService.collapseMergeError(original) == original)
}
@Test func collapseMergeErrorPreserves409WithBody() {
let original = ServiceError.httpError(statusCode: 409, message: "conflict in README.md")
#expect(PullRequestService.collapseMergeError(original) == original)
}
@Test func collapseMergeErrorPassesOtherStatusCodesThrough() {
let original = ServiceError.httpError(statusCode: 500, message: "")
#expect(PullRequestService.collapseMergeError(original) == original)
}
@Test func collapseMergeErrorPassesNonHTTPErrorsThrough() {
#expect(PullRequestService.collapseMergeError(.invalidURL) == .invalidURL)
#expect(PullRequestService.collapseMergeError(.noActiveInstance) == .noActiveInstance)
}
}