From 951f1fe76f1a57b39429ea0e8ea00ab9d6e24756 Mon Sep 17 00:00:00 2001 From: Terry Yiu <963907+tyiu@users.noreply.github.com> Date: Sun, 19 May 2024 09:31:37 -0400 Subject: [PATCH] Relay request and response additions (#151) * Reorder relay request and relay response enums to match the listed ordering in the specs for easier maintainability * Fix RelayResponse to not drop human readable messages for OK messages * Add CLOSED relay response * Add AUTH relay request and response * Apply suggestions from code review Co-authored-by: Bryan Montz * Shorten struct names * Change RelayResponseDecodingTest to use XCTUnwrap instead of if let to make the test code cleaner --------- Co-authored-by: Bryan Montz --- Sources/NostrSDK/EventKind.swift | 11 +- .../NostrSDK/Events/AuthenticationEvent.swift | 55 +++++ Sources/NostrSDK/RelayRequest.swift | 15 +- Sources/NostrSDK/RelayResponse.swift | 64 +++--- .../Events/AuthenticationEventTests.swift | 41 ++++ .../NostrSDKTests/Fixtures/auth_request.json | 1 + .../Fixtures/authentication_event.json | 12 ++ Tests/NostrSDKTests/Fixtures/closed.json | 1 + .../Fixtures/ok_success_reason.json | 2 +- .../ok_success_reason_prefix_no_message.json | 1 + .../Fixtures/ok_unknown_reason.json | 1 + .../RelayRequestEncodingTests.swift | 36 ++-- .../RelayResponseDecodingTests.swift | 195 ++++++++++-------- 13 files changed, 297 insertions(+), 138 deletions(-) create mode 100644 Sources/NostrSDK/Events/AuthenticationEvent.swift create mode 100644 Tests/NostrSDKTests/Events/AuthenticationEventTests.swift create mode 100644 Tests/NostrSDKTests/Fixtures/auth_request.json create mode 100644 Tests/NostrSDKTests/Fixtures/authentication_event.json create mode 100644 Tests/NostrSDKTests/Fixtures/closed.json create mode 100644 Tests/NostrSDKTests/Fixtures/ok_success_reason_prefix_no_message.json create mode 100644 Tests/NostrSDKTests/Fixtures/ok_unknown_reason.json diff --git a/Sources/NostrSDK/EventKind.swift b/Sources/NostrSDK/EventKind.swift index cc542ca..3462377 100644 --- a/Sources/NostrSDK/EventKind.swift +++ b/Sources/NostrSDK/EventKind.swift @@ -72,7 +72,13 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha /// /// See [NIP-51](https://github.com/nostr-protocol/nips/blob/master/51.md#standard-lists) case bookmarksList - + + /// This kind of event provides a way for clients to authenticate to relays by signing an ephemeral event. + /// This kind is not meant to be published or queried. + /// + /// See [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md). + case authentication + /// This kind of event is for long-form texxt content, generally referred to as "articles" or "blog posts". /// /// See [NIP-23](https://github.com/nostr-protocol/nips/blob/master/23.md). @@ -112,6 +118,7 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha .report, .muteList, .bookmarksList, + .authentication, .longformContent, .dateBasedCalendarEvent, .timeBasedCalendarEvent, @@ -141,6 +148,7 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha case .report: return 1984 case .muteList: return 10000 case .bookmarksList: return 10003 + case .authentication: return 22242 case .longformContent: return 30023 case .dateBasedCalendarEvent: return 31922 case .timeBasedCalendarEvent: return 31923 @@ -164,6 +172,7 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha case .report: return ReportEvent.self case .muteList: return MuteListEvent.self case .bookmarksList: return BookmarksListEvent.self + case .authentication: return AuthenticationEvent.self case .longformContent: return LongformContentEvent.self case .dateBasedCalendarEvent: return DateBasedCalendarEvent.self case .timeBasedCalendarEvent: return TimeBasedCalendarEvent.self diff --git a/Sources/NostrSDK/Events/AuthenticationEvent.swift b/Sources/NostrSDK/Events/AuthenticationEvent.swift new file mode 100644 index 0000000..fac45bc --- /dev/null +++ b/Sources/NostrSDK/Events/AuthenticationEvent.swift @@ -0,0 +1,55 @@ +// +// AuthenticationEvent.swift +// +// +// Created by Terry Yiu on 5/1/24. +// + +import Foundation + +/// An event that provides a way for clients to authenticate to relays. +/// This kind is not meant to be published or queried. +/// +/// See [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md). +public final class AuthenticationEvent: NostrEvent, RelayProviding, RelayURLValidating { + public required init(from decoder: Decoder) throws { + try super.init(from: decoder) + } + + @available(*, unavailable, message: "This initializer is unavailable for this class.") + override init(kind: EventKind, content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws { + try super.init(kind: kind, content: content, tags: tags, createdAt: createdAt, signedBy: keypair) + } + + init(content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws { + try super.init(kind: .authentication, content: content, tags: tags, createdAt: createdAt, signedBy: keypair) + } + + /// The relay URL this event authenticates to. + public var relayURL: URL? { + guard let relayURLString = firstValueForRawTagName("relay") else { + return nil + } + + return try? validateRelayURLString(relayURLString) + } + + /// The challenge string as received from the relay. + public var challenge: String? { + firstValueForRawTagName("challenge") + } +} + +public extension EventCreating { + + func authenticate(relayURL: URL, challenge: String, signedBy keypair: Keypair) throws -> AuthenticationEvent { + let validatedRelayURL = try RelayURLValidator.shared.validateRelayURL(relayURL) + + let tags: [Tag] = [ + Tag(name: "relay", value: validatedRelayURL.absoluteString), + Tag(name: "challenge", value: challenge) + ] + + return try AuthenticationEvent(content: "", tags: tags, signedBy: keypair) + } +} diff --git a/Sources/NostrSDK/RelayRequest.swift b/Sources/NostrSDK/RelayRequest.swift index 4b6e75f..a9e9961 100644 --- a/Sources/NostrSDK/RelayRequest.swift +++ b/Sources/NostrSDK/RelayRequest.swift @@ -8,22 +8,25 @@ import Foundation enum RelayRequest { - case close(subscriptionId: String) case event(NostrEvent) - case count(subscriptionId: String, filter: Filter) case request(subscriptionId: String, filter: Filter) + case close(subscriptionId: String) + case auth(AuthenticationEvent) + case count(subscriptionId: String, filter: Filter) var encoded: String? { let payload: [AnyEncodable] switch self { - case .close(let subscriptionId): - payload = [AnyEncodable("CLOSE"), AnyEncodable(subscriptionId)] case .event(let event): payload = [AnyEncodable("EVENT"), AnyEncodable(event)] - case .count(let subscriptionId, let filter): - payload = [AnyEncodable("COUNT"), AnyEncodable(subscriptionId), AnyEncodable(filter)] case .request(let subscriptionId, let filter): payload = [AnyEncodable("REQ"), AnyEncodable(subscriptionId), AnyEncodable(filter)] + case .close(let subscriptionId): + payload = [AnyEncodable("CLOSE"), AnyEncodable(subscriptionId)] + case .auth(let event): + payload = [AnyEncodable("AUTH"), AnyEncodable(event)] + case .count(let subscriptionId, let filter): + payload = [AnyEncodable("COUNT"), AnyEncodable(subscriptionId), AnyEncodable(filter)] } guard let data = try? JSONEncoder().encode(payload) else { diff --git a/Sources/NostrSDK/RelayResponse.swift b/Sources/NostrSDK/RelayResponse.swift index 77cd1c5..826ee87 100644 --- a/Sources/NostrSDK/RelayResponse.swift +++ b/Sources/NostrSDK/RelayResponse.swift @@ -29,34 +29,37 @@ enum RelayResponse: Decodable { enum MessageType: String, Codable { case event = "EVENT" - case notice = "NOTICE" - case eose = "EOSE" case ok = "OK" - case count = "COUNT" + case eose = "EOSE" + case closed = "CLOSED" + case notice = "NOTICE" case auth = "AUTH" + case count = "COUNT" } - struct OKMessage { - let type: OKMessageType - let message: String? + struct Message { + let prefix: MessagePrefix + let message: String init(rawMessage: String) { - let components = rawMessage.split(separator: ":") + let components = rawMessage.split(separator: ":", maxSplits: 1) if let firstComponent = components.first { - type = OKMessageType(rawValue: String(firstComponent)) ?? .unknown + prefix = MessagePrefix(rawValue: String(firstComponent)) ?? .unknown } else { - type = .unknown + prefix = .unknown } - - if components.count >= 2 { - message = components[1].trimmingCharacters(in: .whitespaces) + + if prefix == .unknown { + message = rawMessage.trimmingCharacters(in: .whitespacesAndNewlines) + } else if components.count >= 2 { + message = components[1].trimmingCharacters(in: .whitespacesAndNewlines) } else { - message = nil + message = "" } } } - enum OKMessageType: String, Codable { + enum MessagePrefix: String, Codable { case unknown case duplicate case pow @@ -64,14 +67,17 @@ enum RelayResponse: Decodable { case rateLimited = "rate-limited" case invalid case error + case authRequired = "auth-required" + case restricted } - case notice(message: String) - case eose(subscriptionId: String) case event(subscriptionId: String, event: NostrEvent) - case ok(eventId: String, success: Bool, message: OKMessage) - case count(subscriptionId: String, count: Int) + case ok(eventId: String, success: Bool, message: Message) + case eose(subscriptionId: String) + case closed(subscriptionId: String, message: Message) + case notice(message: String) case auth(challenge: String) + case count(subscriptionId: String, count: Int) init(from decoder: Decoder) throws { var container = try decoder.unkeyedContainer() @@ -89,24 +95,28 @@ enum RelayResponse: Decodable { let event = try container2.decode(kindMapper.classForKind.self) self = .event(subscriptionId: subscriptionId, event: event) - case .notice: + case .ok: + let eventId = try container.decode(String.self) + let success = try container.decode(Bool.self) let message = try container.decode(String.self) - self = .notice(message: message) + self = .ok(eventId: eventId, success: success, message: Message(rawMessage: message)) case .eose: let subscriptionId = try container.decode(String.self) self = .eose(subscriptionId: subscriptionId) - case .ok: - let eventId = try container.decode(String.self) - let success = try container.decode(Bool.self) + case .closed: + let subscriptionId = try container.decode(String.self) + let message = try container.decode(String.self) + self = .closed(subscriptionId: subscriptionId, message: Message(rawMessage: message)) + case .notice: let message = try container.decode(String.self) - self = .ok(eventId: eventId, success: success, message: OKMessage(rawMessage: message)) + self = .notice(message: message) + case .auth: + let challenge = try container.decode(String.self) + self = .auth(challenge: challenge) case .count: let subscriptionId = try container.decode(String.self) let countResponse = try container.decode(CountResponse.self) self = .count(subscriptionId: subscriptionId, count: countResponse.count) - case .auth: - let challenge = try container.decode(String.self) - self = .auth(challenge: challenge) } } diff --git a/Tests/NostrSDKTests/Events/AuthenticationEventTests.swift b/Tests/NostrSDKTests/Events/AuthenticationEventTests.swift new file mode 100644 index 0000000..fc7116d --- /dev/null +++ b/Tests/NostrSDKTests/Events/AuthenticationEventTests.swift @@ -0,0 +1,41 @@ +// +// AuthenticationEventTests.swift +// +// +// Created by Terry Yiu on 5/2/24. +// + +@testable import NostrSDK +import XCTest + +final class AuthenticationEventTests: XCTestCase, EventCreating, EventVerifying, FixtureLoading { + + func testCreateAuthenticationEvent() throws { + let relayURL = try XCTUnwrap(URL(string: "wss://relay.example.com/")) + let event = try authenticate(relayURL: relayURL, challenge: "some-challenge-string", signedBy: Keypair.test) + + XCTAssertEqual(event.kind, .authentication) + XCTAssertEqual(event.relayURL, relayURL) + XCTAssertEqual(event.challenge, "some-challenge-string") + + try verifyEvent(event) + } + + func testDecodeAuthenticationEvent() throws { + let event: AuthenticationEvent = try decodeFixture(filename: "authentication_event") + + XCTAssertEqual(event.id, "adb599bc2f6b4cf97d927de2cb36829326c86013e0a6e8f51159f80938a5c246") + XCTAssertEqual(event.pubkey, "9947f9659dd80c3682402b612f5447e28249997fb3709500c32a585eb0977340") + XCTAssertEqual(event.createdAt, 1714625219) + XCTAssertEqual(event.kind, .authentication) + + let expectedTags: [Tag] = [ + Tag(name: "relay", value: "wss://relay.example.com/"), + Tag(name: "challenge", value: "some-challenge-string") + ] + XCTAssertEqual(event.tags, expectedTags) + XCTAssertEqual(event.content, "") + XCTAssertEqual(event.signature, "b27181e0b72872c463ac75ebf3ad2c2502696d81551bbae6b2a391c67614daf2e321eb5ab724b04520b26fcf7a4c9823fefdb47b10d66c088db44162ba9c1291") + } + +} diff --git a/Tests/NostrSDKTests/Fixtures/auth_request.json b/Tests/NostrSDKTests/Fixtures/auth_request.json new file mode 100644 index 0000000..3e48945 --- /dev/null +++ b/Tests/NostrSDKTests/Fixtures/auth_request.json @@ -0,0 +1 @@ +["AUTH",{"id":"adb599bc2f6b4cf97d927de2cb36829326c86013e0a6e8f51159f80938a5c246","pubkey":"9947f9659dd80c3682402b612f5447e28249997fb3709500c32a585eb0977340","created_at":1714625219,"kind":22242,"tags":[["relay","wss://relay.example.com/"],["challenge","some-challenge-string"]],"content":"","sig":"b27181e0b72872c463ac75ebf3ad2c2502696d81551bbae6b2a391c67614daf2e321eb5ab724b04520b26fcf7a4c9823fefdb47b10d66c088db44162ba9c1291"}] diff --git a/Tests/NostrSDKTests/Fixtures/authentication_event.json b/Tests/NostrSDKTests/Fixtures/authentication_event.json new file mode 100644 index 0000000..874daea --- /dev/null +++ b/Tests/NostrSDKTests/Fixtures/authentication_event.json @@ -0,0 +1,12 @@ +{ + "id": "adb599bc2f6b4cf97d927de2cb36829326c86013e0a6e8f51159f80938a5c246", + "pubkey": "9947f9659dd80c3682402b612f5447e28249997fb3709500c32a585eb0977340", + "created_at": 1714625219, + "kind": 22242, + "tags": [ + ["relay", "wss://relay.example.com/"], + ["challenge", "some-challenge-string"] + ], + "content": "", + "sig": "b27181e0b72872c463ac75ebf3ad2c2502696d81551bbae6b2a391c67614daf2e321eb5ab724b04520b26fcf7a4c9823fefdb47b10d66c088db44162ba9c1291" +} diff --git a/Tests/NostrSDKTests/Fixtures/closed.json b/Tests/NostrSDKTests/Fixtures/closed.json new file mode 100644 index 0000000..f9e50db --- /dev/null +++ b/Tests/NostrSDKTests/Fixtures/closed.json @@ -0,0 +1 @@ +["CLOSED", "some-subscription-id", "error: shutting down idle subscription"] diff --git a/Tests/NostrSDKTests/Fixtures/ok_success_reason.json b/Tests/NostrSDKTests/Fixtures/ok_success_reason.json index 699a640..1e1396b 100644 --- a/Tests/NostrSDKTests/Fixtures/ok_success_reason.json +++ b/Tests/NostrSDKTests/Fixtures/ok_success_reason.json @@ -1 +1 @@ -["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", true, "pow: difficulty 25>=24"] +["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", true, "pow: difficulty: 25>=24"] diff --git a/Tests/NostrSDKTests/Fixtures/ok_success_reason_prefix_no_message.json b/Tests/NostrSDKTests/Fixtures/ok_success_reason_prefix_no_message.json new file mode 100644 index 0000000..ea7cae2 --- /dev/null +++ b/Tests/NostrSDKTests/Fixtures/ok_success_reason_prefix_no_message.json @@ -0,0 +1 @@ +["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", true, "pow:"] diff --git a/Tests/NostrSDKTests/Fixtures/ok_unknown_reason.json b/Tests/NostrSDKTests/Fixtures/ok_unknown_reason.json new file mode 100644 index 0000000..e9b5cfd --- /dev/null +++ b/Tests/NostrSDKTests/Fixtures/ok_unknown_reason.json @@ -0,0 +1 @@ +["OK", "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30", true, "unknown: reason: unknown"] diff --git a/Tests/NostrSDKTests/RelayRequestEncodingTests.swift b/Tests/NostrSDKTests/RelayRequestEncodingTests.swift index 43bfbb9..6f5950a 100644 --- a/Tests/NostrSDKTests/RelayRequestEncodingTests.swift +++ b/Tests/NostrSDKTests/RelayRequestEncodingTests.swift @@ -8,14 +8,7 @@ @testable import NostrSDK import XCTest -final class RelayRequestEncodingTests: XCTestCase, FixtureLoading, JSONTesting { - - func testEncodeClose() throws { - let request = try XCTUnwrap(RelayRequest.close(subscriptionId: "some-subscription-id"), "failed to encode request") - let expected = try loadFixtureString("close_request") - - XCTAssertEqual(request.encoded, expected) - } +final class RelayRequestEncodingTests: XCTestCase, EventCreating, FixtureLoading, JSONTesting { func testEncodeEvent() throws { let eventTag = Tag.event("93930d65435d49db723499335473920795e7f13c45600dcfad922135cf44bd63") @@ -34,7 +27,7 @@ final class RelayRequestEncodingTests: XCTestCase, FixtureLoading, JSONTesting { XCTAssertTrue(areEquivalentJSONArrayStrings(request.encoded, expected)) } - func testEncodeCount() throws { + func testEncodeReq() throws { let filter = Filter(ids: nil, authors: ["some-pubkey"], kinds: [1, 7], @@ -44,13 +37,28 @@ final class RelayRequestEncodingTests: XCTestCase, FixtureLoading, JSONTesting { until: nil, limit: nil) - let request = try XCTUnwrap(RelayRequest.count(subscriptionId: "some-subscription-id", filter: filter), "failed to encode request") - let expected = try loadFixtureString("count_request") + let request = try XCTUnwrap(RelayRequest.request(subscriptionId: "some-subscription-id", filter: filter), "failed to encode request") + let expected = try loadFixtureString("req") XCTAssertTrue(areEquivalentJSONArrayStrings(request.encoded, expected)) } - func testEncodeReq() throws { + func testEncodeClose() throws { + let request = try XCTUnwrap(RelayRequest.close(subscriptionId: "some-subscription-id"), "failed to encode request") + let expected = try loadFixtureString("close_request") + + XCTAssertEqual(request.encoded, expected) + } + + func testEncodeAuth() throws { + let authenticationEvent: AuthenticationEvent = try decodeFixture(filename: "authentication_event") + let request = try XCTUnwrap(RelayRequest.auth(authenticationEvent)) + let expected = try loadFixtureString("auth_request") + + XCTAssertTrue(areEquivalentJSONArrayStrings(request.encoded, expected)) + } + + func testEncodeCount() throws { let filter = Filter(ids: nil, authors: ["some-pubkey"], kinds: [1, 7], @@ -60,8 +68,8 @@ final class RelayRequestEncodingTests: XCTestCase, FixtureLoading, JSONTesting { until: nil, limit: nil) - let request = try XCTUnwrap(RelayRequest.request(subscriptionId: "some-subscription-id", filter: filter), "failed to encode request") - let expected = try loadFixtureString("req") + let request = try XCTUnwrap(RelayRequest.count(subscriptionId: "some-subscription-id", filter: filter), "failed to encode request") + let expected = try loadFixtureString("count_request") XCTAssertTrue(areEquivalentJSONArrayStrings(request.encoded, expected)) } diff --git a/Tests/NostrSDKTests/RelayResponseDecodingTests.swift b/Tests/NostrSDKTests/RelayResponseDecodingTests.swift index 1221f90..6d9f0e5 100644 --- a/Tests/NostrSDKTests/RelayResponseDecodingTests.swift +++ b/Tests/NostrSDKTests/RelayResponseDecodingTests.swift @@ -10,128 +10,145 @@ import XCTest final class RelayResponseDecodingTests: XCTestCase, FixtureLoading { - func testDecodeNoticeMessage() throws { - let data = try loadFixtureData("notice") + func testDecodeEventMessage() throws { + let data = try loadFixtureData("event") - if let relayResponse = RelayResponse.decode(data: data) { - guard case .notice(let message) = relayResponse else { - XCTFail("incorrect type") - return - } - XCTAssertEqual(message, "there was an error") - } else { - XCTFail("failed to decode") + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .event(let subscriptionId, let event) = relayResponse else { + XCTFail("incorrect type") + return } + XCTAssertEqual(subscriptionId, "some-subscription-id") + XCTAssertNotNil(event) + XCTAssertTrue(event is TextNoteEvent) + XCTAssertEqual(event.id, "fa5ed84fc8eeb959fd39ad8e48388cfc33075991ef8e50064cfcecfd918bb91b") } - func testDecodeEOSEMessage() throws { - let data = try loadFixtureData("eose") + func testDecodeOkMessage() throws { + let data = try loadFixtureData("ok_success") - if let relayResponse = RelayResponse.decode(data: data) { - guard case .eose(let subscriptionId) = relayResponse else { - XCTFail("incorrect type") - return - } - XCTAssertEqual(subscriptionId, "some-subscription-id") - } else { - XCTFail("failed to decode") + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .ok(let eventId, let success, let message) = relayResponse else { + XCTFail("incorrect type") + return } + XCTAssertEqual(eventId, "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30") + XCTAssertEqual(success, true) + XCTAssertEqual(message.prefix, .unknown) + XCTAssertEqual(message.message, "") } - func testDecodeEventMessage() throws { - let data = try loadFixtureData("event") + func testDecodeOkMessageWithReason() throws { + let data = try loadFixtureData("ok_success_reason") - if let relayResponse = RelayResponse.decode(data: data) { - guard case .event(let subscriptionId, let event) = relayResponse else { - XCTFail("incorrect type") - return - } - XCTAssertEqual(subscriptionId, "some-subscription-id") - XCTAssertNotNil(event) - XCTAssertTrue(event is TextNoteEvent) - XCTAssertEqual(event.id, "fa5ed84fc8eeb959fd39ad8e48388cfc33075991ef8e50064cfcecfd918bb91b") - } else { - XCTFail("failed to decode") + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .ok(let eventId, let success, let message) = relayResponse else { + XCTFail("incorrect type") + return } + XCTAssertEqual(eventId, "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30") + XCTAssertEqual(success, true) + XCTAssertEqual(message.prefix, .pow) + XCTAssertEqual(message.message, "difficulty: 25>=24") } - func testDecodeOkMessage() throws { - let data = try loadFixtureData("ok_success") + func testDecodeOkMessageWithReasonPrefixNoMessage() throws { + let data = try loadFixtureData("ok_success_reason_prefix_no_message") - if let relayResponse = RelayResponse.decode(data: data) { - guard case .ok(let eventId, let success, let message) = relayResponse else { - XCTFail("incorrect type") - return - } - XCTAssertEqual(eventId, "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30") - XCTAssertEqual(success, true) - XCTAssertEqual(message.type, .unknown) - XCTAssertNil(message.message) - } else { - XCTFail("failed to decode") + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .ok(let eventId, let success, let message) = relayResponse else { + XCTFail("incorrect type") + return } + XCTAssertEqual(eventId, "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30") + XCTAssertEqual(success, true) + XCTAssertEqual(message.prefix, .pow) + XCTAssertEqual(message.message, "") } - func testDecodeOkMessageWithReason() throws { - let data = try loadFixtureData("ok_success_reason") + func testDecodeOkMessageWithUnknownReason() throws { + let data = try loadFixtureData("ok_unknown_reason") - if let relayResponse = RelayResponse.decode(data: data) { - guard case .ok(let eventId, let success, let message) = relayResponse else { - XCTFail("incorrect type") - return - } - XCTAssertEqual(eventId, "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30") - XCTAssertEqual(success, true) - XCTAssertEqual(message.type, .pow) - XCTAssertEqual(message.message, "difficulty 25>=24") - } else { - XCTFail("failed to decode") + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .ok(let eventId, let success, let message) = relayResponse else { + XCTFail("incorrect type") + return } + XCTAssertEqual(eventId, "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30") + XCTAssertEqual(success, true) + XCTAssertEqual(message.prefix, .unknown) + XCTAssertEqual(message.message, "unknown: reason: unknown") } func testDecodeOkFailWithReason() throws { let data = try loadFixtureData("ok_fail_reason") - if let relayResponse = RelayResponse.decode(data: data) { - guard case .ok(let eventId, let success, let message) = relayResponse else { - XCTFail("incorrect type") - return - } - XCTAssertEqual(eventId, "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30") - XCTAssertEqual(success, false) - XCTAssertEqual(message.type, .blocked) - XCTAssertEqual(message.message, "tor exit nodes not allowed") - } else { - XCTFail("failed to decode") + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .ok(let eventId, let success, let message) = relayResponse else { + XCTFail("incorrect type") + return } + XCTAssertEqual(eventId, "b1a649ebe8b435ec71d3784793f3bbf4b93e64e17568a741aecd4c7ddeafce30") + XCTAssertEqual(success, false) + XCTAssertEqual(message.prefix, .blocked) + XCTAssertEqual(message.message, "tor exit nodes not allowed") } - func testDecodeCount() throws { - let data = try loadFixtureData("count_response") + func testDecodeEOSEMessage() throws { + let data = try loadFixtureData("eose") + + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .eose(let subscriptionId) = relayResponse else { + XCTFail("incorrect type") + return + } + XCTAssertEqual(subscriptionId, "some-subscription-id") + } + + func testDecodeClosedMessage() throws { + let data = try loadFixtureData("closed") + + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .closed(let subscriptionId, let message) = relayResponse else { + XCTFail("incorrect type") + return + } + XCTAssertEqual(subscriptionId, "some-subscription-id") + XCTAssertEqual(message.prefix, .error) + XCTAssertEqual(message.message, "shutting down idle subscription") + } - if let relayResponse = RelayResponse.decode(data: data) { - guard case .count(let subscriptionId, let count) = relayResponse else { - XCTFail("incorrect type") - return - } - XCTAssertEqual(subscriptionId, "subscription-id") - XCTAssertEqual(count, 238) - } else { - XCTFail("failed to decode") + func testDecodeNoticeMessage() throws { + let data = try loadFixtureData("notice") + + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .notice(let message) = relayResponse else { + XCTFail("incorrect type") + return } + XCTAssertEqual(message, "there was an error") } func testDecodeAuthChallenge() throws { let data = try loadFixtureData("auth_challenge") - if let relayResponse = RelayResponse.decode(data: data) { - guard case .auth(let challenge) = relayResponse else { - XCTFail("incorrect type") - return - } - XCTAssertEqual(challenge, "some-challenge-string") - } else { - XCTFail("failed to decode") + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .auth(let challenge) = relayResponse else { + XCTFail("incorrect type") + return + } + XCTAssertEqual(challenge, "some-challenge-string") + } + + func testDecodeCount() throws { + let data = try loadFixtureData("count_response") + + let relayResponse = try XCTUnwrap(RelayResponse.decode(data: data)) + guard case .count(let subscriptionId, let count) = relayResponse else { + XCTFail("incorrect type") + return } + XCTAssertEqual(subscriptionId, "subscription-id") + XCTAssertEqual(count, 238) } }