crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

NYTPuzzleFetcherTests.swift (2219B)


      1 import Foundation
      2 import Testing
      3 
      4 @testable import Crossmate
      5 
      6 /// Pins down the response bounds on the NYT download path: the byte budget
      7 /// applied while streaming, the `Content-Length` preflight, and the
      8 /// content-type gate.
      9 @Suite("NYTPuzzleFetcher limits")
     10 struct NYTPuzzleFetcherTests {
     11 
     12     private func byteStream(_ data: Data) -> AsyncStream<UInt8> {
     13         AsyncStream { continuation in
     14             for byte in data { continuation.yield(byte) }
     15             continuation.finish()
     16         }
     17     }
     18 
     19     @Test("A body exactly at the byte budget is accepted")
     20     func bodyAtLimitIsAccepted() async throws {
     21         let data = try await NYTPuzzleFetcher.boundedResponseData(
     22             from: byteStream(Data(count: 100)),
     23             limit: 100,
     24             expectedLength: -1
     25         )
     26         #expect(data.count == 100)
     27     }
     28 
     29     @Test("A body over the byte budget is rejected mid-stream")
     30     func bodyOverLimitIsRejected() async {
     31         await #expect(throws: NYTFetchError.self) {
     32             _ = try await NYTPuzzleFetcher.boundedResponseData(
     33                 from: byteStream(Data(count: 101)),
     34                 limit: 100,
     35                 expectedLength: -1
     36             )
     37         }
     38     }
     39 
     40     @Test("An oversized Content-Length is rejected before reading")
     41     func oversizedContentLengthIsRejected() async {
     42         await #expect(throws: NYTFetchError.self) {
     43             _ = try await NYTPuzzleFetcher.boundedResponseData(
     44                 from: byteStream(Data()),
     45                 limit: 100,
     46                 expectedLength: 101
     47             )
     48         }
     49     }
     50 
     51     @Test("JSON content types pass; others are rejected; missing is allowed")
     52     func contentTypeGate() {
     53         #expect(NYTPuzzleFetcher.isJSONContentType(nil))
     54         #expect(NYTPuzzleFetcher.isJSONContentType("application/json"))
     55         #expect(NYTPuzzleFetcher.isJSONContentType("application/json; charset=utf-8"))
     56         #expect(NYTPuzzleFetcher.isJSONContentType("application/vnd.api+json"))
     57         #expect(!NYTPuzzleFetcher.isJSONContentType("text/html"))
     58         #expect(!NYTPuzzleFetcher.isJSONContentType("text/html; charset=utf-8"))
     59         #expect(!NYTPuzzleFetcher.isJSONContentType("application/octet-stream"))
     60     }
     61 }