init
This commit is contained in:
253
Tests/NatsTests/Unit/ParserTests.swift
Normal file
253
Tests/NatsTests/Unit/ParserTests.swift
Normal file
@@ -0,0 +1,253 @@
|
||||
// Copyright 2024 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import Nats
|
||||
|
||||
class ParserTests: XCTestCase {
|
||||
|
||||
static var allTests = [
|
||||
("testParseOutMessages", testParseOutMessages)
|
||||
]
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testParseOutMessages() {
|
||||
struct TestCase {
|
||||
let name: String
|
||||
let givenChunks: [String]
|
||||
let expectedOps: [ServerOp]
|
||||
}
|
||||
|
||||
let fail: ((Int, String) -> String) = { index, name in
|
||||
return "Test case: \(index)\n Input: \(name)"
|
||||
}
|
||||
var hm = NatsHeaderMap()
|
||||
hm.append(try! NatsHeaderName("h1"), NatsHeaderValue("X"))
|
||||
hm.append(try! NatsHeaderName("h1"), NatsHeaderValue("Y"))
|
||||
hm.append(try! NatsHeaderName("h2"), NatsHeaderValue("Z"))
|
||||
|
||||
let testCases = [
|
||||
TestCase(
|
||||
name: "Single chunk, different operations",
|
||||
givenChunks: ["MSG foo 1 5\r\nhello\r\n+OK\r\nPONG\r\n"],
|
||||
expectedOps: [
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "hello".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
.ok,
|
||||
.pong,
|
||||
]
|
||||
),
|
||||
TestCase(
|
||||
name: "Chunked messages",
|
||||
givenChunks: [
|
||||
"MSG foo 1 5\r\nhello\r\nMSG foo 1 5\r\nwo",
|
||||
"rld\r\nMSG f",
|
||||
"oo 1 5\r\nhello\r\nMSG foo 1 5\r\nworld",
|
||||
"\r\nMSG foo 1 5\r\nhello\r",
|
||||
"\nMSG foo 1 5\r\nworld\r\nMSG foo 1 5\r\n",
|
||||
"hello\r\n",
|
||||
],
|
||||
expectedOps: [
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "hello".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "world".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "hello".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "world".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "hello".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "world".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "hello".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
]
|
||||
),
|
||||
TestCase(
|
||||
name: "Message with headers only",
|
||||
givenChunks: [
|
||||
"HMSG foo 1 30 30\r\nNATS/1.0\r\nh1:X\r\nh1:Y\r\nh2:Z\r\n\r\n\r\n"
|
||||
],
|
||||
expectedOps: [
|
||||
.hMessage(
|
||||
HMessageInbound(
|
||||
subject: "foo", sid: 1, payload: nil, headers: hm, headersLength: 30,
|
||||
length: 30)
|
||||
)
|
||||
]
|
||||
),
|
||||
TestCase(
|
||||
name: "Message with headers and payload",
|
||||
givenChunks: [
|
||||
"HMSG foo 1 30 35\r\nNATS/1.0\r\nh1:X\r\nh1:Y\r\nh2:Z\r\n\r\nhello\r\n"
|
||||
],
|
||||
expectedOps: [
|
||||
.hMessage(
|
||||
HMessageInbound(
|
||||
subject: "foo", sid: 1, payload: "hello".data(using: .utf8)!,
|
||||
headers: hm, headersLength: 30, length: 35)
|
||||
)
|
||||
]
|
||||
),
|
||||
TestCase(
|
||||
name: "Message with status and no other headers",
|
||||
givenChunks: [
|
||||
"HMSG foo 1 30 30\r\nNATS/1.0 503 no responders\r\n\r\n\r\n"
|
||||
],
|
||||
expectedOps: [
|
||||
.hMessage(
|
||||
HMessageInbound(
|
||||
subject: "foo", sid: 1, payload: nil, headers: NatsHeaderMap(),
|
||||
headersLength: 30, length: 30, status: StatusCode.noResponders,
|
||||
description: "no responders"
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
TestCase(
|
||||
name: "Message with status, headers and payload",
|
||||
givenChunks: [
|
||||
"HMSG foo 1 48 53\r\nNATS/1.0 503 no responders\r\nh1:X\r\nh1:Y\r\nh2:Z\r\n\r\nhello\r\n"
|
||||
],
|
||||
expectedOps: [
|
||||
.hMessage(
|
||||
HMessageInbound(
|
||||
subject: "foo", sid: 1, payload: "hello".data(using: .utf8)!,
|
||||
headers: hm, headersLength: 48, length: 53,
|
||||
status: StatusCode.noResponders,
|
||||
description: "no responders")
|
||||
)
|
||||
]
|
||||
),
|
||||
TestCase(
|
||||
name: "With empty lines",
|
||||
givenChunks: [
|
||||
"MSG foo 1 5\r\nhello\r\nMSG foo 1 5\r\nwo",
|
||||
"",
|
||||
"",
|
||||
"rld\r\nMSG f",
|
||||
"oo 1 5\r\nhello\r\n",
|
||||
],
|
||||
expectedOps: [
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "hello".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "world".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: "hello".data(using: .utf8)!, length: 5)
|
||||
),
|
||||
]
|
||||
),
|
||||
TestCase(
|
||||
name: "With crlf in payload",
|
||||
givenChunks: [
|
||||
"MSG foo 1 7\r\nhe\r\nllo\r\n"
|
||||
],
|
||||
expectedOps: [
|
||||
.message(
|
||||
MessageInbound(
|
||||
subject: "foo", sid: 1, payload: Data("he\r\nllo".utf8), length: 7))
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
for (tn, tc) in testCases.enumerated() {
|
||||
logger.logLevel = .critical
|
||||
var ops = [ServerOp]()
|
||||
var prevRemainder: Data?
|
||||
for chunk in tc.givenChunks {
|
||||
var chunkData = Data(chunk.utf8)
|
||||
if let prevRemainder {
|
||||
chunkData.prepend(prevRemainder)
|
||||
}
|
||||
let res = try! chunkData.parseOutMessages()
|
||||
prevRemainder = res.remainder
|
||||
ops.append(contentsOf: res.ops)
|
||||
}
|
||||
XCTAssertEqual(ops.count, tc.expectedOps.count)
|
||||
for (i, op) in ops.enumerated() {
|
||||
switch op {
|
||||
case .ok:
|
||||
if case .ok = tc.expectedOps[i] {
|
||||
} else {
|
||||
XCTFail(fail(tn, tc.name))
|
||||
}
|
||||
case .info(let info):
|
||||
if case .info(let expectedInfo) = tc.expectedOps[i] {
|
||||
XCTAssertEqual(info, expectedInfo, fail(tn, tc.name))
|
||||
} else {
|
||||
XCTFail(fail(tn, tc.name))
|
||||
}
|
||||
|
||||
case .ping:
|
||||
if case .ping = tc.expectedOps[i] {
|
||||
} else {
|
||||
XCTFail(fail(tn, tc.name))
|
||||
}
|
||||
case .pong:
|
||||
if case .pong = tc.expectedOps[i] {
|
||||
} else {
|
||||
XCTFail(fail(tn, tc.name))
|
||||
}
|
||||
case .error(_):
|
||||
if case .error(_) = tc.expectedOps[i] {
|
||||
} else {
|
||||
XCTFail(fail(tn, tc.name))
|
||||
}
|
||||
case .message(let msg):
|
||||
if case .message(let expectedMessage) = tc.expectedOps[i] {
|
||||
XCTAssertEqual(msg, expectedMessage, fail(tn, tc.name))
|
||||
} else {
|
||||
XCTFail(fail(tn, tc.name))
|
||||
}
|
||||
case .hMessage(let msg):
|
||||
if case .hMessage(let expectedMessage) = tc.expectedOps[i] {
|
||||
XCTAssertEqual(msg, expectedMessage, fail(tn, tc.name))
|
||||
} else {
|
||||
XCTFail(fail(tn, tc.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user