Skip to content

Commit

Permalink
[V3] Fix up all the tests (#421)
Browse files Browse the repository at this point in the history
* update all the tests and get rid of bob and alice

* get more tests passing

* more test tweaks

* fix up all the tests

* [V3] Update Example App (#422)

* update the readme and the example app

* get the example app working

* fix up lint
  • Loading branch information
nplasterer authored Nov 8, 2024
1 parent 1862832 commit 7c43c30
Show file tree
Hide file tree
Showing 36 changed files with 2,099 additions and 3,573 deletions.
433 changes: 121 additions & 312 deletions README.md

Large diffs are not rendered by default.

213 changes: 84 additions & 129 deletions Sources/XMTPTestHelpers/TestHelpers.swift
Original file line number Diff line number Diff line change
@@ -1,145 +1,100 @@
//
// TestHelpers.swift
//
//
// Created by Pat Nakajima on 12/6/22.
//

#if canImport(XCTest)
import Combine
import CryptoKit
import XCTest
@testable import XMTPiOS
import LibXMTP

public struct TestConfig {
static let TEST_SERVER_ENABLED = _env("TEST_SERVER_ENABLED") == "true"
// TODO: change Client constructor to accept these explicitly (so we can config CI):
// static let TEST_SERVER_HOST = _env("TEST_SERVER_HOST") ?? "127.0.0.1"
// static let TEST_SERVER_PORT = Int(_env("TEST_SERVER_PORT")) ?? 5556
// static let TEST_SERVER_IS_SECURE = _env("TEST_SERVER_IS_SECURE") == "true"

static private func _env(_ key: String) -> String? {
ProcessInfo.processInfo.environment[key]
}

static public func skipIfNotRunningLocalNodeTests() throws {
try XCTSkipIf(!TEST_SERVER_ENABLED, "requires local node")
}

static public func skip(because: String) throws {
try XCTSkipIf(true, because)
}
}

// Helper for tests gathering transcripts in a background task.
public actor TestTranscript {
public var messages: [String] = []
public init() {}
public func add(_ message: String) {
messages.append(message)
}
}

public struct FakeWallet: SigningKey {
public static func generate() throws -> FakeWallet {
let key = try PrivateKey.generate()
return FakeWallet(key)
import Combine
import CryptoKit
import XCTest
@testable import XMTPiOS
import LibXMTP

public struct TestConfig {
static let TEST_SERVER_ENABLED = _env("TEST_SERVER_ENABLED") == "true"
// TODO: change Client constructor to accept these explicitly (so we can config CI):
// static let TEST_SERVER_HOST = _env("TEST_SERVER_HOST") ?? "127.0.0.1"
// static let TEST_SERVER_PORT = Int(_env("TEST_SERVER_PORT")) ?? 5556
// static let TEST_SERVER_IS_SECURE = _env("TEST_SERVER_IS_SECURE") == "true"

static private func _env(_ key: String) -> String? {
ProcessInfo.processInfo.environment[key]
}

static public func skipIfNotRunningLocalNodeTests() throws {
try XCTSkipIf(!TEST_SERVER_ENABLED, "requires local node")
}

static public func skip(because: String) throws {
try XCTSkipIf(true, because)
}
}

public var address: String {
key.walletAddress
// Helper for tests gathering transcripts in a background task.
public actor TestTranscript {
public var messages: [String] = []
public init() {}
public func add(_ message: String) {
messages.append(message)
}
}

public func sign(_ data: Data) async throws -> XMTPiOS.Signature {
let signature = try await key.sign(data)
return signature
}
public struct FakeWallet: SigningKey {
public static func generate() throws -> FakeWallet {
let key = try PrivateKey.generate()
return FakeWallet(key)
}

public func sign(message: String) async throws -> XMTPiOS.Signature {
let signature = try await key.sign(message: message)
return signature
}
public var address: String {
key.walletAddress
}

public var key: PrivateKey
public func sign(_ data: Data) async throws -> XMTPiOS.Signature {
let signature = try await key.sign(data)
return signature
}

public init(_ key: PrivateKey) {
self.key = key
}
}

public struct FakeSCWWallet: SigningKey {
public var walletAddress: String
private var internalSignature: String

public init() throws {
// Simulate a wallet address (could be derived from a hash of some internal data)
self.walletAddress = UUID().uuidString // Using UUID for uniqueness in this fake example
self.internalSignature = Data(repeating: 0x01, count: 64).toHex // Fake internal signature
}

public var address: String {
walletAddress
}
public func sign(message: String) async throws -> XMTPiOS.Signature {
let signature = try await key.sign(message: message)
return signature
}

public var type: WalletType {
WalletType.SCW
}

public var chainId: Int64? {
1
}

public static func generate() throws -> FakeSCWWallet {
return try FakeSCWWallet()
}

public func signSCW(message: String) async throws -> Data {
// swiftlint:disable force_unwrapping
let digest = SHA256.hash(data: message.data(using: .utf8)!)
// swiftlint:enable force_unwrapping
return Data(digest)
}
}

@available(iOS 15, *)
public struct Fixtures {
public var alice: PrivateKey!
public var aliceClient: Client!

public var bob: PrivateKey!
public var bobClient: Client!
public let clientOptions: ClientOptions? = ClientOptions(
api: ClientOptions.Api(env: XMTPEnvironment.local, isSecure: false)
)

init() async throws {
alice = try PrivateKey.generate()
bob = try PrivateKey.generate()
public var key: PrivateKey

aliceClient = try await Client.create(account: alice, options: clientOptions)
bobClient = try await Client.create(account: bob, options: clientOptions)
public init(_ key: PrivateKey) {
self.key = key
}
}

public func publishLegacyContact(client: Client) async throws {
var contactBundle = ContactBundle()
contactBundle.v1.keyBundle = try client.v1keys.toPublicKeyBundle()

var envelope = Envelope()
envelope.contentTopic = Topic.contact(client.address).description
envelope.timestampNs = UInt64(Date().millisecondsSinceEpoch * 1_000_000)
envelope.message = try contactBundle.serializedData()

try await client.publish(envelopes: [envelope])
@available(iOS 15, *)
public struct Fixtures {
public var alix: PrivateKey!
public var alixClient: Client!
public var bo: PrivateKey!
public var boClient: Client!
public var caro: PrivateKey!
public var caroClient: Client!

init() async throws {
alix = try PrivateKey.generate()
bo = try PrivateKey.generate()
caro = try PrivateKey.generate()

let key = try Crypto.secureRandomBytes(count: 32)
let clientOptions: ClientOptions = ClientOptions(
api: ClientOptions.Api(
env: XMTPEnvironment.local, isSecure: false),
dbEncryptionKey: key
)

alixClient = try await Client.create(
account: alix, options: clientOptions)
boClient = try await Client.create(
account: bo, options: clientOptions)
caroClient = try await Client.create(
account: caro, options: clientOptions)
}
}
}

public extension XCTestCase {
@available(iOS 15, *)
func fixtures() async -> Fixtures {
// swiftlint:disable force_try
return try! await Fixtures()
// swiftlint:enable force_try
extension XCTestCase {
@available(iOS 15, *)
public func fixtures() async throws -> Fixtures {
return try await Fixtures()
}
}
}
#endif
4 changes: 2 additions & 2 deletions Sources/XMTPiOS/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ public struct ClientOptions {
api: Api = Api(),
codecs: [any ContentCodec] = [],
preAuthenticateToInboxCallback: PreEventCallback? = nil,
encryptionKey: Data,
dbEncryptionKey: Data,
dbDirectory: String? = nil,
historySyncUrl: String? = nil
) {
self.api = api
self.codecs = codecs
self.preAuthenticateToInboxCallback = preAuthenticateToInboxCallback
self.dbEncryptionKey = encryptionKey
self.dbEncryptionKey = dbEncryptionKey
self.dbDirectory = dbDirectory
if historySyncUrl == nil {
switch api.env {
Expand Down
32 changes: 14 additions & 18 deletions Sources/XMTPiOS/Conversation.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import Foundation
import LibXMTP

public enum Conversation {
public enum Conversation: Identifiable, Equatable, Hashable {
case group(Group)
case dm(Dm)

public static func == (lhs: Conversation, rhs: Conversation) -> Bool {
lhs.topic == rhs.topic
}

public func hash(into hasher: inout Hasher) {
hasher.combine(topic)
}

public enum ConversationType {
case group, dm
}

public var id: String {
get throws {
switch self {
case let .group(group):
return group.id
case let .dm(dm):
return dm.id
}
switch self {
case let .group(group):
return group.id
case let .dm(dm):
return dm.id
}
}

Expand Down Expand Up @@ -189,13 +195,3 @@ public enum Conversation {
}
}
}

extension Conversation: Hashable, Equatable {
public static func == (lhs: Conversation, rhs: Conversation) -> Bool {
lhs.topic == rhs.topic
}

public func hash(into hasher: inout Hasher) {
hasher.combine(topic)
}
}
4 changes: 2 additions & 2 deletions Sources/XMTPiOS/PrivatePreferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ public class ConsentList {
).fromFFI
}

func conversationState(groupId: String) async throws -> ConsentState {
func conversationState(conversationId: String) async throws -> ConsentState {
return try await ffiClient.getConsentState(
entityType: .conversationId,
entity: groupId
entity: conversationId
).fromFFI
}

Expand Down
27 changes: 14 additions & 13 deletions Tests/XMTPTests/AttachmentTests.swift
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
//
// AttachmentsTests.swift
//
//
// Created by Pat on 2/14/23.
//
import Foundation

import XCTest

@testable import XMTPiOS

@available(iOS 15, *)
class AttachmentsTests: XCTestCase {
func testCanUseAttachmentCodec() async throws {
// swiftlint:disable force_try
let iconData = Data(base64Encoded: Data("iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAhGVYSWZNTQAqAAAACAAFARIAAwAAAAEAAQAAARoABQAAAAEAAABKARsABQAAAAEAAABSASgAAwAAAAEAAgAAh2kABAAAAAEAAABaAAAAAAAAAEgAAAABAAAASAAAAAEAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAADHbxzxAAAACXBIWXMAAAsTAAALEwEAmpwYAAACymlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8dGlmZjpZUmVzb2x1dGlvbj43MjwvdGlmZjpZUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6WFJlc29sdXRpb24+NzI8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj40NjA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjQ2MDwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgr0TTmKAAAC5ElEQVQ4EW2TWWxMYRTHf3e2zjClVa0trVoqFRk1VKmIWhJ0JmkNETvvEtIHLwixxoM1xIOIiAjzxhBCQ9ESlRJNJEj7gJraJ63SdDrbvc53Z6xx7r253/lyzvnO/3/+n7a69KTBnyae1anJZ0nviq9pkIzppKLK+TMYbH+74Bhsobslzmv6yJQgJUHFuMiryCL+Tf8r5XcBqWxzWWhv+c6cDSPYsm4ehWPy5XSNd28j3Aw+49apMOO92aT6pRN5lf0qoJI7nvay4/JcFi+ZTiKepLPjC4ahM3VGCZVVk6iqaWWv/w5F3gEkFRyzgPxV221y8s5L6eSbocdUB25QhFUeBE6C0MWF1K6aReqqzs6aBkorBhHv0bEpwr4K5tlrhrM4MJ36K084HXhEfcjH/WvtJBM685dO5MymRyacmpWVNKx7Sdv5LrLL7FhU64ow//rJxGMJTix5QP4CF/P9Xjbv81F3wM8CWQ/1uDixqpn+aJzqtR5eSY6alMUQCIrXwuJ8PrzrokfaDTf0cnhbiPxhOQwbkcvBrZd5e/07SYl83xmhaGyBgm/az0ll3DQxulCc5fzFr7nuIs5Dotjtsm8emo61KZEobXS+iTCzaiJuGUxJTQ51u2t5H46QTKao21NL9+cgG6cNl04LCJ6+xxDsGCkDqyfPt2vgJyvdWg+LlgvWMhvNFzpwF2sEjzdzO/iCyurx+FaU45k2hicP2zgSaGLUFBlln4FNiSKnwkHT+Y/UL31sTkLXDdHCdSbIKVHp90PBWRbuH0dPJMrdo2EKSp3osQwE1b+SZ4nXzYFAI1pIw7esgv5+b0ZIBucONXJ2+3NG4mTk1AFyJ4QlxbzkWj1D/bsUg7oIfkihg0vH2nkVfoM7105untsk7UVrmL7WGLnlWSR6M3dBESem/XsbHYMsdLXERBtRU4UqaFz2QJyjbRgJaTuTqPaV/Z5V2jflObjMQbnLKW2mcSaErP8lq5QfTHkZ9teKBsUAAAAASUVORK5CYII=".utf8))!
let fixtures = await fixtures()
let conversation = try await fixtures.aliceClient.conversations.newConversation(with: fixtures.bobClient.address)
let iconData = Data(
base64Encoded: Data(
"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"
.utf8))!
let fixtures = try await fixtures()
let conversation = try await fixtures.alixClient.conversations
.newConversation(with: fixtures.boClient.address)

fixtures.aliceClient.register(codec: AttachmentCodec())
fixtures.alixClient.register(codec: AttachmentCodec())

try await conversation.send(content: Attachment(filename: "icon.png", mimeType: "image/png", data: iconData), options: .init(contentType: ContentTypeAttachment))
try await conversation.send(
content: Attachment(
filename: "icon.png", mimeType: "image/png", data: iconData),
options: .init(contentType: ContentTypeAttachment))
let messages = try await conversation.messages()

XCTAssertEqual(1, messages.count)
XCTAssertEqual(2, messages.count)

let message = messages[0]
let attachment: Attachment = try message.content()
Expand Down
Loading

0 comments on commit 7c43c30

Please sign in to comment.