init
This commit is contained in:
42
Tests/NatsTests/Unit/ErrorsTests.swift
Normal file
42
Tests/NatsTests/Unit/ErrorsTests.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
// 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 ErrorsTests: XCTestCase {
|
||||
|
||||
static var allTests = [
|
||||
("testServerErrorPermissionsDenied", testServerErrorPermissionsDenied)
|
||||
]
|
||||
|
||||
func testServerErrorPermissionsDenied() {
|
||||
var err = NatsError.ServerError(
|
||||
"Permissions Violation for Subscription to \"events.A.B.*\"]")
|
||||
XCTAssertEqual(
|
||||
err, NatsError.ServerError.permissionsViolation(.subscribe, "events.A.B.*", nil))
|
||||
|
||||
err = NatsError.ServerError("Permissions Violation for Publish to \"events.A.B.*\"")
|
||||
XCTAssertEqual(
|
||||
err, NatsError.ServerError.permissionsViolation(.publish, "events.A.B.*", nil))
|
||||
|
||||
err = NatsError.ServerError(
|
||||
"Permissions Violation for Publish to \"events.A.B.*\" using queue \"q\"")
|
||||
XCTAssertEqual(
|
||||
err, NatsError.ServerError.permissionsViolation(.publish, "events.A.B.*", "q"))
|
||||
|
||||
err = NatsError.ServerError("Some other error")
|
||||
XCTAssertEqual(err, NatsError.ServerError.proto("Some other error"))
|
||||
}
|
||||
}
|
||||
97
Tests/NatsTests/Unit/HeadersTests.swift
Normal file
97
Tests/NatsTests/Unit/HeadersTests.swift
Normal file
@@ -0,0 +1,97 @@
|
||||
// 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 HeadersTests: XCTestCase {
|
||||
|
||||
static var allTests = [
|
||||
("testAppend", testAppend),
|
||||
("testSubscript", testSubscript),
|
||||
("testInsert", testInsert),
|
||||
("testSerialize", testSerialize),
|
||||
("testValidNatsHeaderName", testValidNatsHeaderName),
|
||||
("testDollarNatsHeaderName", testDollarNatsHeaderName),
|
||||
("testInvalidNatsHeaderName", testInvalidNatsHeaderName),
|
||||
(
|
||||
"testInvalidNatsHeaderNameWithSpecialCharacters",
|
||||
testInvalidNatsHeaderNameWithSpecialCharacters
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
func testAppend() {
|
||||
var hm = NatsHeaderMap()
|
||||
hm.append(try! NatsHeaderName("foo"), NatsHeaderValue("bar"))
|
||||
hm.append(try! NatsHeaderName("foo"), NatsHeaderValue("baz"))
|
||||
XCTAssertEqual(
|
||||
hm.getAll(try! NatsHeaderName("foo")), [NatsHeaderValue("bar"), NatsHeaderValue("baz")])
|
||||
}
|
||||
|
||||
func testInsert() {
|
||||
var hm = NatsHeaderMap()
|
||||
hm.insert(try! NatsHeaderName("foo"), NatsHeaderValue("bar"))
|
||||
XCTAssertEqual(hm.getAll(try! NatsHeaderName("foo")), [NatsHeaderValue("bar")])
|
||||
}
|
||||
|
||||
func testSerialize() {
|
||||
var hm = NatsHeaderMap()
|
||||
hm.append(try! NatsHeaderName("foo"), NatsHeaderValue("bar"))
|
||||
hm.append(try! NatsHeaderName("foo"), NatsHeaderValue("baz"))
|
||||
hm.insert(try! NatsHeaderName("bar"), NatsHeaderValue("foo"))
|
||||
|
||||
let expected = [
|
||||
"NATS/1.0\r\nfoo:bar\r\nfoo:baz\r\nbar:foo\r\n\r\n",
|
||||
"NATS/1.0\r\nbar:foo\r\nfoo:bar\r\nfoo:baz\r\n\r\n",
|
||||
]
|
||||
|
||||
XCTAssertTrue(expected.contains(String(bytes: hm.toBytes(), encoding: .utf8)!))
|
||||
}
|
||||
|
||||
func testValidNatsHeaderName() {
|
||||
XCTAssertNoThrow(try NatsHeaderName("X-Custom-Header"))
|
||||
}
|
||||
|
||||
func testDollarNatsHeaderName() {
|
||||
XCTAssertNoThrow(try NatsHeaderName("$Dollar"))
|
||||
}
|
||||
|
||||
func testInvalidNatsHeaderName() {
|
||||
XCTAssertThrowsError(try NatsHeaderName("Invalid Header Name"))
|
||||
}
|
||||
|
||||
func testInvalidNatsHeaderNameWithSpecialCharacters() {
|
||||
XCTAssertThrowsError(try NatsHeaderName("Invalid:Header:Name"))
|
||||
}
|
||||
|
||||
func testSubscript() {
|
||||
var hm = NatsHeaderMap()
|
||||
|
||||
// Test setting a value
|
||||
hm[try! NatsHeaderName("foo")] = NatsHeaderValue("bar")
|
||||
XCTAssertEqual(hm[try! NatsHeaderName("foo")], NatsHeaderValue("bar"))
|
||||
|
||||
// Test updating existing value
|
||||
hm[try! NatsHeaderName("foo")] = NatsHeaderValue("baz")
|
||||
XCTAssertEqual(hm[try! NatsHeaderName("foo")], NatsHeaderValue("baz"))
|
||||
|
||||
// Test retrieving non-existing value (should be nil or default)
|
||||
XCTAssertNil(hm[try! NatsHeaderName("non-existing")])
|
||||
|
||||
// Test removal of a value
|
||||
hm[try! NatsHeaderName("foo")] = nil
|
||||
XCTAssertNil(hm[try! NatsHeaderName("foo")])
|
||||
}
|
||||
}
|
||||
43
Tests/NatsTests/Unit/JwtTests.swift
Normal file
43
Tests/NatsTests/Unit/JwtTests.swift
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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 Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import Nats
|
||||
|
||||
class JwtTests: XCTestCase {
|
||||
|
||||
static var allTests = [
|
||||
("testParseCredentialsFile", testParseCredentialsFile)
|
||||
]
|
||||
|
||||
func testParseCredentialsFile() async throws {
|
||||
logger.logLevel = .critical
|
||||
let currentFile = URL(fileURLWithPath: #file)
|
||||
let testDir = currentFile.deletingLastPathComponent().deletingLastPathComponent()
|
||||
let resourceURL = testDir.appendingPathComponent("Integration/Resources/TestUser.creds")
|
||||
let credsData = try await URLSession.shared.data(from: resourceURL).0
|
||||
|
||||
let nkey = String(data: JwtUtils.parseDecoratedNKey(contents: credsData)!, encoding: .utf8)
|
||||
let expectedNkey = "SUACH75SWCM5D2JMJM6EKLR2WDARVGZT4QC6LX3AGHSWOMVAKERABBBRWM"
|
||||
|
||||
XCTAssertEqual(nkey, expectedNkey)
|
||||
|
||||
let jwt = String(data: JwtUtils.parseDecoratedJWT(contents: credsData)!, encoding: .utf8)
|
||||
let expectedJWT =
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJlZDI1NTE5LW5rZXkifQ.eyJqdGkiOiJMN1dBT1hJU0tPSUZNM1QyNEhMQ09ENzJRT1czQkNVWEdETjRKVU1SSUtHTlQ3RzdZVFRRIiwiaWF0IjoxNjUxNzkwOTgyLCJpc3MiOiJBRFRRUzdaQ0ZWSk5XNTcyNkdPWVhXNVRTQ1pGTklRU0hLMlpHWVVCQ0Q1RDc3T1ROTE9PS1pPWiIsIm5hbWUiOiJUZXN0VXNlciIsInN1YiI6IlVBRkhHNkZVRDJVVTRTREZWQUZVTDVMREZPMlhNNFdZTTc2VU5YVFBKWUpLN0VFTVlSQkhUMlZFIiwibmF0cyI6eyJwdWIiOnt9LCJzdWIiOnt9LCJzdWJzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJ0eXBlIjoidXNlciIsInZlcnNpb24iOjJ9fQ.bp2-Jsy33l4ayF7Ku1MNdJby4WiMKUrG-rSVYGBusAtV3xP4EdCa-zhSNUaBVIL3uYPPCQYCEoM1pCUdOnoJBg"
|
||||
|
||||
XCTAssertEqual(jwt, expectedJWT)
|
||||
}
|
||||
}
|
||||
39
Tests/NatsTests/Unit/NatsClientOptionsTests.swift
Normal file
39
Tests/NatsTests/Unit/NatsClientOptionsTests.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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 NatsClientOptionsTests: XCTestCase {
|
||||
static var allTests = [
|
||||
("testDefaultInboxPrefix", testDefaultInboxPrefix),
|
||||
("testCustomInboxPrefix", testCustomInboxPrefix),
|
||||
]
|
||||
|
||||
func testDefaultInboxPrefix() {
|
||||
let client = NatsClientOptions().build()
|
||||
let inbox = client.newInbox()
|
||||
XCTAssertTrue(inbox.hasPrefix("_INBOX."), "Default inbox prefix should be '_INBOX.'")
|
||||
XCTAssertEqual(inbox.count, "_INBOX.".count + 22, "Inbox should have prefix plus NUID")
|
||||
}
|
||||
|
||||
func testCustomInboxPrefix() {
|
||||
let customPrefix = "_INBOX_abc123."
|
||||
let client = NatsClientOptions().inboxPrefix(customPrefix).build()
|
||||
let inbox = client.newInbox()
|
||||
XCTAssertTrue(inbox.hasPrefix(customPrefix), "Inbox should use custom prefix")
|
||||
XCTAssertEqual(
|
||||
inbox.count, customPrefix.count + 22, "Inbox should have custom prefix plus NUID")
|
||||
}
|
||||
}
|
||||
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