mirror of
https://github.com/yattee/yattee.git
synced 2026-02-19 17:29:45 +00:00
305 lines
12 KiB
Swift
305 lines
12 KiB
Swift
//
|
|
// NetworkingTests.swift
|
|
// YatteeTests
|
|
//
|
|
// Tests for networking layer components.
|
|
//
|
|
|
|
import Testing
|
|
import Foundation
|
|
@testable import Yattee
|
|
|
|
// MARK: - Endpoint Tests
|
|
|
|
@Suite("Endpoint Tests")
|
|
@MainActor
|
|
struct EndpointTests {
|
|
|
|
@Test("GET endpoint construction")
|
|
func getEndpoint() {
|
|
let endpoint = GenericEndpoint.get("/api/v1/videos")
|
|
#expect(endpoint.path == "/api/v1/videos")
|
|
#expect(endpoint.method == .get)
|
|
#expect(endpoint.queryItems == nil)
|
|
#expect(endpoint.body == nil)
|
|
}
|
|
|
|
@Test("GET endpoint with query parameters")
|
|
func getEndpointWithQuery() {
|
|
let endpoint = GenericEndpoint.get("/api/v1/search", query: [
|
|
"q": "test",
|
|
"page": "1"
|
|
])
|
|
#expect(endpoint.path == "/api/v1/search")
|
|
#expect(endpoint.queryItems?.count == 2)
|
|
|
|
let queryDict = Dictionary(uniqueKeysWithValues: endpoint.queryItems!.map { ($0.name, $0.value) })
|
|
#expect(queryDict["q"] == "test")
|
|
#expect(queryDict["page"] == "1")
|
|
}
|
|
|
|
@Test("POST endpoint with body")
|
|
func postEndpointWithBody() throws {
|
|
struct TestBody: Encodable {
|
|
let name: String
|
|
}
|
|
let body = TestBody(name: "test")
|
|
let endpoint = GenericEndpoint.post("/api/v1/create", body: body)
|
|
#expect(endpoint.path == "/api/v1/create")
|
|
#expect(endpoint.method == .post)
|
|
#expect(endpoint.body != nil)
|
|
}
|
|
|
|
@Test("Generic endpoint with custom timeout")
|
|
func endpointWithTimeout() {
|
|
let endpoint = GenericEndpoint(path: "/slow", timeout: 60)
|
|
#expect(endpoint.timeout == 60)
|
|
}
|
|
|
|
@Test("Default endpoint timeout is 30 seconds")
|
|
func defaultTimeout() {
|
|
let endpoint = GenericEndpoint.get("/fast")
|
|
#expect(endpoint.timeout == 30)
|
|
}
|
|
}
|
|
|
|
// MARK: - APIError Tests
|
|
|
|
@Suite("APIError Tests")
|
|
@MainActor
|
|
struct APIErrorTests {
|
|
|
|
@Test("APIError descriptions")
|
|
func errorDescriptions() {
|
|
let invalidURL = APIError.invalidURL
|
|
#expect(invalidURL.localizedDescription.contains("URL"))
|
|
|
|
let httpError = APIError.httpError(statusCode: 404, message: nil)
|
|
#expect(httpError.localizedDescription.contains("404"))
|
|
|
|
let timeout = APIError.timeout
|
|
#expect(timeout.localizedDescription.contains("timed out"))
|
|
|
|
let notFound = APIError.notFound(nil)
|
|
#expect(notFound.localizedDescription.contains("not found"))
|
|
}
|
|
|
|
@Test("All simple error descriptions")
|
|
func allSimpleErrorDescriptions() {
|
|
#expect(APIError.invalidURL.errorDescription == "Invalid URL")
|
|
#expect(APIError.timeout.errorDescription == "Request timed out")
|
|
#expect(APIError.noConnection.errorDescription == "No network connection")
|
|
#expect(APIError.cancelled.errorDescription == "Request was cancelled")
|
|
#expect(APIError.unauthorized.errorDescription == "Authentication required")
|
|
#expect(APIError.notFound(nil).errorDescription == "Resource not found")
|
|
#expect(APIError.commentsDisabled.errorDescription == "Comments are disabled")
|
|
#expect(APIError.noInstance.errorDescription == "No suitable instance available")
|
|
#expect(APIError.noStreams.errorDescription == "No playable streams available")
|
|
#expect(APIError.invalidRequest.errorDescription == "Invalid request")
|
|
}
|
|
|
|
@Test("Decoding error description")
|
|
func decodingErrorDescription() {
|
|
let error = APIError.decodingError("Missing key 'title'")
|
|
#expect(error.errorDescription?.contains("Missing key 'title'") == true)
|
|
}
|
|
|
|
@Test("Server error description")
|
|
func serverErrorDescription() {
|
|
let error = APIError.serverError("Internal server error")
|
|
#expect(error.errorDescription?.contains("Internal server error") == true)
|
|
}
|
|
|
|
@Test("Rate limited description with retry after")
|
|
func rateLimitedWithRetry() {
|
|
let error = APIError.rateLimited(retryAfter: 60)
|
|
#expect(error.errorDescription?.contains("60") == true)
|
|
}
|
|
|
|
@Test("Rate limited description without retry after")
|
|
func rateLimitedWithoutRetry() {
|
|
let error = APIError.rateLimited(retryAfter: nil)
|
|
#expect(error.errorDescription == "Rate limited")
|
|
}
|
|
|
|
@Test("Unknown error description")
|
|
func unknownErrorDescription() {
|
|
let error = APIError.unknown("Something went wrong")
|
|
#expect(error.errorDescription == "Something went wrong")
|
|
}
|
|
|
|
@Test("APIError equality")
|
|
func errorEquality() {
|
|
#expect(APIError.invalidURL == APIError.invalidURL)
|
|
#expect(APIError.httpError(statusCode: 404, message: nil) == APIError.httpError(statusCode: 404, message: nil))
|
|
#expect(APIError.httpError(statusCode: 404, message: nil) != APIError.httpError(statusCode: 500, message: nil))
|
|
#expect(APIError.timeout == APIError.timeout)
|
|
#expect(APIError.notFound(nil) == APIError.notFound(nil))
|
|
#expect(APIError.unauthorized == APIError.unauthorized)
|
|
}
|
|
|
|
@Test("APIError equality for parameterized errors")
|
|
func parameterizedErrorEquality() {
|
|
#expect(APIError.decodingError("msg") == APIError.decodingError("msg"))
|
|
#expect(APIError.decodingError("msg1") != APIError.decodingError("msg2"))
|
|
|
|
#expect(APIError.serverError("msg") == APIError.serverError("msg"))
|
|
#expect(APIError.serverError("msg1") != APIError.serverError("msg2"))
|
|
|
|
#expect(APIError.rateLimited(retryAfter: 30) == APIError.rateLimited(retryAfter: 30))
|
|
#expect(APIError.rateLimited(retryAfter: nil) == APIError.rateLimited(retryAfter: nil))
|
|
#expect(APIError.rateLimited(retryAfter: 30) != APIError.rateLimited(retryAfter: 60))
|
|
|
|
#expect(APIError.unknown("msg") == APIError.unknown("msg"))
|
|
#expect(APIError.unknown("msg1") != APIError.unknown("msg2"))
|
|
|
|
#expect(APIError.notFound(nil) == APIError.notFound(nil))
|
|
#expect(APIError.notFound("detail") == APIError.notFound("detail"))
|
|
#expect(APIError.notFound("detail1") != APIError.notFound("detail2"))
|
|
#expect(APIError.notFound(nil) != APIError.notFound("detail"))
|
|
}
|
|
|
|
@Test("notFound error with detail message")
|
|
func notFoundWithDetail() {
|
|
let noDetail = APIError.notFound(nil)
|
|
#expect(noDetail.errorDescription == "Resource not found")
|
|
|
|
let withDetail = APIError.notFound("Video not found: This live event will begin in 11 days.")
|
|
#expect(withDetail.errorDescription == "Video not found: This live event will begin in 11 days.")
|
|
}
|
|
|
|
@Test("Different error types are not equal")
|
|
func differentTypesNotEqual() {
|
|
#expect(APIError.invalidURL != APIError.timeout)
|
|
#expect(APIError.notFound(nil) != APIError.unauthorized)
|
|
#expect(APIError.commentsDisabled != APIError.noStreams)
|
|
}
|
|
|
|
@Test("APIError isRetryable")
|
|
func retryableErrors() {
|
|
#expect(APIError.timeout.isRetryable == true)
|
|
#expect(APIError.noConnection.isRetryable == true)
|
|
#expect(APIError.rateLimited(retryAfter: 60).isRetryable == true)
|
|
#expect(APIError.httpError(statusCode: 500, message: nil).isRetryable == true)
|
|
#expect(APIError.httpError(statusCode: 429, message: nil).isRetryable == true)
|
|
|
|
#expect(APIError.invalidURL.isRetryable == false)
|
|
#expect(APIError.notFound(nil).isRetryable == false)
|
|
#expect(APIError.unauthorized.isRetryable == false)
|
|
#expect(APIError.httpError(statusCode: 400, message: nil).isRetryable == false)
|
|
}
|
|
|
|
@Test("All non-retryable errors")
|
|
func allNonRetryableErrors() {
|
|
#expect(APIError.invalidURL.isRetryable == false)
|
|
#expect(APIError.decodingError("").isRetryable == false)
|
|
#expect(APIError.cancelled.isRetryable == false)
|
|
#expect(APIError.serverError("").isRetryable == false)
|
|
#expect(APIError.unauthorized.isRetryable == false)
|
|
#expect(APIError.notFound(nil).isRetryable == false)
|
|
#expect(APIError.commentsDisabled.isRetryable == false)
|
|
#expect(APIError.noInstance.isRetryable == false)
|
|
#expect(APIError.noStreams.isRetryable == false)
|
|
#expect(APIError.invalidRequest.isRetryable == false)
|
|
#expect(APIError.unknown("").isRetryable == false)
|
|
}
|
|
|
|
@Test("Server errors (5xx) are retryable")
|
|
func serverErrorsRetryable() {
|
|
#expect(APIError.httpError(statusCode: 500, message: nil).isRetryable == true)
|
|
#expect(APIError.httpError(statusCode: 502, message: nil).isRetryable == true)
|
|
#expect(APIError.httpError(statusCode: 503, message: nil).isRetryable == true)
|
|
#expect(APIError.httpError(statusCode: 504, message: nil).isRetryable == true)
|
|
}
|
|
|
|
@Test("Decoding error from Swift DecodingError types")
|
|
func decodingErrorFactory() {
|
|
// Test typeMismatch
|
|
let typeMismatchContext = DecodingError.Context(codingPath: [], debugDescription: "Expected String")
|
|
let typeMismatch = DecodingError.typeMismatch(String.self, typeMismatchContext)
|
|
let apiError1 = APIError.decodingError(typeMismatch)
|
|
#expect(apiError1.errorDescription?.contains("Type mismatch") == true)
|
|
|
|
// Test valueNotFound
|
|
let valueNotFoundContext = DecodingError.Context(codingPath: [], debugDescription: "No value")
|
|
let valueNotFound = DecodingError.valueNotFound(Int.self, valueNotFoundContext)
|
|
let apiError2 = APIError.decodingError(valueNotFound)
|
|
#expect(apiError2.errorDescription?.contains("Value not found") == true)
|
|
|
|
// Test dataCorrupted
|
|
let dataCorruptedContext = DecodingError.Context(codingPath: [], debugDescription: "Corrupted")
|
|
let dataCorrupted = DecodingError.dataCorrupted(dataCorruptedContext)
|
|
let apiError3 = APIError.decodingError(dataCorrupted)
|
|
#expect(apiError3.errorDescription?.contains("Data corrupted") == true)
|
|
}
|
|
}
|
|
|
|
// MARK: - URL Building Tests
|
|
|
|
@Suite("URL Building Tests")
|
|
@MainActor
|
|
struct URLBuildingTests {
|
|
|
|
@Test("Build URL from base and endpoint")
|
|
func buildURL() throws {
|
|
let baseURL = URL(string: "https://api.example.com")!
|
|
let endpoint = GenericEndpoint.get("/v1/videos")
|
|
|
|
let request = try endpoint.urlRequest(baseURL: baseURL)
|
|
#expect(request.url?.absoluteString == "https://api.example.com/v1/videos")
|
|
}
|
|
|
|
@Test("Build URL with query parameters")
|
|
func buildURLWithQuery() throws {
|
|
let baseURL = URL(string: "https://api.example.com")!
|
|
let endpoint = GenericEndpoint.get("/search", query: [
|
|
"q": "hello world",
|
|
"limit": "10"
|
|
])
|
|
|
|
let request = try endpoint.urlRequest(baseURL: baseURL)
|
|
let urlString = request.url?.absoluteString ?? ""
|
|
|
|
#expect(urlString.contains("q=hello%20world"))
|
|
#expect(urlString.contains("limit=10"))
|
|
}
|
|
|
|
@Test("URLRequest has correct method")
|
|
func requestMethod() throws {
|
|
let baseURL = URL(string: "https://api.example.com")!
|
|
|
|
let getEndpoint = GenericEndpoint.get("/resource")
|
|
let getRequest = try getEndpoint.urlRequest(baseURL: baseURL)
|
|
#expect(getRequest.httpMethod == "GET")
|
|
|
|
let postEndpoint = GenericEndpoint.post("/resource", body: ["key": "value"])
|
|
let postRequest = try postEndpoint.urlRequest(baseURL: baseURL)
|
|
#expect(postRequest.httpMethod == "POST")
|
|
}
|
|
|
|
@Test("URLRequest has JSON Accept header")
|
|
func acceptHeader() throws {
|
|
let baseURL = URL(string: "https://api.example.com")!
|
|
let endpoint = GenericEndpoint.get("/resource")
|
|
let request = try endpoint.urlRequest(baseURL: baseURL)
|
|
|
|
#expect(request.value(forHTTPHeaderField: "Accept") == "application/json")
|
|
}
|
|
}
|
|
|
|
// MARK: - HTTPMethod Tests
|
|
|
|
@Suite("HTTPMethod Tests")
|
|
@MainActor
|
|
struct HTTPMethodTests {
|
|
|
|
@Test("HTTPMethod raw values")
|
|
func rawValues() {
|
|
#expect(HTTPMethod.get.rawValue == "GET")
|
|
#expect(HTTPMethod.post.rawValue == "POST")
|
|
#expect(HTTPMethod.put.rawValue == "PUT")
|
|
#expect(HTTPMethod.patch.rawValue == "PATCH")
|
|
#expect(HTTPMethod.delete.rawValue == "DELETE")
|
|
}
|
|
}
|