|
| 1 | +import NIO |
| 2 | +@testable import NIORedis |
| 3 | +import XCTest |
| 4 | + |
| 5 | +final class RedisDataDecoderTests: XCTestCase { |
| 6 | + private let decoder = RedisDataDecoder() |
| 7 | + private let allocator = ByteBufferAllocator() |
| 8 | + |
| 9 | + func test_error() throws { |
| 10 | + XCTAssertNil(try runTest("-ERR")) |
| 11 | + XCTAssertNil(try runTest("-ERR\r")) |
| 12 | + XCTAssertEqual(try runTest("-ERROR\r\n")?.error?.description.contains("ERROR"), true) |
| 13 | + |
| 14 | + let multiError: (RedisData?, RedisData?) = try runTest("-ERROR\r\n-OTHER ERROR\r\n") |
| 15 | + XCTAssertEqual(multiError.0?.error?.description.contains("ERROR"), true) |
| 16 | + XCTAssertEqual(multiError.1?.error?.description.contains("OTHER ERROR"), true) |
| 17 | + } |
| 18 | + |
| 19 | + func test_simpleString() throws { |
| 20 | + XCTAssertNil(try runTest("+OK")) |
| 21 | + XCTAssertNil(try runTest("+OK\r")) |
| 22 | + XCTAssertEqual(try runTest("+\r\n")?.string, "") |
| 23 | + XCTAssertEqual(try runTest("+OK\r\n")?.string, "OK") |
| 24 | + |
| 25 | + XCTAssertEqual(try runTest("+©ºmpl³x\r\n")?.string, "©ºmpl³x") |
| 26 | + |
| 27 | + let multiSimpleString: (RedisData?, RedisData?) = try runTest("+OK\r\n+OTHER STRINGS\r\n") |
| 28 | + XCTAssertEqual(multiSimpleString.0?.string, "OK") |
| 29 | + XCTAssertEqual(multiSimpleString.1?.string, "OTHER STRINGS") |
| 30 | + } |
| 31 | + |
| 32 | + func test_integer() throws { |
| 33 | + XCTAssertNil(try runTest(":100")) |
| 34 | + XCTAssertNil(try runTest(":100\r")) |
| 35 | + XCTAssertNil(try runTest(":\r")) |
| 36 | + XCTAssertEqual(try runTest(":0\r\n")?.int, 0) |
| 37 | + XCTAssertEqual(try runTest(":01\r\n")?.int, 1) |
| 38 | + XCTAssertEqual(try runTest(":1000\r\n")?.int, 1000) |
| 39 | + XCTAssertEqual(try runTest(":-9223372036854775807\r\n")?.int, -9223372036854775807) |
| 40 | + |
| 41 | + let multiInteger: (RedisData?, RedisData?) = try runTest(":9223372036854775807\r\n:99\r\n") |
| 42 | + XCTAssertEqual(multiInteger.0?.int, 9223372036854775807) |
| 43 | + XCTAssertEqual(multiInteger.1?.int, 99) |
| 44 | + } |
| 45 | + |
| 46 | + func test_bulkString() throws { |
| 47 | + XCTAssertNil(try runTest("$0")) |
| 48 | + XCTAssertNil(try runTest("$0\r")) |
| 49 | + XCTAssertNil(try runTest("$0\r\n\r")) |
| 50 | + XCTAssertNil(try runTest("$-1\r")) |
| 51 | + XCTAssertEqual(try runTest("$-1\r\n")?.isNull, true) |
| 52 | + XCTAssertEqual(try runTest("$0\r\n\r\n")?.string, "") |
| 53 | + XCTAssertNil(try runTest("$1\r\na\r")) |
| 54 | + XCTAssertEqual(try runTest("$1\r\na\r\n")?.string, "a") |
| 55 | + XCTAssertNil(try runTest("$3\r\nfoo\r")) |
| 56 | + XCTAssertEqual(try runTest("$3\r\nfoo\r\n")?.string, "foo") |
| 57 | + XCTAssertNil(try runTest("$3\r\nn³\r")) |
| 58 | + XCTAssertEqual(try runTest("$3\r\nn³\r\n")?.string, "n³") |
| 59 | + |
| 60 | + let str = "κόσμε" |
| 61 | + let strBytes = str.convertedToData() |
| 62 | + let strInput = "$\(strBytes.count)\r\n\(str)\r\n" |
| 63 | + XCTAssertEqual(try runTest(strInput)?.string, str) |
| 64 | + XCTAssertEqual(try runTest(strInput)?.data, strBytes) |
| 65 | + |
| 66 | + let multiBulkString: (RedisData?, RedisData?) = try runTest("$-1\r\n$3\r\nn³\r\n") |
| 67 | + XCTAssertEqual(multiBulkString.0?.isNull, true) |
| 68 | + XCTAssertEqual(multiBulkString.1?.string, "n³") |
| 69 | + |
| 70 | + let rawBytes = Data(bytes: [0x00, 0x01, 0x02, 0x03, 0x0A, 0xff]) |
| 71 | + let rawByteInput = "$\(rawBytes.count)\r\n".convertedToData() + rawBytes + "\r\n".convertedToData() |
| 72 | + XCTAssertEqual(try runTest(rawByteInput)?.data, rawBytes) |
| 73 | + } |
| 74 | + |
| 75 | + func test_array() throws { |
| 76 | + func runArrayTest(_ input: String) throws -> [RedisData]? { |
| 77 | + return try runTest(input)?.array |
| 78 | + } |
| 79 | + |
| 80 | + XCTAssertNil(try runArrayTest("*0\r")) |
| 81 | + XCTAssertNil(try runArrayTest("*1\r\n+OK\r")) |
| 82 | + XCTAssertEqual(try runArrayTest("*0\r\n")?.count, 0) |
| 83 | + XCTAssertTrue(arraysAreEqual( |
| 84 | + try runArrayTest("*1\r\n$3\r\nfoo\r\n"), |
| 85 | + expected: [.bulkString("foo".convertedToData())] |
| 86 | + )) |
| 87 | + XCTAssertTrue(arraysAreEqual( |
| 88 | + try runArrayTest("*3\r\n+foo\r\n$3\r\nbar\r\n:3\r\n"), |
| 89 | + expected: [.simpleString("foo"), .bulkString("bar".convertedToData()), .integer(3)] |
| 90 | + )) |
| 91 | + XCTAssertTrue(arraysAreEqual( |
| 92 | + try runArrayTest("*1\r\n*2\r\n+OK\r\n:1\r\n"), |
| 93 | + expected: [.array([ .simpleString("OK"), .integer(1) ])] |
| 94 | + )) |
| 95 | + } |
| 96 | + |
| 97 | + private func runTest(_ input: String) throws -> RedisData? { |
| 98 | + return try runTest(input.convertedToData()) |
| 99 | + } |
| 100 | + |
| 101 | + private func runTest(_ input: Data) throws -> RedisData? { |
| 102 | + return try runTest(input).0 |
| 103 | + } |
| 104 | + |
| 105 | + private func runTest(_ input: String) throws -> (RedisData?, RedisData?) { |
| 106 | + return try runTest(input.convertedToData()) |
| 107 | + } |
| 108 | + |
| 109 | + private func runTest(_ input: Data) throws -> (RedisData?, RedisData?) { |
| 110 | + let embeddedChannel = EmbeddedChannel() |
| 111 | + defer { _ = try? embeddedChannel.finish() } |
| 112 | + try embeddedChannel.pipeline.add(handler: decoder).wait() |
| 113 | + var buffer = allocator.buffer(capacity: 256) |
| 114 | + buffer.write(bytes: input) |
| 115 | + try embeddedChannel.writeInbound(buffer) |
| 116 | + return (embeddedChannel.readInbound(), embeddedChannel.readInbound()) |
| 117 | + } |
| 118 | + |
| 119 | + private func arraysAreEqual(_ lhs: [RedisData]?, expected right: [RedisData]) -> Bool { |
| 120 | + guard |
| 121 | + let left = lhs, |
| 122 | + left.count == right.count |
| 123 | + else { return false } |
| 124 | + |
| 125 | + var arraysMatch = true |
| 126 | + |
| 127 | + left.enumerated().forEach { |
| 128 | + let (offset, decodedElement) = $0 |
| 129 | + |
| 130 | + switch (decodedElement, right[offset]) { |
| 131 | + case (let .bulkString(decoded), let .bulkString(expected)): arraysMatch = decoded == expected |
| 132 | + case (let .simpleString(decoded), let .simpleString(expected)): arraysMatch = decoded == expected |
| 133 | + case (let .integer(decoded), let .integer(expected)): arraysMatch = decoded == expected |
| 134 | + case (let .error(decoded), let .error(expected)): arraysMatch = decoded == expected |
| 135 | + case (.null, .null): break |
| 136 | + case (let .array(decoded), let .array(expected)): arraysMatch = arraysAreEqual(decoded, expected: expected) |
| 137 | + default: |
| 138 | + XCTFail("Array mismatch!") |
| 139 | + arraysMatch = false |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + return arraysMatch |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +// MARK: All Types |
| 148 | + |
| 149 | +extension RedisDataDecoderTests { |
| 150 | + private struct AllData { |
| 151 | + static let expectedString = "string" |
| 152 | + static let expectedError = "ERROR" |
| 153 | + static let expectedBulkString = "aa" |
| 154 | + static let expectedInteger = -1000 |
| 155 | + |
| 156 | + static var messages = [ |
| 157 | + "+\(expectedString)\r\n", |
| 158 | + ":\(expectedInteger)\r\n", |
| 159 | + "-\(expectedError)\r\n", |
| 160 | + "$2\r\n\(expectedBulkString)\r\n", |
| 161 | + "$-1\r\n", |
| 162 | + "$0\r\n\r\n", |
| 163 | + "*3\r\n+\(expectedString)\r\n$2\r\n\(expectedBulkString)\r\n:\(expectedInteger)\r\n", |
| 164 | + "*1\r\n*1\r\n:\(expectedInteger)\r\n", |
| 165 | + "*0\r\n", |
| 166 | + "*-1\r\n" |
| 167 | + ] |
| 168 | + } |
| 169 | + |
| 170 | + func test_all() throws { |
| 171 | + let embeddedChannel = EmbeddedChannel() |
| 172 | + defer { _ = try? embeddedChannel.finish() } |
| 173 | + try embeddedChannel.pipeline.add(handler: decoder).wait() |
| 174 | + |
| 175 | + var buffer = allocator.buffer(capacity: 256) |
| 176 | + for message in AllData.messages { |
| 177 | + buffer.write(string: message) |
| 178 | + } |
| 179 | + |
| 180 | + try embeddedChannel.writeInbound(buffer) |
| 181 | + |
| 182 | + var results = [RedisData?]() |
| 183 | + for _ in 0..<AllData.messages.count { |
| 184 | + results.append(embeddedChannel.readInbound()) |
| 185 | + } |
| 186 | + |
| 187 | + XCTAssertEqual(results[0]?.string, AllData.expectedString) |
| 188 | + XCTAssertEqual(results[1]?.int, AllData.expectedInteger) |
| 189 | + XCTAssertEqual(results[2]?.error?.description.contains(AllData.expectedError), true) |
| 190 | + |
| 191 | + XCTAssertEqual(results[3]?.string, AllData.expectedBulkString) |
| 192 | + XCTAssertEqual(results[3]?.data, AllData.expectedBulkString.convertedToData()) |
| 193 | + |
| 194 | + XCTAssertEqual(results[4]?.isNull, true) |
| 195 | + |
| 196 | + XCTAssertEqual(results[5]?.data?.count, 0) |
| 197 | + XCTAssertEqual(results[5]?.string, "") |
| 198 | + |
| 199 | + XCTAssertEqual(results[6]?.array?.count, 3) |
| 200 | + XCTAssertTrue(arraysAreEqual( |
| 201 | + results[6]?.array, |
| 202 | + expected: [ |
| 203 | + .simpleString(AllData.expectedString), |
| 204 | + .bulkString(AllData.expectedBulkString.convertedToData()), |
| 205 | + .integer(AllData.expectedInteger) |
| 206 | + ] |
| 207 | + )) |
| 208 | + |
| 209 | + XCTAssertEqual(results[7]?.array?.count, 1) |
| 210 | + XCTAssertTrue(arraysAreEqual( |
| 211 | + results[7]?.array, |
| 212 | + expected: [.array([.integer(AllData.expectedInteger)])] |
| 213 | + )) |
| 214 | + |
| 215 | + XCTAssertEqual(results[8]?.array?.count, 0) |
| 216 | + XCTAssertEqual(results[9]?.isNull, true) |
| 217 | + } |
| 218 | +} |
| 219 | + |
| 220 | +extension RedisDataDecoderTests { |
| 221 | + static var allTests = [ |
| 222 | + ("test_error", test_error), |
| 223 | + ("test_simpleString", test_simpleString), |
| 224 | + ("test_integer", test_integer), |
| 225 | + ("test_bulkString", test_bulkString), |
| 226 | + ("test_array", test_array), |
| 227 | + ("test_all", test_all), |
| 228 | + ] |
| 229 | +} |
| 230 | + |
| 231 | +extension RedisError: Equatable { |
| 232 | + public static func == (lhs: RedisError, rhs: RedisError) -> Bool { |
| 233 | + return lhs.description == rhs.description |
| 234 | + } |
| 235 | +} |
0 commit comments