GRPCWebToHTTP2StateMachineTests.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. /*
  2. * Copyright 2021, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. @testable import GRPC
  17. import NIO
  18. import NIOHPACK
  19. import NIOHTTP1
  20. import NIOHTTP2
  21. import XCTest
  22. final class GRPCWebToHTTP2StateMachineTests: GRPCTestCase {
  23. fileprivate typealias StateMachine = GRPCWebToHTTP2ServerCodec.StateMachine
  24. private let allocator = ByteBufferAllocator()
  25. private func makeStateMachine(scheme: String = "http") -> StateMachine {
  26. return StateMachine(scheme: scheme)
  27. }
  28. private func makeRequestHead(
  29. version: HTTPVersion = .http1_1,
  30. method: HTTPMethod = .POST,
  31. uri: String,
  32. headers: HTTPHeaders = [:]
  33. ) -> HTTPServerRequestPart {
  34. return .head(.init(version: version, method: method, uri: uri, headers: headers))
  35. }
  36. // MARK: - grpc-web
  37. func test_gRPCWeb_requestHeaders() {
  38. var state = self.makeStateMachine(scheme: "http")
  39. let head = self.makeRequestHead(method: .POST, uri: "foo", headers: ["host": "localhost"])
  40. let action = state.processInbound(serverRequestPart: head, allocator: self.allocator)
  41. action.assertRead { payload in
  42. payload.assertHeaders { payload in
  43. XCTAssertFalse(payload.endStream)
  44. XCTAssertEqual(payload.headers[canonicalForm: ":path"], ["foo"])
  45. XCTAssertEqual(payload.headers[canonicalForm: ":method"], ["POST"])
  46. XCTAssertEqual(payload.headers[canonicalForm: ":scheme"], ["http"])
  47. XCTAssertEqual(payload.headers[canonicalForm: ":authority"], ["localhost"])
  48. }
  49. }
  50. }
  51. func test_gRPCWeb_requestBody() {
  52. var state = self.makeStateMachine()
  53. let head = self.makeRequestHead(
  54. uri: "foo",
  55. headers: ["content-type": "application/grpc-web"]
  56. )
  57. state.processInbound(serverRequestPart: head, allocator: self.allocator).assertRead {
  58. $0.assertHeaders()
  59. }
  60. let b1 = ByteBuffer(string: "hello")
  61. for _ in 0 ..< 5 {
  62. state.processInbound(serverRequestPart: .body(b1), allocator: self.allocator).assertRead {
  63. $0.assertData {
  64. XCTAssertFalse($0.endStream)
  65. $0.data.assertByteBuffer { buffer in
  66. var buffer = buffer
  67. XCTAssertEqual(buffer.readString(length: buffer.readableBytes), "hello")
  68. }
  69. }
  70. }
  71. }
  72. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead {
  73. $0.assertEmptyDataWithEndStream()
  74. }
  75. }
  76. private func checkResponseHeaders(
  77. from state: StateMachine,
  78. expectConnectionCloseHeader: Bool,
  79. line: UInt = #line
  80. ) {
  81. var state = state
  82. state.processOutbound(
  83. framePayload: .headers(.init(headers: [":status": "200"])),
  84. promise: nil,
  85. allocator: self.allocator
  86. ).assertWrite { write in
  87. write.part.assertHead {
  88. XCTAssertEqual($0.status, .ok, line: line)
  89. XCTAssertFalse($0.headers.contains(name: ":status"), line: line)
  90. if expectConnectionCloseHeader {
  91. XCTAssertEqual($0.headers[canonicalForm: "connection"], ["close"], line: line)
  92. } else {
  93. XCTAssertFalse($0.headers.contains(name: "connection"), line: line)
  94. }
  95. }
  96. XCTAssertNil(write.additionalPart, line: line)
  97. XCTAssertFalse(write.closeChannel, line: line)
  98. }
  99. }
  100. func test_gRPCWeb_responseHeaders() {
  101. for connectionClose in [true, false] {
  102. let headers: HTTPHeaders = connectionClose ? ["connection": "close"] : [:]
  103. let requestHead = self.makeRequestHead(uri: "/echo", headers: headers)
  104. var state = self.makeStateMachine()
  105. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  106. self.checkResponseHeaders(from: state, expectConnectionCloseHeader: connectionClose)
  107. // Do it again with the request stream closed.
  108. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  109. self.checkResponseHeaders(from: state, expectConnectionCloseHeader: connectionClose)
  110. }
  111. }
  112. private func checkTrailersOnlyResponse(
  113. from state: StateMachine,
  114. expectConnectionCloseHeader: Bool,
  115. line: UInt = #line
  116. ) {
  117. var state = state
  118. state.processOutbound(
  119. framePayload: .headers(.init(headers: [":status": "415"], endStream: true)),
  120. promise: nil,
  121. allocator: self.allocator
  122. ).assertWrite { write in
  123. write.part.assertHead {
  124. XCTAssertEqual($0.status, .unsupportedMediaType, line: line)
  125. XCTAssertFalse($0.headers.contains(name: ":status"), line: line)
  126. if expectConnectionCloseHeader {
  127. XCTAssertEqual($0.headers[canonicalForm: "connection"], ["close"], line: line)
  128. } else {
  129. XCTAssertFalse($0.headers.contains(name: "connection"), line: line)
  130. }
  131. }
  132. // Should also send end.
  133. write.additionalPart.assertSome { $0.assertEnd() }
  134. XCTAssertEqual(write.closeChannel, expectConnectionCloseHeader, line: line)
  135. }
  136. }
  137. func test_gRPCWeb_responseTrailersOnly() {
  138. for connectionClose in [true, false] {
  139. let headers: HTTPHeaders = connectionClose ? ["connection": "close"] : [:]
  140. let requestHead = self.makeRequestHead(uri: "/echo", headers: headers)
  141. var state = self.makeStateMachine()
  142. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  143. self.checkTrailersOnlyResponse(from: state, expectConnectionCloseHeader: connectionClose)
  144. // Do it again with the request stream closed.
  145. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  146. self.checkTrailersOnlyResponse(from: state, expectConnectionCloseHeader: connectionClose)
  147. }
  148. }
  149. private func checkGRPCWebResponseData(from state: StateMachine, line: UInt = #line) {
  150. var state = state
  151. for i in 0 ..< 10 {
  152. let buffer = ByteBuffer(string: "foo-\(i)")
  153. state.processOutbound(
  154. framePayload: .data(.init(data: .byteBuffer(buffer))),
  155. promise: nil,
  156. allocator: self.allocator
  157. ).assertWrite { write in
  158. write.part.assertBody {
  159. XCTAssertEqual($0, buffer, line: line)
  160. }
  161. XCTAssertNil(write.additionalPart, line: line)
  162. XCTAssertFalse(write.closeChannel, line: line)
  163. }
  164. }
  165. }
  166. func test_gRPCWeb_responseData() {
  167. var state = self.makeStateMachine()
  168. let requestHead = self.makeRequestHead(
  169. uri: "/echo",
  170. headers: ["content-type": "application/grpc-web"]
  171. )
  172. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  173. state.processOutbound(
  174. framePayload: .headers(.init(headers: [":status": "200"])),
  175. promise: nil,
  176. allocator: self.allocator
  177. ).assertWrite()
  178. // Request stream is open.
  179. self.checkGRPCWebResponseData(from: state)
  180. // Close request stream and test again.
  181. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  182. self.checkGRPCWebResponseData(from: state)
  183. }
  184. private func checkGRPCWebResponseTrailers(
  185. from state: StateMachine,
  186. expectChannelClose: Bool,
  187. line: UInt = #line
  188. ) {
  189. var state = state
  190. state.processOutbound(
  191. framePayload: .headers(.init(headers: ["grpc-status": "0"], endStream: true)),
  192. promise: nil,
  193. allocator: self.allocator
  194. ).assertWrite { write in
  195. write.part.assertEnd {
  196. $0.assertSome { trailers in
  197. XCTAssertEqual(trailers[canonicalForm: "grpc-status"], ["0"])
  198. }
  199. }
  200. XCTAssertEqual(write.closeChannel, expectChannelClose)
  201. }
  202. }
  203. func test_gRPCWeb_responseTrailers() {
  204. for connectionClose in [true, false] {
  205. let headers: HTTPHeaders = connectionClose ? ["connection": "close"] : [:]
  206. let requestHead = self.makeRequestHead(uri: "/echo", headers: headers)
  207. var state = self.makeStateMachine()
  208. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  209. state.processOutbound(
  210. framePayload: .headers(.init(headers: [":status": "200"])),
  211. promise: nil,
  212. allocator: self.allocator
  213. ).assertWrite()
  214. // Request stream is open.
  215. self.checkGRPCWebResponseTrailers(from: state, expectChannelClose: connectionClose)
  216. // Check again with request stream closed.
  217. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  218. self.checkGRPCWebResponseTrailers(from: state, expectChannelClose: connectionClose)
  219. }
  220. }
  221. // MARK: - grpc-web-text
  222. func test_gRPCWebText_requestBody() {
  223. var state = self.makeStateMachine()
  224. let head = self.makeRequestHead(
  225. uri: "foo",
  226. headers: ["content-type": "application/grpc-web-text"]
  227. )
  228. state.processInbound(serverRequestPart: head, allocator: self.allocator).assertRead {
  229. $0.assertHeaders()
  230. }
  231. let expected = ["hel", "lo"]
  232. let buffers = [ByteBuffer(string: "aGVsb"), ByteBuffer(string: "G8=")]
  233. for (buffer, expected) in zip(buffers, expected) {
  234. state.processInbound(serverRequestPart: .body(buffer), allocator: self.allocator).assertRead {
  235. $0.assertData {
  236. XCTAssertFalse($0.endStream)
  237. $0.data.assertByteBuffer { buffer in
  238. var buffer = buffer
  239. XCTAssertEqual(buffer.readString(length: buffer.readableBytes), expected)
  240. }
  241. }
  242. }
  243. }
  244. // If there's not enough to decode, there's nothing to do.
  245. let buffer = ByteBuffer(string: "a")
  246. state.processInbound(serverRequestPart: .body(buffer), allocator: self.allocator).assertNone()
  247. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead {
  248. $0.assertEmptyDataWithEndStream()
  249. }
  250. }
  251. private func checkResponseDataAndTrailersForGRPCWebText(
  252. from state: StateMachine,
  253. line: UInt = #line
  254. ) {
  255. var state = state
  256. state.processOutbound(
  257. framePayload: .headers(.init(headers: [":status": "200"])),
  258. promise: nil,
  259. allocator: self.allocator
  260. ).assertWrite()
  261. // Write some bytes.
  262. for text in ["hello", ", world!"] {
  263. let buffer = ByteBuffer(string: text)
  264. state.processOutbound(
  265. framePayload: .data(.init(data: .byteBuffer(buffer))),
  266. promise: nil,
  267. allocator: self.allocator
  268. ).assertCompletePromise { error in
  269. XCTAssertNil(error)
  270. }
  271. }
  272. state.processOutbound(
  273. framePayload: .headers(.init(headers: ["grpc-status": "0"], endStream: true)),
  274. promise: nil,
  275. allocator: self.allocator
  276. ).assertWrite { write in
  277. // The response is encoded by:
  278. // - accumulating the bytes of request messages (these would normally be gRPC length prefixed
  279. // messages)
  280. // - appending a 'trailers' byte (0x80)
  281. // - appending the UInt32 length of the trailers when encoded as HTTP/1 header lines
  282. // - the encoded headers
  283. write.part.assertBody { buffer in
  284. var buffer = buffer
  285. let base64Encoded = buffer.readString(length: buffer.readableBytes)!
  286. XCTAssertEqual(base64Encoded, "aGVsbG8sIHdvcmxkIYAAAAAOZ3JwYy1zdGF0dXM6IDA=")
  287. let data = Data(base64Encoded: base64Encoded)!
  288. buffer.writeData(data)
  289. XCTAssertEqual(buffer.readString(length: 13), "hello, world!")
  290. XCTAssertEqual(buffer.readInteger(), UInt8(0x80))
  291. XCTAssertEqual(buffer.readInteger(), UInt32(14))
  292. XCTAssertEqual(buffer.readString(length: 14), "grpc-status: 0")
  293. XCTAssertEqual(buffer.readableBytes, 0)
  294. }
  295. // There should be an end now.
  296. write.additionalPart.assertSome { $0.assertEnd() }
  297. XCTAssertFalse(write.closeChannel)
  298. }
  299. }
  300. func test_gRPCWebText_responseDataAndTrailers() {
  301. var state = self.makeStateMachine()
  302. let requestHead = self.makeRequestHead(
  303. uri: "/echo",
  304. headers: ["content-type": "application/grpc-web-text"]
  305. )
  306. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  307. // Request stream is still open.
  308. self.checkResponseDataAndTrailersForGRPCWebText(from: state)
  309. // Check again with request stream closed.
  310. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertRead()
  311. self.checkResponseDataAndTrailersForGRPCWebText(from: state)
  312. }
  313. // MARK: - General
  314. func test_requestPartsAfterServerClosed() {
  315. var state = self.makeStateMachine()
  316. let requestHead = self.makeRequestHead(uri: "/echo")
  317. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  318. // Close the response stream.
  319. state.processOutbound(
  320. framePayload: .headers(.init(headers: [":status": "415"], endStream: true)),
  321. promise: nil,
  322. allocator: self.allocator
  323. ).assertWrite()
  324. state.processInbound(serverRequestPart: .body(.init()), allocator: self.allocator).assertNone()
  325. state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator).assertNone()
  326. }
  327. func test_responsePartsAfterServerClosed() {
  328. var state = self.makeStateMachine()
  329. let requestHead = self.makeRequestHead(uri: "/echo")
  330. state.processInbound(serverRequestPart: requestHead, allocator: self.allocator).assertRead()
  331. // Close the response stream.
  332. state.processOutbound(
  333. framePayload: .headers(.init(headers: [":status": "415"], endStream: true)),
  334. promise: nil,
  335. allocator: self.allocator
  336. ).assertWrite()
  337. // More writes should be told to fail their promise.
  338. state.processOutbound(
  339. framePayload: .headers(.init(headers: .init())), promise: nil, allocator: self.allocator
  340. ).assertCompletePromise { error in
  341. XCTAssertNotNil(error)
  342. }
  343. state.processOutbound(
  344. framePayload: .data(.init(data: .byteBuffer(.init()))),
  345. promise: nil,
  346. allocator: self.allocator
  347. ).assertCompletePromise { error in
  348. XCTAssertNotNil(error)
  349. }
  350. }
  351. func test_handleMultipleRequests() {
  352. func sendRequestHead(_ state: inout StateMachine, contentType: ContentType) -> StateMachine
  353. .Action {
  354. let requestHead = self.makeRequestHead(
  355. uri: "/echo", headers: ["content-type": contentType.canonicalValue]
  356. )
  357. return state.processInbound(serverRequestPart: requestHead, allocator: self.allocator)
  358. }
  359. func sendRequestBody(_ state: inout StateMachine, buffer: ByteBuffer) -> StateMachine.Action {
  360. return state.processInbound(serverRequestPart: .body(buffer), allocator: self.allocator)
  361. }
  362. func sendRequestEnd(_ state: inout StateMachine) -> StateMachine.Action {
  363. return state.processInbound(serverRequestPart: .end(nil), allocator: self.allocator)
  364. }
  365. func sendResponseHeaders(
  366. _ state: inout StateMachine,
  367. headers: HPACKHeaders,
  368. endStream: Bool = false
  369. ) -> StateMachine.Action {
  370. return state.processOutbound(
  371. framePayload: .headers(.init(headers: headers, endStream: endStream)),
  372. promise: nil,
  373. allocator: self.allocator
  374. )
  375. }
  376. func sendResponseData(
  377. _ state: inout StateMachine,
  378. buffer: ByteBuffer
  379. ) -> StateMachine.Action {
  380. return state.processOutbound(
  381. framePayload: .data(.init(data: .byteBuffer(buffer))),
  382. promise: nil,
  383. allocator: self.allocator
  384. )
  385. }
  386. var state = self.makeStateMachine()
  387. // gRPC-Web, all request parts then all response parts.
  388. sendRequestHead(&state, contentType: .webProtobuf).assertRead()
  389. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  390. sendRequestEnd(&state).assertRead()
  391. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  392. sendResponseData(&state, buffer: .init(string: "bye")).assertWrite()
  393. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  394. // gRPC-Web text, all requests then all response parts.
  395. sendRequestHead(&state, contentType: .webTextProtobuf).assertRead()
  396. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  397. sendRequestEnd(&state).assertRead()
  398. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  399. // nothing; buffered and sent with end.
  400. sendResponseData(&state, buffer: .init(string: "bye")).assertCompletePromise()
  401. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  402. // gRPC-Web, interleaving
  403. sendRequestHead(&state, contentType: .webProtobuf).assertRead()
  404. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  405. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  406. sendResponseData(&state, buffer: .init(string: "bye")).assertWrite()
  407. sendRequestEnd(&state).assertRead()
  408. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  409. // gRPC-Web text, interleaving
  410. sendRequestHead(&state, contentType: .webTextProtobuf).assertRead()
  411. sendResponseHeaders(&state, headers: [":status": "200"]).assertWrite()
  412. sendRequestBody(&state, buffer: .init(string: "hello")).assertRead()
  413. sendResponseData(&state, buffer: .init(string: "bye")).assertCompletePromise()
  414. sendRequestEnd(&state).assertRead()
  415. sendResponseHeaders(&state, headers: ["grpc-status": "0"], endStream: true).assertWrite()
  416. // gRPC-Web, server closes immediately.
  417. sendRequestHead(&state, contentType: .webProtobuf).assertRead()
  418. sendResponseHeaders(&state, headers: [":status": "415"], endStream: true).assertWrite()
  419. sendRequestBody(&state, buffer: .init(string: "hello")).assertNone()
  420. sendRequestEnd(&state).assertNone()
  421. // gRPC-Web text, server closes immediately.
  422. sendRequestHead(&state, contentType: .webTextProtobuf).assertRead()
  423. sendResponseHeaders(&state, headers: [":status": "415"], endStream: true).assertWrite()
  424. sendRequestBody(&state, buffer: .init(string: "hello")).assertNone()
  425. sendRequestEnd(&state).assertNone()
  426. }
  427. }
  428. // MARK: - Assertions
  429. extension GRPCWebToHTTP2ServerCodec.StateMachine.Action {
  430. func assertRead(
  431. file: StaticString = #file,
  432. line: UInt = #line,
  433. verify: (HTTP2Frame.FramePayload) -> Void = { _ in }
  434. ) {
  435. if case let .fireChannelRead(payload) = self {
  436. verify(payload)
  437. } else {
  438. XCTFail("Expected '.fireChannelRead' but got '\(self)'", file: file, line: line)
  439. }
  440. }
  441. func assertWrite(
  442. file: StaticString = #file,
  443. line: UInt = #line,
  444. verify: (Write) -> Void = { _ in }
  445. ) {
  446. if case let .write(write) = self {
  447. verify(write)
  448. } else {
  449. XCTFail("Expected '.write' but got '\(self)'", file: file, line: line)
  450. }
  451. }
  452. func assertCompletePromise(
  453. file: StaticString = #file,
  454. line: UInt = #line,
  455. verify: (Error?) -> Void = { _ in }
  456. ) {
  457. if case let .completePromise(_, result) = self {
  458. do {
  459. try result.get()
  460. verify(nil)
  461. } catch {
  462. verify(error)
  463. }
  464. } else {
  465. XCTFail("Expected '.completePromise' but got '\(self)'", file: file, line: line)
  466. }
  467. }
  468. func assertNone(
  469. file: StaticString = #file,
  470. line: UInt = #line
  471. ) {
  472. if case .none = self {
  473. ()
  474. } else {
  475. XCTFail("Expected '.none' but got '\(self)'", file: file, line: line)
  476. }
  477. }
  478. }
  479. extension HTTP2Frame.FramePayload {
  480. func assertHeaders(
  481. file: StaticString = #file,
  482. line: UInt = #line,
  483. verify: (Headers) -> Void = { _ in }
  484. ) {
  485. if case let .headers(headers) = self {
  486. verify(headers)
  487. } else {
  488. XCTFail("Expected '.headers' but got '\(self)'", file: file, line: line)
  489. }
  490. }
  491. func assertData(
  492. file: StaticString = #file,
  493. line: UInt = #line,
  494. verify: (Data) -> Void = { _ in }
  495. ) {
  496. if case let .data(data) = self {
  497. verify(data)
  498. } else {
  499. XCTFail("Expected '.data' but got '\(self)'", file: file, line: line)
  500. }
  501. }
  502. func assertEmptyDataWithEndStream(
  503. file: StaticString = #file,
  504. line: UInt = #line
  505. ) {
  506. self.assertData(file: file, line: line) {
  507. XCTAssertTrue($0.endStream)
  508. $0.data.assertByteBuffer { buffer in
  509. XCTAssertEqual(buffer.readableBytes, 0)
  510. }
  511. }
  512. }
  513. }
  514. extension HTTPServerResponsePart {
  515. func assertHead(
  516. file: StaticString = #file,
  517. line: UInt = #line,
  518. verify: (HTTPResponseHead) -> Void = { _ in }
  519. ) {
  520. if case let .head(head) = self {
  521. verify(head)
  522. } else {
  523. XCTFail("Expected '.head' but got '\(self)'", file: file, line: line)
  524. }
  525. }
  526. func assertBody(
  527. file: StaticString = #file,
  528. line: UInt = #line,
  529. verify: (ByteBuffer) -> Void = { _ in }
  530. ) {
  531. if case let .body(.byteBuffer(buffer)) = self {
  532. verify(buffer)
  533. } else {
  534. XCTFail("Expected '.body(.byteBuffer)' but got '\(self)'", file: file, line: line)
  535. }
  536. }
  537. func assertEnd(
  538. file: StaticString = #file,
  539. line: UInt = #line,
  540. verify: (HTTPHeaders?) -> Void = { _ in }
  541. ) {
  542. if case let .end(trailers) = self {
  543. verify(trailers)
  544. } else {
  545. XCTFail("Expected '.end' but got '\(self)'", file: file, line: line)
  546. }
  547. }
  548. }
  549. extension IOData {
  550. func assertByteBuffer(
  551. file: StaticString = #file,
  552. line: UInt = #line,
  553. verify: (ByteBuffer) -> Void = { _ in }
  554. ) {
  555. if case let .byteBuffer(buffer) = self {
  556. verify(buffer)
  557. } else {
  558. XCTFail("Expected '.byteBuffer' but got '\(self)'", file: file, line: line)
  559. }
  560. }
  561. }
  562. extension Optional {
  563. func assertSome(
  564. file: StaticString = #file,
  565. line: UInt = #line,
  566. verify: (Wrapped) -> Void = { _ in }
  567. ) {
  568. switch self {
  569. case let .some(wrapped):
  570. verify(wrapped)
  571. case .none:
  572. XCTFail("Expected '.some' but got 'nil'", file: file, line: line)
  573. }
  574. }
  575. }