diff --git a/circle.yml b/.circleci/config.yml similarity index 100% rename from circle.yml rename to .circleci/config.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..cf6bb647 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +dist: xenial +language: c + +env: + SWIFT_VERSION: 5.0-DEVELOPMENT-SNAPSHOT-2019-03-06-a + +setup: + - curl -o swift.tar.gz https://swift.org/builds/swift-5.0-branch/ubuntu1604/swift-${SWIFT_VERSION}/swift${SWIFT_VERSION}-ubuntu16.04.tar.gz + - tar xfz swift.tar.gz +script: + - curl -o swift.tar.gz https://swift.org/builds/swift-5.0-branch/ubuntu1604/swift-${SWIFT_VERSION}/swift${SWIFT_VERSION}-ubuntu16.04.tar.gz + - tar xfz swift.tar.gz + diff --git a/Package.swift b/Package.swift index cdd4ab06..49da97ee 100644 --- a/Package.swift +++ b/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], targets: [ .target(name: "CMD5", dependencies: []), - .target(name: "NIOPostgres", dependencies: ["CMD5", "NIO", "NIOSSL"]), + .target(name: "NIOPostgres", dependencies: ["CMD5", "NIO", "NIOSSL", "NIOFoundationCompat"]), .target(name: "NIOPostgresBenchmark", dependencies: ["NIOPostgres"]), .testTarget(name: "NIOPostgresTests", dependencies: ["NIOPostgres"]), ] diff --git a/Sources/NIOPostgres/Connection/PostgresDatabase+PreparedQuery.swift b/Sources/NIOPostgres/Connection/PostgresDatabase+PreparedQuery.swift new file mode 100644 index 00000000..c2d0fea7 --- /dev/null +++ b/Sources/NIOPostgres/Connection/PostgresDatabase+PreparedQuery.swift @@ -0,0 +1,140 @@ +import Foundation + +extension PostgresClient { + + public func prepare(query: String) -> EventLoopFuture { + let name = "nio-postgres-\(UUID().uuidString)" + let prepare = PrepareQueryHandler(query, as: name) + return self.send(prepare).map { () -> (PreparedQuery) in + let prepared = PreparedQuery(database: self, name: name, rowDescription: prepare.rowLookupTable!) + return prepared + } + } +} + + +public class PreparedQuery { + let database: PostgresClient + let name: String + let rowLookupTable: PostgresRow.LookupTable + + init(database: PostgresClient, name: String, rowDescription: PostgresRow.LookupTable) { + self.database = database + self.name = name + self.rowLookupTable = rowDescription + } + + public func execute(_ binds: [PostgresData] = []) -> EventLoopFuture<[PostgresRow]> { + var rows: [PostgresRow] = [] + return execute(binds) { rows.append($0) }.map { rows } + + } + + public func execute(_ binds: [PostgresData] = [], _ onRow: @escaping (PostgresRow) throws -> ()) -> EventLoopFuture { + let handler = ExecutePreparedQueryHandler(query: self, binds: binds, onRow: onRow) + return database.send(handler) + } +} + + +private final class PrepareQueryHandler: PostgresRequestHandler { + let query: String + let name: String + var rowLookupTable: PostgresRow.LookupTable? + var resultFormatCodes: [PostgresFormatCode] + + init(_ query: String, as name: String) { + self.query = query + self.name = name + self.resultFormatCodes = [.binary] + } + + func respond(to message: PostgresMessage) throws -> [PostgresMessage]? { + switch message.identifier { + case .rowDescription: + let row = try PostgresMessage.RowDescription(message: message) + self.rowLookupTable = PostgresRow.LookupTable( + rowDescription: row, + resultFormat: self.resultFormatCodes + ) + return [] + case .parseComplete, .parameterDescription: + return [] + case .readyForQuery: + return nil + default: + fatalError("Unexpected message: \(message)") + } + + } + + func start() throws -> [PostgresMessage] { + let parse = PostgresMessage.Parse( + statementName: self.name, + query: self.query, + parameterTypes: [] + ) + let describe = PostgresMessage.Describe( + command: .statement, + name: self.name + ) + return try [parse.message(), describe.message(), PostgresMessage.Sync().message()] + } + +} + + +private final class ExecutePreparedQueryHandler: PostgresRequestHandler { + + let query: PreparedQuery + let binds: [PostgresData] + var onRow: (PostgresRow) throws -> () + var resultFormatCodes: [PostgresFormatCode] + + init(query: PreparedQuery, binds: [PostgresData], onRow: @escaping (PostgresRow) throws -> ()) { + self.query = query + self.binds = binds + self.onRow = onRow + self.resultFormatCodes = [.binary] + + } + + func respond(to message: PostgresMessage) throws -> [PostgresMessage]? { + switch message.identifier { + case .bindComplete: + return [] + case .dataRow: + let data = try PostgresMessage.DataRow(message: message) + let row = PostgresRow(dataRow: data, lookupTable: query.rowLookupTable) + try onRow(row) + return [] + case .noData: + return [] + case .commandComplete: + return [] + case .readyForQuery: + return nil + default: throw PostgresError.protocol("Unexpected message during query: \(message)") + } + } + + func start() throws -> [PostgresMessage] { + + let bind = PostgresMessage.Bind( + portalName: "", + statementName: query.name, + parameterFormatCodes: self.binds.map { $0.formatCode }, + parameters: self.binds.map { .init(value: $0.value) }, + resultFormatCodes: self.resultFormatCodes + ) + let execute = PostgresMessage.Execute( + portalName: "", + maxRows: 0 + ) + + let sync = PostgresMessage.Sync() + return try [bind.message(), execute.message(), sync.message()] + } + + +} diff --git a/Sources/NIOPostgres/Data/PostgresData+Data.swift b/Sources/NIOPostgres/Data/PostgresData+Data.swift new file mode 100644 index 00000000..783dc6a4 --- /dev/null +++ b/Sources/NIOPostgres/Data/PostgresData+Data.swift @@ -0,0 +1,29 @@ +import Foundation +import NIOFoundationCompat + +extension PostgresData { + public var data: Data? { + guard var value = self.value else { + return nil + } + switch self.formatCode { + case .binary: + return value.readData(length: value.readableBytes) + case .text: + fatalError("Decoding the hex representation is not supported") + } + } + + + public var bytes: [UInt8]? { + guard var value = self.value else { + return nil + } + switch self.formatCode { + case .binary: + return value.readBytes(length: value.readableBytes) + case .text: + fatalError("Decoding the hex representation is not supported") + } + } +} diff --git a/Sources/NIOPostgres/Message/PostgresMessage+Authentication.swift b/Sources/NIOPostgres/Message/PostgresMessage+Authentication.swift index 4794668e..d50c1e0f 100644 --- a/Sources/NIOPostgres/Message/PostgresMessage+Authentication.swift +++ b/Sources/NIOPostgres/Message/PostgresMessage+Authentication.swift @@ -2,11 +2,11 @@ import NIO extension PostgresMessage { /// Authentication request returned by the server. - public enum Authentication: PostgresMessageType { + public enum Authentication: PostgresMessageType, Equatable { public static var identifier: PostgresMessage.Identifier { return .authentication } - + /// Parses an instance of this message type from a byte buffer. public static func parse(from buffer: inout ByteBuffer) throws -> Authentication { guard let type = buffer.readInteger(as: Int32.self) else { @@ -47,7 +47,15 @@ extension PostgresMessage { } public func serialize(into buffer: inout ByteBuffer) { - fatalError() + switch self { + case .plaintext: + buffer.writeInteger(Int32(3)) + case .ok: + buffer.writeInteger(Int32(0)) + case .md5(let salt): + buffer.writeInteger(5) + buffer.writeBytes(salt) + } } } } diff --git a/Sources/NIOPostgres/Message/PostgresMessage+Error.swift b/Sources/NIOPostgres/Message/PostgresMessage+Error.swift index 61f74857..5b9d871b 100644 --- a/Sources/NIOPostgres/Message/PostgresMessage+Error.swift +++ b/Sources/NIOPostgres/Message/PostgresMessage+Error.swift @@ -96,6 +96,10 @@ extension PostgresMessage { /// Routine: the name of the source-code routine reporting the error. case routine = 0x52 /// R } + + public init(fields: [Field: String]) { + self.fields = fields + } /// The diagnostic messages. public var fields: [Field: String] @@ -106,7 +110,11 @@ extension PostgresMessage { } public func serialize(into buffer: inout ByteBuffer) throws { - fatalError() + for field in fields { + buffer.writeInteger(field.key.rawValue) + buffer.write(nullTerminated: field.value) + } + buffer.writeInteger(0) } } } diff --git a/Sources/NIOPostgres/Message/PostgresMessage+Identifier.swift b/Sources/NIOPostgres/Message/PostgresMessage+Identifier.swift index 92b8d2e4..ab23367e 100644 --- a/Sources/NIOPostgres/Message/PostgresMessage+Identifier.swift +++ b/Sources/NIOPostgres/Message/PostgresMessage+Identifier.swift @@ -143,7 +143,7 @@ extension PostgresMessage { } extension ByteBuffer { - mutating func write(identifier: PostgresMessage.Identifier) { + public mutating func write(identifier: PostgresMessage.Identifier) { self.writeInteger(identifier.value) } } diff --git a/Sources/NIOPostgres/Message/PostgresMessage+ParameterStatus.swift b/Sources/NIOPostgres/Message/PostgresMessage+ParameterStatus.swift index 3077c626..397bac12 100644 --- a/Sources/NIOPostgres/Message/PostgresMessage+ParameterStatus.swift +++ b/Sources/NIOPostgres/Message/PostgresMessage+ParameterStatus.swift @@ -1,7 +1,7 @@ import NIO extension PostgresMessage { - public struct ParameterStatus: CustomStringConvertible { + public struct ParameterStatus: PostgresMessageType, CustomStringConvertible { /// Parses an instance of this message type from a byte buffer. public static func parse(from buffer: inout ByteBuffer) throws -> ParameterStatus { guard let parameter = buffer.readNullTerminatedString() else { @@ -12,7 +12,12 @@ extension PostgresMessage { } return .init(parameter: parameter, value: value) } - + + public init(parameter: String, value: String) { + self.parameter = parameter + self.value = value + } + /// The name of the run-time parameter being reported. public var parameter: String @@ -23,5 +28,15 @@ extension PostgresMessage { public var description: String { return "\(parameter): \(value)" } + + public static var identifier: PostgresMessage.Identifier { + return .parameterStatus + } + + public func serialize(into buffer: inout ByteBuffer) throws { + buffer.writeString(parameter + "\0") + buffer.writeString(value + "\0") + + } } } diff --git a/Sources/NIOPostgres/Message/PostgresMessage+ReadyForQuery.swift b/Sources/NIOPostgres/Message/PostgresMessage+ReadyForQuery.swift index b05e833b..d41f3042 100644 --- a/Sources/NIOPostgres/Message/PostgresMessage+ReadyForQuery.swift +++ b/Sources/NIOPostgres/Message/PostgresMessage+ReadyForQuery.swift @@ -2,25 +2,49 @@ import NIO extension PostgresMessage { /// Identifies the message type. ReadyForQuery is sent whenever the backend is ready for a new query cycle. - public struct ReadyForQuery: CustomStringConvertible { + public struct ReadyForQuery: PostgresMessageType, CustomStringConvertible { + + public enum TransactionStatus: UInt8 { + case idle = 0x49 // 'I' character + case inTransaction = 0x54 // 'T' character + case inFailedTransaction = 0x46 // 'F' character + } + /// Parses an instance of this message type from a byte buffer. public static func parse(from buffer: inout ByteBuffer) throws -> ReadyForQuery { - guard let status = buffer.readInteger(as: UInt8.self) else { + guard let statusCode = buffer.readInteger(as: UInt8.self) else { throw PostgresError.protocol("Could not read transaction status from ready for query message") } + guard let status = TransactionStatus(rawValue: statusCode) else { + throw PostgresError.protocol("Invalid status code \(statusCode).") + } return .init(transactionStatus: status) } - + + public init(transactionStatus: TransactionStatus) { + self.transactionStatus = transactionStatus + } + + + /// Current backend transaction status indicator. /// Possible values are 'I' if idle (not in a transaction block); /// 'T' if in a transaction block; or 'E' if in a failed transaction block /// (queries will be rejected until block is ended). - public var transactionStatus: UInt8 + public var transactionStatus: TransactionStatus /// See `CustomStringConvertible`. public var description: String { - let char = String(bytes: [transactionStatus], encoding: .ascii) ?? "n/a" + let char = String(bytes: [transactionStatus.rawValue], encoding: .ascii) ?? "n/a" return "transactionStatus: \(char)" } + + public static var identifier: PostgresMessage.Identifier { + return .readyForQuery + } + + public func serialize(into buffer: inout ByteBuffer) throws { + buffer.writeInteger(transactionStatus.rawValue) + } } } diff --git a/Sources/NIOPostgres/Message/PostgresMessage+RowDescription.swift b/Sources/NIOPostgres/Message/PostgresMessage+RowDescription.swift index db5d61ce..38edfab6 100644 --- a/Sources/NIOPostgres/Message/PostgresMessage+RowDescription.swift +++ b/Sources/NIOPostgres/Message/PostgresMessage+RowDescription.swift @@ -7,7 +7,11 @@ extension PostgresMessage { public static var identifier: PostgresMessage.Identifier { return .rowDescription } - + + public init(fields: [Field]) { + self.fields = fields + } + /// Describes a single field returns in a `RowDescription` message. public struct Field: CustomStringConvertible { static func parse(from buffer: inout ByteBuffer) throws -> Field { @@ -42,7 +46,17 @@ extension PostgresMessage { formatCode: formatCode ) } - + + init(name: String, tableOID: UInt32, columnAttributeNumber: Int16, dataType: PostgresDataType, dataTypeSize: Int16, dataTypeModifier: Int32, formatCode: PostgresFormatCode) { + self.name = name + self.tableOID = tableOID + self.columnAttributeNumber = columnAttributeNumber + self.dataType = dataType + self.dataTypeSize = dataTypeSize + self.dataTypeModifier = dataTypeModifier + self.formatCode = formatCode + } + /// The field name. public var name: String diff --git a/Sources/NIOPostgres/Message/PostgresMessage+SimpleQuery.swift b/Sources/NIOPostgres/Message/PostgresMessage+SimpleQuery.swift index 30bcc332..713fdefc 100644 --- a/Sources/NIOPostgres/Message/PostgresMessage+SimpleQuery.swift +++ b/Sources/NIOPostgres/Message/PostgresMessage+SimpleQuery.swift @@ -4,7 +4,10 @@ extension PostgresMessage { /// Identifies the message as a simple query. public struct SimpleQuery: PostgresMessageType { public static func parse(from buffer: inout ByteBuffer) throws -> PostgresMessage.SimpleQuery { - fatalError() + guard let query = buffer.readNullTerminatedString() else { + throw PostgresError.protocol("Failed to read query string") + } + return .init(string: query) } public static var identifier: PostgresMessage.Identifier { diff --git a/Sources/NIOPostgres/Message/PostgresMessage+Startup.swift b/Sources/NIOPostgres/Message/PostgresMessage+Startup.swift index 47a57e1b..a09ee28b 100644 --- a/Sources/NIOPostgres/Message/PostgresMessage+Startup.swift +++ b/Sources/NIOPostgres/Message/PostgresMessage+Startup.swift @@ -1,10 +1,43 @@ import NIO +/// Extension to Int32 to extract minor/major version +/// from the protocol version +fileprivate extension Int32 { + var majorVersion: Int16 { + return Int16(self >> 16) + } + + var minorVersion: Int16 { + return Int16(self & 0xFFFF) + } +} + extension PostgresMessage { /// First message sent from the frontend during startup. public struct Startup: PostgresMessageType { public static func parse(from buffer: inout ByteBuffer) throws -> PostgresMessage.Startup { - fatalError() + guard let protocolVersion = buffer.readInteger(as: Int32.self) else { + throw PostgresError.protocol("Failed to read protocol version") + } + + var keyValues : [String: String] = [:] + + var currentKey: String? + while true { + guard let value = buffer.readNullTerminatedString() else { + break + } + if value.lengthOfBytes(using: .ascii) == 0 { + break + } + if let key = currentKey { + keyValues[key] = value + currentKey = nil + } else { + currentKey = value + } + } + return Startup(protocolVersion: protocolVersion, parameters: keyValues) } public static var identifier: PostgresMessage.Identifier { @@ -24,7 +57,17 @@ extension PostgresMessage { /// version number (3 for the protocol described here). The least significant /// 16 bits are the minor version number (0 for the protocol described here). public var protocolVersion: Int32 - + + /// Returns the major protocol version of the protocol (upper 16 bits) + public var majorProtocolVersion: Int16 { + return protocolVersion.majorVersion + } + + /// Returns the minor protocol version of the protocol (lower 16 bits) + public var minorProtocolVersion: Int16 { + return protocolVersion.minorVersion + } + /// The protocol version number is followed by one or more pairs of parameter /// name and value strings. A zero byte is required as a terminator after /// the last name/value pair. Parameters can appear in any order. user is required, diff --git a/Sources/NIOPostgres/Message/PostgresMessageEncoder.swift b/Sources/NIOPostgres/Message/PostgresMessageEncoder.swift index afbd824e..1d30f17c 100644 --- a/Sources/NIOPostgres/Message/PostgresMessageEncoder.swift +++ b/Sources/NIOPostgres/Message/PostgresMessageEncoder.swift @@ -3,11 +3,11 @@ import NIO public final class PostgresMessageEncoder: MessageToByteEncoder { /// See `MessageToByteEncoder`. public typealias OutboundIn = PostgresMessage - + /// See `MessageToByteEncoder`. public func encode(data message: PostgresMessage, out: inout ByteBuffer) throws { // print("PostgresMessage.ChannelEncoder.encode(\(data))") - + // serialize identifier var message = message switch message.identifier { @@ -15,14 +15,14 @@ public final class PostgresMessageEncoder: MessageToByteEncoder { default: out.write(identifier: message.identifier) } - + // leave room for identifier and size let messageSizeIndex = out.writerIndex out.moveWriterIndex(forwardBy: 4) - + // serialize the message data out.writeBuffer(&message.data) - + // set message size out.setInteger(Int32(out.writerIndex - messageSizeIndex), at: messageSizeIndex) } diff --git a/Sources/NIOPostgres/Message/PostgresMessageType.swift b/Sources/NIOPostgres/Message/PostgresMessageType.swift index dfd756a8..4d133d04 100644 --- a/Sources/NIOPostgres/Message/PostgresMessageType.swift +++ b/Sources/NIOPostgres/Message/PostgresMessageType.swift @@ -5,7 +5,7 @@ public protocol PostgresMessageType: CustomStringConvertible { } extension PostgresMessageType { - func message() throws -> PostgresMessage { + public func message() throws -> PostgresMessage { var buffer = ByteBufferAllocator().buffer(capacity: 0) try self.serialize(into: &buffer) return .init(identifier: Self.identifier, data: buffer) diff --git a/Tests/NIOPostgresTests/Fixtures/AuthenticationRequestCleartextPassword.fixture b/Tests/NIOPostgresTests/Fixtures/AuthenticationRequestCleartextPassword.fixture new file mode 100644 index 00000000..165be421 Binary files /dev/null and b/Tests/NIOPostgresTests/Fixtures/AuthenticationRequestCleartextPassword.fixture differ diff --git a/Tests/NIOPostgresTests/Fixtures/AuthenticationRequestMD5Password.fixture b/Tests/NIOPostgresTests/Fixtures/AuthenticationRequestMD5Password.fixture new file mode 100644 index 00000000..f53a0437 Binary files /dev/null and b/Tests/NIOPostgresTests/Fixtures/AuthenticationRequestMD5Password.fixture differ diff --git a/Tests/NIOPostgresTests/Fixtures/AuthenticationRequestOK.fixture b/Tests/NIOPostgresTests/Fixtures/AuthenticationRequestOK.fixture new file mode 100644 index 00000000..5afe1e32 Binary files /dev/null and b/Tests/NIOPostgresTests/Fixtures/AuthenticationRequestOK.fixture differ diff --git a/Tests/NIOPostgresTests/Fixtures/ParameterStatus.fixture b/Tests/NIOPostgresTests/Fixtures/ParameterStatus.fixture new file mode 100644 index 00000000..0e0bd127 Binary files /dev/null and b/Tests/NIOPostgresTests/Fixtures/ParameterStatus.fixture differ diff --git a/Tests/NIOPostgresTests/Fixtures/Startup.fixture b/Tests/NIOPostgresTests/Fixtures/Startup.fixture new file mode 100644 index 00000000..10006b06 Binary files /dev/null and b/Tests/NIOPostgresTests/Fixtures/Startup.fixture differ diff --git a/Tests/NIOPostgresTests/NIOPostgresTests.swift b/Tests/NIOPostgresTests/NIOPostgresTests.swift index 4821b380..42cf4fa2 100644 --- a/Tests/NIOPostgresTests/NIOPostgresTests.swift +++ b/Tests/NIOPostgresTests/NIOPostgresTests.swift @@ -322,4 +322,28 @@ final class NIOPostgresTests: XCTestCase { } } } + + func testInvalidPassword() throws { + let auth = PostgresConnection.testUnauthenticated(on: eventLoop).flatMap({ (connection) in + connection.authenticate(username: "invalid", database: "invalid", password: "bad").map { connection } + }) + do { + let _ = try auth.wait() + XCTFail("The authentication should fail") + } catch let error as PostgresError { + XCTAssertEqual(error.code, .invalid_password) + } + } + + func testPreparedQuery() throws { + let conn = try PostgresConnection.test(on: eventLoop).wait() + + let prepared = try conn.prepare(query: "SELECT 1 as one;").wait() + let rows = try prepared.execute().wait() + + XCTAssertEqual(rows.count, 1) + let value = rows[0].column("one") + XCTAssertEqual(value?.int, 1) + + } } diff --git a/Tests/NIOPostgresTests/ParsingTests.swift b/Tests/NIOPostgresTests/ParsingTests.swift new file mode 100644 index 00000000..341d756c --- /dev/null +++ b/Tests/NIOPostgresTests/ParsingTests.swift @@ -0,0 +1,94 @@ +// +// NIOPostgresParsingTests.swift +// NIOPostgresTests +// +// Created by Thomas Bartelmess on 2019-03-10. +// + +import NIOPostgres +import XCTest + +class ParsingTests: XCTestCase { + + var fixtureDirectory: URL { + let currentFile = URL(fileURLWithPath: #file) + let fixtureDirectory = currentFile.deletingLastPathComponent() + .appendingPathComponent("Fixtures") + return fixtureDirectory; + } + + func getFixture(named name: String) -> ByteBuffer { + let fixturePath = fixtureDirectory.appendingPathComponent("\(name).fixture") + if !FileManager.default.fileExists(atPath: fixturePath.path) { + XCTFail("Fixture named: \(name), not found at \(fixturePath.path))") + + } + let data = try! Data(contentsOf: fixturePath) + var byteBuffer = ByteBufferAllocator().buffer(capacity: data.count) + byteBuffer.writeBytes(data) + + return byteBuffer + } + + /// Parses a message from a fixture, returning the parsed PostgreSQL message + /// + /// - parameters: + /// - fixtureName: name of the fixture in the fixtures directory (without the file extension) + /// - hasType: Specifies if the message has the message type prefix (1 character) + /// - returns: The Message struct, if parsing failed this, `XCTFail` with the error description will be called + func parseMessage(fixtureName: String, hasType: Bool = true) -> T? { + do { + var fixture = getFixture(named: fixtureName) + if (hasType) { + let _: UInt8 = fixture.readInteger()! + } + let _: UInt32 = fixture.readInteger()! + return try T.parse(from: &fixture) + } catch PostgresError.protocol(let errorMessage) { + XCTFail("Failed to parse message: \(errorMessage)") + } catch { + XCTFail("Unexpected error") + } + fatalError() + } + + func testParseStartupMessage() { + let startupMessage: PostgresMessage.Startup? = parseMessage(fixtureName: "Startup", hasType: false) + XCTAssertEqual(startupMessage?.protocolVersion, 196608) + XCTAssertEqual(startupMessage?.minorProtocolVersion, 0) + XCTAssertEqual(startupMessage?.majorProtocolVersion, 3) + let parameters = startupMessage?.parameters + XCTAssertEqual(parameters?["user"], "thomasbartelmess") + XCTAssertEqual(parameters?["database"], "thomasbartelmess") + } + + func testParseParameterStatus() { + let parameterStatusMessage: PostgresMessage.ParameterStatus? = parseMessage(fixtureName: "ParameterStatus") + XCTAssertEqual(parameterStatusMessage?.parameter, "server_version") + XCTAssertEqual(parameterStatusMessage?.value, "10.5") + } + + func testAuthorizationRequestOK() { + let authMessage: PostgresMessage.Authentication? = parseMessage(fixtureName: "AuthenticationRequestOK") + guard let message = authMessage else { + return + } + XCTAssertEqual(message, PostgresMessage.Authentication.ok) + } + + func testAuthorizationRequestCleartextPassword() { + let authMessage: PostgresMessage.Authentication? = parseMessage(fixtureName: "AuthenticationRequestCleartextPassword") + guard let message = authMessage else { + return + } + XCTAssertEqual(message, PostgresMessage.Authentication.plaintext) + } + + func testAuthorizationRequestMD5Password() { + let authMessage: PostgresMessage.Authentication? = parseMessage(fixtureName: "AuthenticationRequestMD5Password") + guard let message = authMessage else { + return + } + XCTAssertEqual(message, PostgresMessage.Authentication.md5([40,155,15,168])) + } +} diff --git a/Tests/NIOPostgresTests/SerializationTests.swift b/Tests/NIOPostgresTests/SerializationTests.swift new file mode 100644 index 00000000..c24b4217 --- /dev/null +++ b/Tests/NIOPostgresTests/SerializationTests.swift @@ -0,0 +1,21 @@ +import Foundation +import XCTest +import NIO +import NIOPostgres + + +class SerializationTests: XCTestCase { + func testErrorSerialization() throws { + let testFields: [PostgresMessage.Error.Field: String] = + [.severity: "ERROR", + .sqlState: "42P01", + .message: "relation \"foo\" does not exist"] + let message = PostgresMessage.Error(fields: testFields) + var buffer = ByteBufferAllocator().buffer(capacity: 0) + try message.serialize(into: &buffer) + let parsed = try PostgresMessage.Error.parse(from: &buffer) + XCTAssertEqual(parsed.fields[.severity], "ERROR") + XCTAssertEqual(parsed.fields[.sqlState], "42P01") + XCTAssertEqual(parsed.fields[.message], "relation \"foo\" does not exist") + } +} diff --git a/Tests/NIOPostgresTests/XCTestManifests.swift b/Tests/NIOPostgresTests/XCTestManifests.swift index 282fde1e..58a79c5a 100644 --- a/Tests/NIOPostgresTests/XCTestManifests.swift +++ b/Tests/NIOPostgresTests/XCTestManifests.swift @@ -23,6 +23,7 @@ extension NIOPostgresTests { ("testSimpleQueryVersion", testSimpleQueryVersion), ("testSQLError", testSQLError), ("testUUID", testUUID), + ("testAuthorizationRequestMD5Password", testAuthorizationRequestMD5Password) ] }