diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 193f46cb..236752fd 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -66,6 +66,54 @@ jobs: - name: Cancel workflow runs for commit on error if: failure() uses: ./.github/.release/actions/actions/utils/fast-jobs-failure + integration-tests: + name: Integration tests + env: + SDK_PUB_KEY: ${{ secrets.SDK_PUB_KEY }} + SDK_SUB_KEY: ${{ secrets.SDK_SUB_KEY }} + runs-on: + group: macos-gh + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_TOKEN }} + - name: Checkout actions + uses: actions/checkout@v4 + with: + repository: pubnub/client-engineering-deployment-tools + ref: v1 + token: ${{ secrets.GH_TOKEN }} + path: .github/.release/actions + - name: Setup Ruby 3.2.2 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2.2 + bundler-cache: true + - name: Cache installed Pods + uses: actions/cache@v4 + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: Cache Swift Package Manager + uses: actions/cache@v4 + with: + path: | + .build + ~/Library/Developer/Xcode/DerivedData/**/SourcePackages/checkouts + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + restore-keys: | + ${{ runner.os }}-spm- + - name: Pre-load simulators list + run: xcrun simctl list -j + - name: Run integration tests + run: bundle exec fastlane integration_test + - name: Cancel workflow runs for commit on error + if: failure() + uses: ./.github/.release/actions/actions/utils/fast-jobs-failure acceptance-tests: name: Acceptance tests runs-on: @@ -131,7 +179,7 @@ jobs: retention-days: 7 all-tests: name: Tests - needs: [tests, acceptance-tests] + needs: [tests, integration-tests, acceptance-tests] runs-on: group: organization/Default steps: diff --git a/PubNub.xcodeproj/project.pbxproj b/PubNub.xcodeproj/project.pbxproj index 5233ec64..40fc6807 100644 --- a/PubNub.xcodeproj/project.pbxproj +++ b/PubNub.xcodeproj/project.pbxproj @@ -143,7 +143,7 @@ 355E1E6923458BC10094D3E0 /* objects_members_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 355E1E6823458BC10094D3E0 /* objects_members_success.json */; }; 355F213722DECFCD004DEFBF /* Typealias+PubNub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 355F213622DECFCD004DEFBF /* Typealias+PubNub.swift */; }; 3562DBB923428961006DFFBC /* objects_uuid_remove_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 3562DBB823428961006DFFBC /* objects_uuid_remove_success.json */; }; - 3562DBBD23429798006DFFBC /* UUIDObjectsEndpointIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3562DBBA234294D5006DFFBC /* UUIDObjectsEndpointIntegrationTests.swift */; }; + 3562DBBD23429798006DFFBC /* UserObjectsEndpointIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3562DBBA234294D5006DFFBC /* UserObjectsEndpointIntegrationTests.swift */; }; 3562DBC1234408DC006DFFBC /* ChannelObjectsEndpointIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3562DBC0234408DC006DFFBC /* ChannelObjectsEndpointIntegrationTests.swift */; }; 3562DBC523450D6F006DFFBC /* objects_channel_all_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 3562DBC423450D6E006DFFBC /* objects_channel_all_success.json */; }; 3562DBC923450D98006DFFBC /* objects_channel_fetch_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 3562DBC823450D98006DFFBC /* objects_channel_fetch_success.json */; }; @@ -187,7 +187,6 @@ 358C6421238C6787009CE354 /* PubNubPushMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358C6420238C6787009CE354 /* PubNubPushMessage.swift */; }; 359152A122BA9AA30048842D /* PubNubConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359152A022BA9AA30048842D /* PubNubConfiguration.swift */; }; 359152AB22BAA6730048842D /* PubNubConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359152AA22BAA6730048842D /* PubNubConfigurationTests.swift */; }; - 359287B02316EC580046F7A2 /* OnboardingSnippets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C829D723145AFD00F59D3C /* OnboardingSnippets.swift */; }; 359287B82317294B0046F7A2 /* PublishEndpointIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 359287B5231727EC0046F7A2 /* PublishEndpointIntegrationTests.swift */; }; 359287BF23183DC20046F7A2 /* subscription_signal_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 359287BE23183DBB0046F7A2 /* subscription_signal_success.json */; }; 359287C123183E530046F7A2 /* subscription_presence_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 359287C023183E4A0046F7A2 /* subscription_presence_success.json */; }; @@ -365,8 +364,10 @@ 35FE941222EFB70B0051C455 /* unrecognizedEndpointError.json in Resources */ = {isa = PBXBuildFile; fileRef = 35FE941122EFB70B0051C455 /* unrecognizedEndpointError.json */; }; 35FE941422EFB7C10051C455 /* unknownEndpointError.json in Resources */ = {isa = PBXBuildFile; fileRef = 35FE941322EFB7C10051C455 /* unknownEndpointError.json */; }; 35FE941F22F0929A0051C455 /* RequestRetrierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FE941E22F0929A0051C455 /* RequestRetrierTests.swift */; }; + 3D0934362DD33A74007132B1 /* FilesEndpointIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D0934352DD33A74007132B1 /* FilesEndpointIntegrationTests.swift */; }; 3D1C4FF62C074BDB0030B3D6 /* KMPHashedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1C4FF52C074BDB0030B3D6 /* KMPHashedPage.swift */; }; 3D2642942C3BE835000154EC /* Dictionary+ObjCRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2642932C3BE835000154EC /* Dictionary+ObjCRepresentable.swift */; }; + 3D2AF8BD2DD72CEB009303B6 /* HistoryEndpointIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2AF8BC2DD72CEB009303B6 /* HistoryEndpointIntegrationTests.swift */; }; 3D339C782BFF826500197342 /* KMPEventListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D339C742BFF826500197342 /* KMPEventListener.swift */; }; 3D339C792BFF826500197342 /* KMPFetchMessagesResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D339C752BFF826500197342 /* KMPFetchMessagesResult.swift */; }; 3D339C7A2BFF826500197342 /* KMPPresenceChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D339C762BFF826500197342 /* KMPPresenceChange.swift */; }; @@ -452,7 +453,9 @@ 3DA24A412C2AAB23005B959B /* KMPPubNub+ChannelGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DA24A402C2AAB23005B959B /* KMPPubNub+ChannelGroups.swift */; }; 3DA24A432C2AAB54005B959B /* KMPPubNub+AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DA24A422C2AAB54005B959B /* KMPPubNub+AppContext.swift */; }; 3DA24A452C2AABC0005B959B /* KMPPubNub+Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DA24A442C2AABC0005B959B /* KMPPubNub+Files.swift */; }; + 3DAB7C512DD5047F0003D693 /* XCTestCase+Integration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAB7C502DD5047F0003D693 /* XCTestCase+Integration.swift */; }; 3DACC7F72AB88F8E00210B14 /* Data+CommonCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DACC7F62AB88F8E00210B14 /* Data+CommonCrypto.swift */; }; + 3DADDF972DCB4E8E001564C3 /* ChannelGroupEndpointIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DADDF962DCB4E8E001564C3 /* ChannelGroupEndpointIntegrationTests.swift */; }; 3DB2C4872C0F4B250060B8CF /* KMPStatusListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB2C4862C0F4B250060B8CF /* KMPStatusListener.swift */; }; 3DB692522D555AA5006702DC /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB692512D555AA5006702DC /* Logger.swift */; }; 3DB9255C2B7A2B89001B7E90 /* SubscriptionStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925592B7A2B89001B7E90 /* SubscriptionStreamTests.swift */; }; @@ -757,7 +760,7 @@ 355E1E6823458BC10094D3E0 /* objects_members_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = objects_members_success.json; sourceTree = ""; }; 355F213622DECFCD004DEFBF /* Typealias+PubNub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Typealias+PubNub.swift"; sourceTree = ""; }; 3562DBB823428961006DFFBC /* objects_uuid_remove_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = objects_uuid_remove_success.json; sourceTree = ""; }; - 3562DBBA234294D5006DFFBC /* UUIDObjectsEndpointIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UUIDObjectsEndpointIntegrationTests.swift; sourceTree = ""; }; + 3562DBBA234294D5006DFFBC /* UserObjectsEndpointIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserObjectsEndpointIntegrationTests.swift; sourceTree = ""; }; 3562DBC0234408DC006DFFBC /* ChannelObjectsEndpointIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelObjectsEndpointIntegrationTests.swift; sourceTree = ""; }; 3562DBC22345066F006DFFBC /* ObjectsChannelRouterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectsChannelRouterTests.swift; sourceTree = ""; }; 3562DBC423450D6E006DFFBC /* objects_channel_all_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = objects_channel_all_success.json; sourceTree = ""; }; @@ -887,7 +890,6 @@ 35C6B6DC22F501780054F242 /* Encodable+PubNub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Encodable+PubNub.swift"; sourceTree = ""; }; 35C6B6DF22F513D80054F242 /* subscription_mixed_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscription_mixed_success.json; sourceTree = ""; }; 35C6B6E522F51A060054F242 /* AnyJSONType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyJSONType.swift; sourceTree = ""; }; - 35C829D723145AFD00F59D3C /* OnboardingSnippets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSnippets.swift; sourceTree = ""; }; 35C829DB23147AC000F59D3C /* SubscriptionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionState.swift; sourceTree = ""; }; 35C8FDCA25003A9F0069E89E /* file_generateURL_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = file_generateURL_success.json; sourceTree = ""; }; 35CA8FFF285A5DAC007C2F65 /* PubNubUserIntTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PubNubUserIntTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -992,8 +994,10 @@ 35FE941122EFB70B0051C455 /* unrecognizedEndpointError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = unrecognizedEndpointError.json; sourceTree = ""; }; 35FE941322EFB7C10051C455 /* unknownEndpointError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = unknownEndpointError.json; sourceTree = ""; }; 35FE941E22F0929A0051C455 /* RequestRetrierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestRetrierTests.swift; sourceTree = ""; }; + 3D0934352DD33A74007132B1 /* FilesEndpointIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesEndpointIntegrationTests.swift; sourceTree = ""; }; 3D1C4FF52C074BDB0030B3D6 /* KMPHashedPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPHashedPage.swift; sourceTree = ""; }; 3D2642932C3BE835000154EC /* Dictionary+ObjCRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+ObjCRepresentable.swift"; sourceTree = ""; }; + 3D2AF8BC2DD72CEB009303B6 /* HistoryEndpointIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryEndpointIntegrationTests.swift; sourceTree = ""; }; 3D339C742BFF826500197342 /* KMPEventListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KMPEventListener.swift; sourceTree = ""; }; 3D339C752BFF826500197342 /* KMPFetchMessagesResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KMPFetchMessagesResult.swift; sourceTree = ""; }; 3D339C762BFF826500197342 /* KMPPresenceChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KMPPresenceChange.swift; sourceTree = ""; }; @@ -1075,7 +1079,9 @@ 3DA24A402C2AAB23005B959B /* KMPPubNub+ChannelGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KMPPubNub+ChannelGroups.swift"; sourceTree = ""; }; 3DA24A422C2AAB54005B959B /* KMPPubNub+AppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KMPPubNub+AppContext.swift"; sourceTree = ""; }; 3DA24A442C2AABC0005B959B /* KMPPubNub+Files.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KMPPubNub+Files.swift"; sourceTree = ""; }; + 3DAB7C502DD5047F0003D693 /* XCTestCase+Integration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Integration.swift"; sourceTree = ""; }; 3DACC7F62AB88F8E00210B14 /* Data+CommonCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+CommonCrypto.swift"; sourceTree = ""; }; + 3DADDF962DCB4E8E001564C3 /* ChannelGroupEndpointIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelGroupEndpointIntegrationTests.swift; sourceTree = ""; }; 3DB2C4862C0F4B250060B8CF /* KMPStatusListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPStatusListener.swift; sourceTree = ""; }; 3DB692512D555AA5006702DC /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 3DB925592B7A2B89001B7E90 /* SubscriptionStreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionStreamTests.swift; sourceTree = ""; }; @@ -1383,15 +1389,17 @@ 3558073823145A48005CDD92 /* Integration */ = { isa = PBXGroup; children = ( - 35293AA1236B51A00049A71F /* PubNub+Integration.swift */, - 35C829D723145AFD00F59D3C /* OnboardingSnippets.swift */, 359287B5231727EC0046F7A2 /* PublishEndpointIntegrationTests.swift */, - 3562DBBA234294D5006DFFBC /* UUIDObjectsEndpointIntegrationTests.swift */, - 35293A99236A1DE70049A71F /* MessageActionsEndpointIntegrationTests.swift */, + 3562DBBA234294D5006DFFBC /* UserObjectsEndpointIntegrationTests.swift */, 3562DBC0234408DC006DFFBC /* ChannelObjectsEndpointIntegrationTests.swift */, + 35293A99236A1DE70049A71F /* MessageActionsEndpointIntegrationTests.swift */, 359287C6232712520046F7A2 /* PresenceEndpointIntegrationTests.swift */, 353D92A92356244100367B9F /* SubscriptionIntegrationTests.swift */, 3557CE022386054C004BBACC /* PushIntegrationTests.swift */, + 3DADDF962DCB4E8E001564C3 /* ChannelGroupEndpointIntegrationTests.swift */, + 3D0934352DD33A74007132B1 /* FilesEndpointIntegrationTests.swift */, + 3D2AF8BC2DD72CEB009303B6 /* HistoryEndpointIntegrationTests.swift */, + 3DADDF942DCB4776001564C3 /* Helpers */, ); path = Integration; sourceTree = ""; @@ -2324,6 +2332,15 @@ path = KMP; sourceTree = ""; }; + 3DADDF942DCB4776001564C3 /* Helpers */ = { + isa = PBXGroup; + children = ( + 35293AA1236B51A00049A71F /* PubNub+Integration.swift */, + 3DAB7C502DD5047F0003D693 /* XCTestCase+Integration.swift */, + ); + path = Helpers; + sourceTree = ""; + }; 3DB692502D555A93006702DC /* Logger */ = { isa = PBXGroup; children = ( @@ -3391,10 +3408,13 @@ buildActionMask = 0; files = ( 35293AA2236B51A00049A71F /* PubNub+Integration.swift in Sources */, - 359287B02316EC580046F7A2 /* OnboardingSnippets.swift in Sources */, - 3562DBBD23429798006DFFBC /* UUIDObjectsEndpointIntegrationTests.swift in Sources */, + 3DADDF972DCB4E8E001564C3 /* ChannelGroupEndpointIntegrationTests.swift in Sources */, + 3562DBBD23429798006DFFBC /* UserObjectsEndpointIntegrationTests.swift in Sources */, 35293A9A236A1DE70049A71F /* MessageActionsEndpointIntegrationTests.swift in Sources */, + 3D2AF8BD2DD72CEB009303B6 /* HistoryEndpointIntegrationTests.swift in Sources */, 3562DBC1234408DC006DFFBC /* ChannelObjectsEndpointIntegrationTests.swift in Sources */, + 3DAB7C512DD5047F0003D693 /* XCTestCase+Integration.swift in Sources */, + 3D0934362DD33A74007132B1 /* FilesEndpointIntegrationTests.swift in Sources */, 359287C7232712520046F7A2 /* PresenceEndpointIntegrationTests.swift in Sources */, 3520962F2358D64C00A641DF /* TestSetup.swift in Sources */, 352096302358D67000A641DF /* TestLogWriter.swift in Sources */, diff --git a/PubNub.xcodeproj/xcshareddata/xcschemes/PubNubIntegration.xcscheme b/PubNub.xcodeproj/xcshareddata/xcschemes/PubNubIntegration.xcscheme new file mode 100644 index 00000000..f1489e88 --- /dev/null +++ b/PubNub.xcodeproj/xcshareddata/xcschemes/PubNubIntegration.xcscheme @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/PubNubTests/Integration/ChannelGroupEndpointIntegrationTests.swift b/Tests/PubNubTests/Integration/ChannelGroupEndpointIntegrationTests.swift new file mode 100644 index 00000000..caa14f62 --- /dev/null +++ b/Tests/PubNubTests/Integration/ChannelGroupEndpointIntegrationTests.swift @@ -0,0 +1,210 @@ +// +// ChannelGroupEndpointIntegrationTests.swift +// +// Copyright (c) PubNub Inc. +// All rights reserved. +// +// This source code is licensed under the license found in the +// LICENSE file in the root directory of this source tree. +// + +import Foundation +import XCTest +import PubNubSDK + +final class ChannelGroupEndpointIntegrationTests: XCTestCase { + let config = PubNubConfiguration(from: Bundle(for: ChannelGroupEndpointIntegrationTests.self)) + + func testListChannelsInGroup() { + let listChannelsExpect = expectation(description: "List Channels Response") + let client = PubNub(configuration: config) + let testGroup = randomString() + let testChannels = [randomString(), randomString()] + + // First add channels to the group + client.add(channels: testChannels, to: testGroup) { [unowned client] _ in + // Then list channels in the group + client.listChannels(for: testGroup) { result in + switch result { + case let .success(response): + XCTAssertEqual(response.group, testGroup) + XCTAssertEqual(Set(response.channels), Set(testChannels)) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + listChannelsExpect.fulfill() + } + } + + defer { + waitForCompletion { + client.remove( + channels: testChannels, + from: testGroup, + completion: $0 + ) + } + waitForCompletion { + client.remove( + channelGroup: testGroup, + completion: $0 + ) + } + } + + wait(for: [listChannelsExpect], timeout: 10.0) + } + + func testAddChannelsToGroup() { + let addChannelsExpect = expectation(description: "Add Channels Response") + let listChannelsExpect = expectation(description: "List Channels Response") + + let client = PubNub(configuration: config) + let testGroup = randomString() + let testChannels = [randomString(), randomString()] + + client.add(channels: testChannels, to: testGroup) { [unowned client] result in + switch result { + case let .success(addChannelsResponse): + XCTAssertEqual(Set(addChannelsResponse.channels), Set(testChannels)) + XCTAssertEqual(addChannelsResponse.group, testGroup) + // Fetch the channels for the group and verify they are the same as the ones added + client.listChannels(for: testGroup) { listChannelsResult in + switch listChannelsResult { + case let .success((group, channels)): + XCTAssertEqual(group, testGroup) + XCTAssertEqual(Set(channels), Set(testChannels)) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + listChannelsExpect.fulfill() + } + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + addChannelsExpect.fulfill() + } + + defer { + waitForCompletion { + client.remove( + channels: testChannels, + from: testGroup, + completion: $0 + ) + } + waitForCompletion { + client.remove( + channelGroup: testGroup, + completion: $0 + ) + } + } + + wait(for: [addChannelsExpect, listChannelsExpect], timeout: 100.0) + } + + func testRemoveChannelsFromGroup() { + let removeChannelsExpect = expectation(description: "Remove Channels Response") + let listChannelsExpect = expectation(description: "List Channels Response") + + let client = PubNub(configuration: config) + let testGroup = randomString() + let testChannels = [randomString(), randomString()] + + // First add channels to the group + client.add(channels: testChannels, to: testGroup) { [unowned client] _ in + // Then remove them + client.remove(channels: testChannels, from: testGroup) { result in + switch result { + case let .success(response): + XCTAssertEqual(response.group, testGroup) + XCTAssertEqual(Set(response.channels), Set(testChannels)) + // Fetch the channels for the group and verify they are empty + client.listChannels(for: testGroup) { listChannelsResult in + switch listChannelsResult { + case let .success((group, channels)): + XCTAssertEqual(group, testGroup) + XCTAssertTrue(channels.isEmpty) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + listChannelsExpect.fulfill() + } + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + removeChannelsExpect.fulfill() + } + } + + defer { + waitForCompletion { + client.remove( + channels: testChannels, + from: testGroup, + completion: $0 + ) + } + waitForCompletion { + client.remove( + channelGroup: testGroup, + completion: $0 + ) + } + } + + wait(for: [removeChannelsExpect, listChannelsExpect], timeout: 10.0) + } + + func testRemoveChannelGroup() { + let removeGroupExpect = expectation(description: "Remove Channel Group Response") + let listGroupsExpect = expectation(description: "List Groups Response") + + let client = PubNub(configuration: config) + let testGroup = randomString() + let testChannels = [randomString(), randomString()] + + // First add channels to the group + client.add(channels: testChannels, to: testGroup) { [unowned client] _ in + // Then remove the group + client.remove(channelGroup: testGroup) { result in + switch result { + case let .success(group): + XCTAssertEqual(group, testGroup) + // Fetch the groups and verify the tested group is not in the list + client.listChannelGroups { listGroupsResult in + switch listGroupsResult { + case let .success(groups): + XCTAssertFalse(groups.contains { $0 == testGroup }) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + listGroupsExpect.fulfill() + } + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + removeGroupExpect.fulfill() + } + } + + defer { + waitForCompletion { + client.remove( + channels: testChannels, + from: testGroup, + completion: $0 + ) + } + waitForCompletion { + client.remove( + channelGroup: testGroup, + completion: $0 + ) + } + } + + wait(for: [removeGroupExpect, listGroupsExpect], timeout: 10.0) + } +} diff --git a/Tests/PubNubTests/Integration/ChannelObjectsEndpointIntegrationTests.swift b/Tests/PubNubTests/Integration/ChannelObjectsEndpointIntegrationTests.swift index e331e852..4717ec57 100644 --- a/Tests/PubNubTests/Integration/ChannelObjectsEndpointIntegrationTests.swift +++ b/Tests/PubNubTests/Integration/ChannelObjectsEndpointIntegrationTests.swift @@ -12,35 +12,45 @@ import PubNubSDK import XCTest class ChannelObjectsEndpointIntegrationTests: XCTestCase { - let config = PubNubConfiguration(from: Bundle(for: ChannelObjectsEndpointIntegrationTests.self)) - + let config: PubNubConfiguration = PubNubConfiguration(from: Bundle(for: ChannelObjectsEndpointIntegrationTests.self)) + func testFetchAllEndpoint() { let fetchAllExpect = expectation(description: "Fetch All Expectation") let client = PubNub(configuration: config) - - client.allChannelMetadata(sort: [.init(property: .name)]) { result in + let expectedChannels = setupTestChannels(client: client) + + client.allChannelMetadata(filter: "id LIKE 'swift-*'") { result in switch result { - case let .success((channels, nextPage)): - XCTAssertTrue(nextPage?.totalCount ?? 0 >= channels.count) + case let .success((channels, _)): + let expectedIds = expectedChannels.map { $0.metadataId }.sorted() + let actualIds = channels.map { $0.metadataId }.sorted() + XCTAssertEqual(expectedIds, actualIds) case let .failure(error): XCTFail("Failed due to error: \(error)") } fetchAllExpect.fulfill() } - + + defer { + for channel in expectedChannels { + waitForCompletion { + client.removeChannelMetadata( + channel.metadataId, + completion: $0 + ) + } + } + } + wait(for: [fetchAllExpect], timeout: 10.0) } - + func testCreateAndFetchEndpoint() { let fetchExpect = expectation(description: "Fetch Expectation") - let client = PubNub(configuration: config) - - let testChannel = PubNubChannelMetadataBase( - metadataId: "testCreateAndFetchEndpoint", name: "Swift ITest" - ) - - client.setChannelMetadata(testChannel) { _ in + let testChannel = PubNubChannelMetadataBase(metadataId: "testCreateAndFetchEndpoint", name: "Swift ITest") + + client.setChannelMetadata(testChannel) { [unowned client] _ in client.fetchChannelMetadata(testChannel.metadataId) { result in switch result { case let .success(channel): @@ -51,68 +61,126 @@ class ChannelObjectsEndpointIntegrationTests: XCTestCase { fetchExpect.fulfill() } } - + + defer { + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + wait(for: [fetchExpect], timeout: 10.0) } - + func testDeleteAndCreateEndpoint() { let fetchExpect = expectation(description: "Create User Expectation") - let client = PubNub(configuration: config) - - let testChannel = PubNubChannelMetadataBase( - metadataId: "testDeleteAndCreateEndpoint", name: "Swift ITest" - ) - - client.removeChannelMetadata(testChannel.metadataId) { _ in - client.setChannelMetadata(testChannel) { result in + let testChannel = PubNubChannelMetadataBase(metadataId: randomString(), name: "Swift ITest") + + client.setChannelMetadata(testChannel) { [unowned client] _ in + client.removeChannelMetadata(testChannel.metadataId) { result in switch result { - case let .success(channel): - XCTAssertEqual(channel.metadataId, testChannel.metadataId) + case let .success(metadataId): + XCTAssertEqual(metadataId, testChannel.metadataId) case let .failure(error): XCTFail("Failed due to error: \(error)") } fetchExpect.fulfill() } } - + + defer { + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + wait(for: [fetchExpect], timeout: 10.0) } - - func testCreateAndDeleteEndpoint() { - let fetchExpect = expectation(description: "Delete Expectation") - + + func testFetchNotExistingChannel() { + let fetchExpect = expectation(description: "Fetch Channel Expectation") let client = PubNub(configuration: config) - - let testChannel = PubNubChannelMetadataBase( - metadataId: "testCreateAndDeleteEndpoint", name: "Swift ITest" + let testChannel = PubNubChannelMetadataBase(metadataId: randomString(), name: "Swift ITest") + + client.fetchChannelMetadata(testChannel.metadataId) { result in + switch result { + case .success: + XCTFail("Test should fail") + case let .failure(error): + XCTAssertNotNil(error.pubNubError) + XCTAssertEqual(error.pubNubError?.reason, .resourceNotFound) + } + fetchExpect.fulfill() + } + + + defer { + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + + wait(for: [fetchExpect], timeout: 10.0) + } + + func testSetChannelWithEntityTag() { + let setExpect = expectation(description: "Set Channel Expectation") + let client = PubNub(configuration: config) + + var testChannel = PubNubChannelMetadataBase( + metadataId: randomString(), + name: "Swift ITest", + custom: ["type": "public"] ) - - client.setChannelMetadata(testChannel) { _ in - client.removeChannelMetadata(testChannel.metadataId) { result in + + client.setChannelMetadata(testChannel) { [unowned client] firstResult in + // Update the channel metadata + testChannel.custom = ["type": "private"] + // Set the channel metadata with the ifMatchesEtag parameter + client.setChannelMetadata(testChannel, ifMatchesEtag: "12345") { result in switch result { - case let .success(metadataId): - XCTAssertEqual(metadataId, testChannel.metadataId) + case .success: + XCTFail("Test should fail") case let .failure(error): - XCTFail("Failed due to error: \(error)") + XCTAssertNotNil(error.pubNubError) + XCTAssertEqual(error.pubNubError?.reason, .preconditionFailed) } - fetchExpect.fulfill() + setExpect.fulfill() } } - - wait(for: [fetchExpect], timeout: 10.0) + + defer { + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + + wait(for: [setExpect], timeout: 10.0) } - + func testFetchMembers() { let fetchMembershipExpect = expectation(description: "Fetch Membership Expectation") - let client = PubNub(configuration: config) - + let testUser = PubNubUserMetadataBase( - metadataId: "testFetchMembersUUID", name: "Swift ITest" + metadataId: randomString(), + name: "Swift ITest" ) let testChannel = PubNubChannelMetadataBase( - metadataId: "testFetchMembersChannel", name: "Swift ITest" + metadataId: randomString(), + name: "Swift ITest" ) let membership = PubNubMembershipMetadataBase( userMetadataId: testUser.metadataId, @@ -120,8 +188,8 @@ class ChannelObjectsEndpointIntegrationTests: XCTestCase { user: testUser, channel: testChannel ) - - client.setUserMetadata(testUser) { _ in + + client.setUserMetadata(testUser) { [unowned client] _ in client.setChannelMetadata(testChannel) { _ in client.setMembers(channel: testChannel.metadataId, users: [membership]) { _ in client.fetchMembers( @@ -143,20 +211,43 @@ class ChannelObjectsEndpointIntegrationTests: XCTestCase { } } } - + + defer { + waitForCompletion { + client.removeMembers( + channel: testChannel.metadataId, + users: [membership], + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + wait(for: [fetchMembershipExpect], timeout: 10.0) } - - func testManageMembers() { + + func testSetMembers() { let fetchMembershipExpect = expectation(description: "Fetch Membership Expectation") - let client = PubNub(configuration: config) - + let testUser = PubNubUserMetadataBase( - metadataId: "testManageMembersUUID", name: "Swift ITest" + metadataId: randomString(), + name: "Swift ITest" ) let testChannel = PubNubChannelMetadataBase( - metadataId: "testManageMembersChannel", name: "Swift ITest" + metadataId: randomString(), + name: "Swift ITest" ) let membership = PubNubMembershipMetadataBase( userMetadataId: testUser.metadataId, @@ -164,13 +255,12 @@ class ChannelObjectsEndpointIntegrationTests: XCTestCase { user: testUser, channel: testChannel ) - - client.setUserMetadata(testUser) { _ in + + client.setUserMetadata(testUser) { [unowned client] _ in client.setChannelMetadata(testChannel) { _ in - client.manageMembers( + client.setMembers( channel: testChannel.metadataId, - setting: [membership], - removing: [], + users: [membership], include: .init(uuidFields: true, uuidCustomFields: true), sort: [.init(property: .object(.id)), .init(property: .updated)] ) { result in @@ -188,7 +278,260 @@ class ChannelObjectsEndpointIntegrationTests: XCTestCase { } } } - + + defer { + waitForCompletion { + client.removeMembers( + channel: testChannel.metadataId, + users: [membership], + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + wait(for: [fetchMembershipExpect], timeout: 10.0) } + + func testRemoveMembers() { + let removeMembershipExpect = expectation(description: "Remove Members Expectation") + let client = PubNub(configuration: config) + + let testUser = PubNubUserMetadataBase( + metadataId: randomString(), + name: "Swift ITest" + ) + let testChannel = PubNubChannelMetadataBase( + metadataId: randomString(), + name: "Swift ITest" + ) + let membership = PubNubMembershipMetadataBase( + userMetadataId: testUser.metadataId, + channelMetadataId: testChannel.metadataId, + user: testUser, + channel: testChannel + ) + + client.setUserMetadata(testUser) { [unowned client] _ in + client.setChannelMetadata(testChannel) { _ in + client.setMembers(channel: testChannel.metadataId, users: [membership]) { _ in + client.removeMembers(channel: testChannel.metadataId, users: [membership]) { result in + switch result { + case let .success((memberships, _)): + XCTAssertTrue(memberships.isEmpty) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + removeMembershipExpect.fulfill() + } + } + } + } + + defer { + waitForCompletion { + client.removeMembers( + channel: testChannel.metadataId, + users: [membership], + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + + wait(for: [removeMembershipExpect], timeout: 10.0) + } + + func testManageMembers() { + let manageMembersExpect = expectation(description: "Manage Members Expectation") + let client = PubNub(configuration: config) + + let testUser1 = PubNubUserMetadataBase( + metadataId: randomString(), + name: "Swift ITest User 1" + ) + let testUser2 = PubNubUserMetadataBase( + metadataId: randomString(), + name: "Swift ITest User 2" + ) + let testUser3 = PubNubUserMetadataBase( + metadataId: randomString(), + name: "Swift ITest User 3" + ) + let testChannel = PubNubChannelMetadataBase( + metadataId: randomString(), + name: "Swift ITest Channel" + ) + + let membership1 = PubNubMembershipMetadataBase( + userMetadataId: testUser1.metadataId, + channelMetadataId: testChannel.metadataId, + user: testUser1, + channel: testChannel + ) + let membership2 = PubNubMembershipMetadataBase( + userMetadataId: testUser2.metadataId, + channelMetadataId: testChannel.metadataId, + user: testUser2, + channel: testChannel + ) + let membership3 = PubNubMembershipMetadataBase( + userMetadataId: testUser3.metadataId, + channelMetadataId: testChannel.metadataId, + user: testUser3, + channel: testChannel + ) + + // First set up initial members + client.setUserMetadata(testUser1) { [unowned client] _ in + client.setUserMetadata(testUser2) { _ in + client.setUserMetadata(testUser3) { _ in + client.setChannelMetadata(testChannel) { _ in + client.setMembers(channel: testChannel.metadataId, users: [membership1, membership2]) { _ in + client.manageMembers( + channel: testChannel.metadataId, + setting: [membership3], + removing: [membership1] + ) { result in + switch result { + case let .success((memberships, _)): + XCTAssertEqual(memberships.count, 2) + XCTAssertTrue(memberships.contains { $0.userMetadataId == testUser2.metadataId }) + XCTAssertTrue(memberships.contains { $0.userMetadataId == testUser3.metadataId }) + XCTAssertFalse(memberships.contains { $0.userMetadataId == testUser1.metadataId }) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + manageMembersExpect.fulfill() + } + } + } + } + } + } + + defer { + waitForCompletion { + client.removeMembers( + channel: testChannel.metadataId, + users: [membership1, membership2, membership3], + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser1.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser2.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser3.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser3.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + + wait(for: [manageMembersExpect], timeout: 10.0) + } +} + +private extension ChannelObjectsEndpointIntegrationTests { + func channelStubs() -> [PubNubChannelMetadataBase] { + [ + PubNubChannelMetadataBase( + metadataId: randomString(), + name: "Test Channel One", + custom: ["type": "public", "category": "general"] + ), + PubNubChannelMetadataBase( + metadataId: randomString(), + name: "Test Channel Two", + custom: ["type": "private", "category": "support"] + ), + PubNubChannelMetadataBase( + metadataId: randomString(), + name: "Test Channel Three", + custom: ["type": "public", "category": "announcements"] + ), + PubNubChannelMetadataBase( + metadataId: randomString(), + name: "Test Channel Four", + custom: ["type": "private", "category": "development"] + ), + PubNubChannelMetadataBase( + metadataId: randomString(), + name: "Test Channel Five", + custom: ["type": "public", "category": "marketing"] + ), + PubNubChannelMetadataBase( + metadataId: randomString(), + name: "Test Channel Six", + custom: ["type": "private", "category": "sales"] + ) + ] + } + + func setupTestChannels(client: PubNub) -> [PubNubChannelMetadata] { + let setupExpect = expectation(description: "Setup Test Channels Expectation") + let testChannels = channelStubs() + + testChannels.enumerated().forEach { index, channel in + client.setChannelMetadata(channel) { result in + if case let .failure(error) = result { + XCTFail("Failed to setup test channel \(channel.metadataId): \(error)") + } + if index == testChannels.count - 1 { + setupExpect.fulfill() + } + } + } + + wait( + for: [setupExpect], + timeout: 10.0 + ) + + return testChannels + } } diff --git a/Tests/PubNubTests/Integration/FilesEndpointIntegrationTests.swift b/Tests/PubNubTests/Integration/FilesEndpointIntegrationTests.swift new file mode 100644 index 00000000..e7094c26 --- /dev/null +++ b/Tests/PubNubTests/Integration/FilesEndpointIntegrationTests.swift @@ -0,0 +1,336 @@ +// +// FilesEndpointIntegrationTests.swift +// +// Copyright (c) PubNub Inc. +// All rights reserved. +// +// This source code is licensed under the license found in the +// LICENSE file in the root directory of this source tree. +// + +import Foundation +import XCTest +import PubNubSDK + +class FilesEndpointIntegrationTests: XCTestCase { + let config = PubNubConfiguration(from: Bundle(for: FilesEndpointIntegrationTests.self)) + + func testUploadFile() throws { + let data = try XCTUnwrap("Lorem ipsum dolor sit amet".data(using: .utf8)) + let client = PubNub(configuration: config, fileSession: URLSession(configuration: .default, delegate: FileSessionManager(), delegateQueue: .main)) + let remoteFileId = "remoteFileId" + let testChannel = randomString() + + let sendFileExpect = expectation(description: "Send File Response") + let removeFileExpect = expectation(description: "Remove File Response") + + let performDeleteFile = { (file: PubNubFile) in + client.remove( + fileId: file.fileId, + filename: file.filename, + channel: testChannel + ) { result in + switch result { + case .success: + removeFileExpect.fulfill() + case let .failure(error): + XCTFail("Unexpected condition: \(error)") + } + } + } + + client.send( + .data(data, contentType: "text/plain"), + channel: testChannel, + remoteFilename: remoteFileId + ) { result in + switch result { + case let .success(sendFileResponse): + XCTAssertEqual(sendFileResponse.file.filename, remoteFileId) + XCTAssertEqual(sendFileResponse.file.channel, testChannel) + sendFileExpect.fulfill() + performDeleteFile(sendFileResponse.file) + case let .failure(error): + XCTFail("Unexpected error: \(error)") + } + } + + wait(for: [sendFileExpect, removeFileExpect], timeout: 20.0) + } + + func testListFiles() throws { + let data = try XCTUnwrap("Lorem ipsum dolor sit amet".data(using: .utf8)) + let client = PubNub(configuration: config, fileSession: URLSession(configuration: .default, delegate: FileSessionManager(), delegateQueue: .main)) + let remoteFileId = "remoteFileId" + let testChannel = randomString() + let removeFileExpect = expectation(description: "Remove File Response") + let listFilesExpect = expectation(description: "List Files Response") + + let performDeleteFile = { (file: PubNubFile) in + client.remove( + fileId: file.fileId, + filename: file.filename, + channel: testChannel + ) { result in + switch result { + case .success: + removeFileExpect.fulfill() + case let .failure(error): + XCTFail("Unexpected condition: \(error)") + } + } + } + + client.send( + .data(data, contentType: "text/plain"), + channel: testChannel, + remoteFilename: remoteFileId + ) { [unowned client] result in + switch result { + case .success: + client.listFiles(channel: testChannel) { getFilesResult in + switch getFilesResult { + case let .success(getFilesResponse): + XCTAssertEqual(getFilesResponse.files.count, 1) + listFilesExpect.fulfill() + performDeleteFile(getFilesResponse.files[0]) + case let .failure(error): + XCTFail("Unexpected error: \(error)") + } + } + case let .failure(error): + XCTFail("Unexpected error: \(error)") + } + } + + wait(for: [listFilesExpect, removeFileExpect], timeout: 30.0) + } + + func testUploadFileAsStream() throws { + let data = try XCTUnwrap("Lorem ipsum dolor sit amet".data(using: .utf8)) + let client = PubNub(configuration: config, fileSession: URLSession(configuration: .default, delegate: FileSessionManager(), delegateQueue: .main)) + let remoteFileId = "remoteFileId" + let testChannel = randomString() + + let sendFileExpect = expectation(description: "Send File Response") + let removeFileExpect = expectation(description: "Remove File Response") + + // Clean up the file after the test + let performDeleteFile = { (file: PubNubFile) in + client.remove( + fileId: file.fileId, + filename: file.filename, + channel: testChannel + ) { result in + switch result { + case .success: + removeFileExpect.fulfill() + case let .failure(error): + XCTFail("Unexpected condition: \(error)") + } + } + } + + client.send( + .stream(InputStream(data: data), contentType: "text/plain", contentLength: data.count), + channel: testChannel, + remoteFilename: remoteFileId + ) { result in + switch result { + case let .success(sendFileResponse): + XCTAssertEqual(sendFileResponse.file.filename, remoteFileId) + XCTAssertEqual(sendFileResponse.file.channel, testChannel) + sendFileExpect.fulfill() + performDeleteFile(sendFileResponse.file) + case let .failure(error): + XCTFail("Unexpected error: \(error)") + } + } + + wait(for: [sendFileExpect, removeFileExpect], timeout: 20.0) + } + + func testManualEncryptDecryptFile() throws { + let data = try XCTUnwrap("This is a secret message that should be encrypted".data(using: .utf8)) + let cryptoModule = CryptoModule.aesCbcCryptoModule(with: "someKey") + + let fileSession = URLSession(configuration: .default, delegate: FileSessionManager(), delegateQueue: .main) + let client = PubNub(configuration: config, fileSession: fileSession) + let encryptedStreamResult = try cryptoModule.encrypt(stream: InputStream(data: data), contentLength: data.count).get() + + let downloadFileExpect = expectation(description: "Download Encrypted File Expect") + let decryptFileExpect = expectation(description: "Decrypt File Expect") + let removeFileExpect = expectation(description: "Remove File Response") + + let remoteFileId = "encryptedFile" + let testChannel = randomString() + let downloadFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) + let decryptionOutputURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) + + // Clean up the file after the test + let performDeleteFile = { (file: PubNubFile) in + client.remove( + fileId: file.fileId, + filename: file.filename, + channel: testChannel + ) { result in + switch result { + case .success: + removeFileExpect.fulfill() + case let .failure(error): + XCTFail("Unexpected condition: \(error)") + } + } + } + + // Upload encrypted file + client.send( + .stream(encryptedStreamResult.stream, contentType: "text/plain", contentLength: encryptedStreamResult.contentLength), + channel: testChannel, + remoteFilename: remoteFileId + ) { [unowned client] result in + switch result { + case let .success(sendFileResponse): + client.download( + file: sendFileResponse.file, + toFileURL: downloadFileURL + ) { downloadResult in + switch downloadResult { + case let .success(downloadResponse): + cryptoModule.decryptStream(from: downloadResponse.file.fileURL, to: decryptionOutputURL) + XCTAssertEqual(try? Data(contentsOf: decryptionOutputURL), data) + decryptFileExpect.fulfill() + performDeleteFile(downloadResponse.file) + case let .failure(error): + XCTFail("Unexpected error: \(error)") + } + downloadFileExpect.fulfill() + } + case let .failure(error): + XCTFail("Failed to upload encrypted file: \(error)") + } + } + wait(for: [downloadFileExpect, decryptFileExpect, removeFileExpect], timeout: 30.0) + } + + func testSystemWideEncryptDecryptFile() throws { + let data = try XCTUnwrap("This is a secret message that should be encrypted".data(using: .utf8)) + let fileSession = URLSession(configuration: .default, delegate: FileSessionManager(), delegateQueue: .main) + + // Reuse the same config + var configuration = config + // Set the system wide crypto module + configuration.cryptoModule = CryptoModule.aesCbcCryptoModule(with: "someKey") + + let client = PubNub(configuration: config, fileSession: fileSession) + let downloadFileExpect = expectation(description: "Download Encrypted File Expect") + let decryptFileExpect = expectation(description: "Decrypt File Expect") + let removeFileExpect = expectation(description: "Remove File Response") + + let remoteFileId = "encryptedFile" + let testChannel = randomString() + let downloadFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString) + + // Clean up the file after the test + let performDeleteFile = { (file: PubNubFile) in + client.remove( + fileId: file.fileId, + filename: file.filename, + channel: testChannel + ) { result in + switch result { + case .success: + removeFileExpect.fulfill() + case let .failure(error): + XCTFail("Unexpected condition: \(error)") + } + } + } + + // Upload file + client.send( + .stream(InputStream(data: data), contentType: "text/plain", contentLength: data.count), + channel: testChannel, + remoteFilename: remoteFileId + ) { [unowned client] result in + switch result { + case let .success(sendFileResponse): + client.download( + file: sendFileResponse.file, + toFileURL: downloadFileURL + ) { downloadResult in + switch downloadResult { + case let .success(downloadResponse): + XCTAssertEqual(try? Data(contentsOf: downloadResponse.file.fileURL), data) + decryptFileExpect.fulfill() + performDeleteFile(downloadResponse.file) + case let .failure(error): + XCTFail("Unexpected error: \(error)") + } + downloadFileExpect.fulfill() + } + case let .failure(error): + XCTFail("Failed to upload encrypted file: \(error)") + } + } + wait(for: [downloadFileExpect, decryptFileExpect, removeFileExpect], timeout: 30.0) + } + + func testGetFileDownloadURL() throws { + let data = try XCTUnwrap("Lorem ipsum dolor sit amet".data(using: .utf8)) + let client = PubNub(configuration: config, fileSession: URLSession(configuration: .default, delegate: FileSessionManager(), delegateQueue: .main)) + let remoteFileId = "remoteFileId" + let testChannel = randomString() + + let downloadContentExpect = expectation(description: "Download Content Check") + let removeFileExpect = expectation(description: "Remove File Response") + + let performDeleteFile = { (file: PubNubFile) in + client.remove( + fileId: file.fileId, + filename: file.filename, + channel: testChannel + ) { result in + switch result { + case .success: + removeFileExpect.fulfill() + case let .failure(error): + XCTFail("Unexpected condition: \(error)") + } + } + } + + client.send( + .data(data, contentType: "text/plain"), + channel: testChannel, + remoteFilename: remoteFileId + ) { result in + switch result { + case let .success(sendFileResponse): + // Get download URL and download the file + guard let downloadURL = try? client.generateFileDownloadURL( + channel: testChannel, + fileId: sendFileResponse.file.fileId, + filename: sendFileResponse.file.filename + ) else { + return XCTFail("Cannot generate file download URL") + } + // Create a task to download the file + let task = URLSession.shared.dataTask(with: downloadURL) { downloadedData, response, error in + performDeleteFile(sendFileResponse.file) + XCTAssertNil(error, "Download failed: \(error?.localizedDescription ?? "")") + XCTAssertEqual((response as? HTTPURLResponse)?.statusCode, 200, "Invalid HTTP response") + XCTAssertEqual(downloadedData, data, "Downloaded content doesn't match original") + downloadContentExpect.fulfill() + } + // Resume the task + task.resume() + + case let .failure(error): + XCTFail("Unexpected error: \(error)") + } + } + + wait(for: [downloadContentExpect, removeFileExpect], timeout: 30.0) + } +} diff --git a/Tests/PubNubTests/Integration/PubNub+Integration.swift b/Tests/PubNubTests/Integration/Helpers/PubNub+Integration.swift similarity index 52% rename from Tests/PubNubTests/Integration/PubNub+Integration.swift rename to Tests/PubNubTests/Integration/Helpers/PubNub+Integration.swift index e42ca2d0..85e73dca 100644 --- a/Tests/PubNubTests/Integration/PubNub+Integration.swift +++ b/Tests/PubNubTests/Integration/Helpers/PubNub+Integration.swift @@ -10,6 +10,9 @@ import Foundation import PubNubSDK +import XCTest + +// MARK: - PubNub extension PubNub { func publishWithMessageAction( @@ -44,4 +47,42 @@ extension PubNub { } } } + + func subscribeSynchronously( + to channels: [String], + and channelGroups: [String] = [], + withPresence: Bool = false, + timeout: TimeInterval = 10.0 + ) { + let expectation = XCTestExpectation(description: "Subscribe synchronously") + expectation.assertForOverFulfill = true + expectation.expectedFulfillmentCount = 1 + + onConnectionStateChange = { newStatus in + if newStatus == .connected { + expectation.fulfill() + } + } + subscribe( + to: channels, + and: channelGroups, + withPresence: withPresence + ) + + let result = XCTWaiter.wait( + for: [expectation], + timeout: timeout + ) + + if result != .completed { + XCTFail("Subscribe operation timed out") + } + } +} + +// MARK: - Random string generator + +func randomString(length: Int = 6) -> String { + let characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + return "swift-" + String((0..( + suppressErrorIfAny: Bool = false, + timeout: TimeInterval = 10.0, + file: StaticString = #file, + line: UInt = #line, + operation: (@escaping (Result) -> Void) -> Void + ) { + let expect = XCTestExpectation(description: "Wait for completion (\(file) \(line)") + expect.assertForOverFulfill = true + expect.expectedFulfillmentCount = 1 + + operation { result in + if case .failure(let failure) = result { + preconditionFailure("Operation failed with error: \(failure)", file: file, line: line) + } else { + expect.fulfill() + } + } + + wait( + for: [expect], + timeout: timeout + ) + } +} + diff --git a/Tests/PubNubTests/Integration/HistoryEndpointIntegrationTests.swift b/Tests/PubNubTests/Integration/HistoryEndpointIntegrationTests.swift new file mode 100644 index 00000000..c099245a --- /dev/null +++ b/Tests/PubNubTests/Integration/HistoryEndpointIntegrationTests.swift @@ -0,0 +1,169 @@ +// +// HistoryEndpointIntegrationTests.swift +// +// Copyright (c) PubNub Inc. +// All rights reserved. +// +// This source code is licensed under the license found in the +// LICENSE file in the root directory of this source tree. +// + +import Foundation +import XCTest +import PubNubSDK + +class HistoryEndpointIntegrationTests: XCTestCase { + let config = PubNubConfiguration(from: Bundle(for: FilesEndpointIntegrationTests.self)) + + func testFetchMessageHistory() throws { + let channel = randomString() + let messagesToSend = ["Message 1", "Message 2", "Message 3"] + + // Populate the channel with test messages + populateChannel(channel, with: messagesToSend) + + let historyExpect = expectation(description: "History Response") + let client = PubNub(configuration: config) + + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [unowned client] in + client.fetchMessageHistory(for: [channel]) { result in + switch result { + case let .success(response): + XCTAssertEqual(response.messagesByChannel.count, 1) + XCTAssertEqual(response.messagesByChannel[channel]?.count, 3) + XCTAssertEqual(response.messagesByChannel[channel]?.compactMap { $0.payload.stringOptional }, ["Message 1", "Message 2", "Message 3"]) + case let .failure(error): + XCTFail("Failed to fetch history: \(error)") + } + historyExpect.fulfill() + } + } + + defer { + waitForCompletion { + client.deleteMessageHistory( + from: channel, + completion: $0 + ) + } + } + + wait(for: [historyExpect], timeout: 15.0) + } + + func testDeleteMessageHistory() throws { + let channel = randomString() + let messagesToSend = ["Message 1", "Message 2", "Message 3"] + + // Populate the channel with test messages + populateChannel(channel, with: messagesToSend) + + let deleteHistoryExpect = expectation(description: "History Response") + let client = PubNub(configuration: config) + + client.deleteMessageHistory(from: channel) { [unowned client] _ in + client.fetchMessageHistory(for: [channel]) { fetchHistoryResult in + switch fetchHistoryResult { + case let .success(historyResponse): + XCTAssertTrue(historyResponse.messagesByChannel.isEmpty) + case let .failure(error): + XCTFail("Unexpected error: \(error)") + } + deleteHistoryExpect.fulfill() + } + } + + defer { + waitForCompletion { + client.deleteMessageHistory( + from: channel, + completion: $0 + ) + } + } + + wait(for: [deleteHistoryExpect], timeout: 10.0) + } + + func testMessageCounts() throws { + let channel1 = randomString() + let channel2 = randomString() + let channel3 = randomString() + + let messagesToSend = ["Message 1", "Message 2", "Message 3"] + + // Populate both channels with test messages + populateChannel(channel1, with: messagesToSend) + populateChannel(channel2, with: messagesToSend) + + let messageCountsExpect = expectation(description: "Message Counts Response") + let client = PubNub(configuration: config) + + client.messageCounts(channels: [channel1, channel2, channel3]) { result in + switch result { + case let .success(response): + XCTAssertEqual(response.count, 3) + XCTAssertEqual(response[channel1], 3) + XCTAssertEqual(response[channel2], 3) + XCTAssertEqual(response[channel3], 0) + case let .failure(error): + XCTFail("Failed to get message counts: \(error)") + } + messageCountsExpect.fulfill() + } + + defer { + waitForCompletion { + client.deleteMessageHistory( + from: channel1, + completion: $0 + ) + } + waitForCompletion { + client.deleteMessageHistory( + from: channel2, + completion: $0 + ) + } + waitForCompletion { + client.deleteMessageHistory( + from: channel3, + completion: $0 + ) + } + } + + wait(for: [messageCountsExpect], timeout: 10.0) + } +} + +// MARK: - Channel Population + +private extension HistoryEndpointIntegrationTests { + func populateChannel(_ channel: String, with messages: [String]) { + let publishExpect = expectation(description: "Publish Messages") + publishExpect.expectedFulfillmentCount = messages.count + publishExpect.assertForOverFulfill = true + + let client = PubNub(configuration: config) + + func publishNext(_ remainingMessages: [String]) { + if let message = remainingMessages.first { + client.publish(channel: channel, message: message) { result in + switch result { + case .success: + publishNext(Array(remainingMessages.dropFirst())) + case let .failure(error): + XCTFail("Failed to publish message: \(error)") + } + publishExpect.fulfill() + } + } + } + + // Start the publishing process + publishNext(messages) + // Wait for all messages to be published + wait(for: [publishExpect], timeout: 10.0) + } +} diff --git a/Tests/PubNubTests/Integration/MessageActionsEndpointIntegrationTests.swift b/Tests/PubNubTests/Integration/MessageActionsEndpointIntegrationTests.swift index 3fb0eb5e..fa3f7672 100644 --- a/Tests/PubNubTests/Integration/MessageActionsEndpointIntegrationTests.swift +++ b/Tests/PubNubTests/Integration/MessageActionsEndpointIntegrationTests.swift @@ -13,38 +13,32 @@ import XCTest class MessageActionsEndpointIntegrationTests: XCTestCase { let testsBundle = Bundle(for: MessageActionsEndpointIntegrationTests.self) - let testChannel = "SwiftITest-MessageActions" + let testChannel = randomString() - // swiftlint:disable:next cyclomatic_complexity function_body_length - func testAddThenDeleteMessageAction() { + func testAddMessageAction() { let addExpect = expectation(description: "Add Message Action Expectation") let fetchExpect = expectation(description: "Fetch Message Action Expectation") - let removeExpect = expectation(description: "Remove Message Action Expectation") - let addedEventExcept = expectation(description: "Add Message Action Event Expectation") - let removedEventExcept = expectation(description: "Remove Message Action Event Expectation") let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) - let actionType = "reaction" let actionValue = "smiley_face" let listener = SubscriptionListener() + listener.didReceiveMessageAction = { event in switch event { case let .added(action): XCTAssertEqual(action.actionType, actionType) XCTAssertEqual(action.actionValue, actionValue) addedEventExcept.fulfill() - case let .removed(action): - XCTAssertEqual(action.actionType, actionType) - XCTAssertEqual(action.actionValue, actionValue) - removedEventExcept.fulfill() + case .removed: + XCTFail("Unexpected message action removal") } } - listener.didReceiveStatus = { [unowned self] status in + listener.didReceiveStatus = { [unowned self, unowned client] status in switch status { case let .success(connection): if connection.isConnected { @@ -60,32 +54,18 @@ class MessageActionsEndpointIntegrationTests: XCTestCase { XCTAssertEqual(messageAction.actionValue, actionValue) // Fetch the Message - client.fetchMessageActions(channel: self.testChannel) { [unowned self] actionResult in + client.fetchMessageActions(channel: self.testChannel) { actionResult in switch actionResult { case let .success((messageActions, _)): // Assert that we successfully published to server XCTAssertNotNil(messageActions.filter { $0.actionTimetoken == messageAction.actionTimetoken }) - // Remove the message - client.removeMessageActions( - channel: self.testChannel, - message: messageAction.messageTimetoken, - action: messageAction.actionTimetoken - ) { removeResult in - switch removeResult { - case let .success((channel, _, _)): - XCTAssertEqual(channel, self.testChannel) - case .failure: - XCTFail("Failed Fetching Message Actions") - } - removeExpect.fulfill() - } case .failure: XCTFail("Failed Fetching Message Actions") } fetchExpect.fulfill() } case .failure: - XCTFail("Failed Fetching Message Actions") + XCTFail("Failed Adding Message Action") } addExpect.fulfill() } @@ -98,25 +78,127 @@ class MessageActionsEndpointIntegrationTests: XCTestCase { client.add(listener) client.subscribe(to: [testChannel]) - defer { listener.cancel() } + defer { + listener.cancel() + waitForCompletion { client.deleteMessageHistory(from: testChannel, completion: $0) } + } - wait(for: [addExpect, fetchExpect, removeExpect, addedEventExcept, removedEventExcept], timeout: 10.0) + wait(for: [addExpect, fetchExpect, addedEventExcept], timeout: 15.0) + } + + func testDeleteMessageAction() { + let addExpect = expectation(description: "Add Message Action Expectation") + let removeExpect = expectation(description: "Remove Message Action Expectation") + let addedEventExcept = expectation(description: "Add Message Action Event Expectation") + let removedEventExcept = expectation(description: "Remove Message Action Event Expectation") + + addedEventExcept.assertForOverFulfill = true + addedEventExcept.expectedFulfillmentCount = 1 + removedEventExcept.assertForOverFulfill = true + removedEventExcept.expectedFulfillmentCount = 1 + + let configuration = PubNubConfiguration(from: testsBundle) + let client = PubNub(configuration: configuration) + let actionType = "reaction" + let actionValue = "smiley_face" + + let listener = SubscriptionListener() + + listener.didReceiveMessageAction = { event in + switch event { + case .added: + addedEventExcept.fulfill() + case let .removed(action): + XCTAssertEqual(action.actionType, actionType) + XCTAssertEqual(action.actionValue, actionValue) + removedEventExcept.fulfill() + } + } + + listener.didReceiveStatus = { [unowned self, unowned client] status in + switch status { + case let .success(connection): + if connection.isConnected { + // First add a message action + client.publishWithMessageAction( + channel: self.testChannel, + message: "Hello!", + actionType: actionType, actionValue: actionValue + ) { [unowned self] publishResult in + switch publishResult { + case let .success(messageAction): + // Then remove it + client.removeMessageActions( + channel: self.testChannel, + message: messageAction.messageTimetoken, + action: messageAction.actionTimetoken + ) { removeResult in + switch removeResult { + case let .success((channel, _, _)): + XCTAssertEqual(channel, self.testChannel) + case .failure: + XCTFail("Failed Removing Message Action") + } + removeExpect.fulfill() + } + case .failure: + XCTFail("Failed Adding Message Action") + } + addExpect.fulfill() + } + } + case .failure: + XCTFail("An error occurred") + } + } + + client.add(listener) + client.subscribe(to: [testChannel]) + + defer { + listener.cancel() + waitForCompletion { client.deleteMessageHistory(from: testChannel, completion: $0) } + } + + wait(for: [addExpect, addedEventExcept, removeExpect, removedEventExcept], timeout: 10.0) } func testFetchMessageActionsEndpoint() { let fetchExpect = expectation(description: "Fetch Message Actions Expectation") - let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) - client.fetchMessageActions(channel: testChannel) { result in - switch result { - case .success: - break - case .failure: - XCTFail("Failed Fetching Message Actions") + client.publish(channel: testChannel, message: "This is a message") { [unowned self, unowned client] result in + if let timetoken = try? result.get() { + client.addMessageAction( + channel: self.testChannel, + type: "reaction", + value: ":+1", + messageTimetoken: timetoken + ) { _ in + client.fetchMessageActions(channel: self.testChannel) { fetchMessageActionsResult in + if let messageActions = try? fetchMessageActionsResult.get().actions { + XCTAssertEqual(messageActions.count, 1) + XCTAssertEqual(messageActions.first?.actionType, "reaction") + XCTAssertEqual(messageActions.first?.actionValue, ":+1") + } else { + XCTFail("Unexpected condition. Unable to retrieve fetched message actions") + } + fetchExpect.fulfill() + } + } + } else { + XCTFail("Unexpected condition") + } + } + + defer { + waitForCompletion { + client.deleteMessageHistory( + from: testChannel, + completion: $0 + ) } - fetchExpect.fulfill() } wait(for: [fetchExpect], timeout: 10.0) @@ -125,10 +207,8 @@ class MessageActionsEndpointIntegrationTests: XCTestCase { func testHistoryWithMessageActions() { let addExpect = expectation(description: "Add Message Action Expectation") let historyExpect = expectation(description: "Fetch Message Action Expectation") - let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) - let actionType = "reaction" let actionValue = "smiley_face" @@ -136,33 +216,41 @@ class MessageActionsEndpointIntegrationTests: XCTestCase { channel: testChannel, message: "Hello!", actionType: actionType, actionValue: actionValue - ) { [unowned self] publishResult in + ) { [unowned self, unowned client] publishResult in switch publishResult { case let .success(messageAction): XCTAssertEqual(messageAction.publisher, configuration.uuid) XCTAssertEqual(messageAction.actionType, actionType) XCTAssertEqual(messageAction.actionValue, actionValue) - client.fetchMessageHistory(for: [self.testChannel], includeActions: true) { historyResult in + client.fetchMessageHistory( + for: [self.testChannel], + includeActions: true + ) { historyResult in switch historyResult { case let .success((messages, _)): let channelHistory = messages[self.testChannel] - XCTAssertNotNil(channelHistory) - let message = channelHistory?.filter { $0.published == messageAction.messageTimetoken } XCTAssertNotNil(message) case .failure: XCTFail("Failed Fetching Message Actions") } - historyExpect.fulfill() } - case .failure: XCTFail("Failed Fetching Message Actions") } addExpect.fulfill() } + + defer { + waitForCompletion { + client.deleteMessageHistory( + from: testChannel, + completion: $0 + ) + } + } wait(for: [addExpect, historyExpect], timeout: 10.0) } diff --git a/Tests/PubNubTests/Integration/OnboardingSnippets.swift b/Tests/PubNubTests/Integration/OnboardingSnippets.swift deleted file mode 100644 index d0ea16f5..00000000 --- a/Tests/PubNubTests/Integration/OnboardingSnippets.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// OnboardingSnippets.swift -// -// Copyright (c) PubNub Inc. -// All rights reserved. -// -// This source code is licensed under the license found in the -// LICENSE file in the root directory of this source tree. -// - -import PubNubSDK -import XCTest - -class OnboardingSnippets: XCTestCase { - let testsBundle = Bundle(for: OnboardingSnippets.self) - - // Subscribe with presence - func testPubSub() { - let messageExpect = expectation(description: "Message Response") - let publishExpect = expectation(description: "Publish Response") - - // Instantiate PubNub - let configuration = PubNubConfiguration(from: testsBundle) - let client = PubNub(configuration: configuration) - - let performPublish = { - // Publish a simple message to the demo_tutorial channel - client.publish(channel: "pubnub_onboarding_channel", - message: ["sender": configuration.uuid, - "content": "Hello From SDK_NAME SDK"]) { result in - _ = result.map { print("Successfully sent at \($0)!") } - publishExpect.fulfill() - } - } - - // Add Listener - let listener = SubscriptionListener() - listener.didReceiveMessage = { event in - print("Received \(event.payload) from \(event.publisher ?? "_Unknown_")") - messageExpect.fulfill() - } - listener.didReceivePresence = { event in - print("Channel `\(event.channel)` has occupancy of \(event.occupancy)") - for action in event.actions { - print("Event `\(action)` at \(event.timetoken)") - } - } - listener.didReceiveStatus = { event in - switch event { - case let .success(status): - print("Status changed to \(status)") - if status == .connected { performPublish() } - case let .failure(error): - print("Error received on endpoint \(error.localizedDescription)") - XCTFail("Publish returned an error") - } - } - client.add(listener) - - // Subscribe to the demo_tutorial channel - client.subscribe(to: ["pubnub_onboarding_channel"], withPresence: true) - - // Cleanup - defer { listener.cancel() } - wait(for: [messageExpect, publishExpect], timeout: 10.0) - } - - // Publish 10 messages, History of 10 messages, Delete 10 messages - func testFetchChannelHistory() { - let historyExpect = expectation(description: "Message Response") - let publishExpect = expectation(description: "Publish Response") - - // Instantiate PubNub - let configuration = PubNubConfiguration(from: testsBundle) - let client = PubNub(configuration: configuration) - - // Fetch last 10 messages - let performMessageFetch = { - client.fetchMessageHistory(for: ["pubnub_onboarding_channel"]) { result in - switch result { - case let .success((actions, nextPage)): - XCTAssertNotNil(actions["pubnub_onboarding_channel"]) - if let channelMessages = actions["pubnub_onboarding_channel"] { - print("Start timetoken: \(nextPage?.start ?? 0)") - print("End timetoken: \(nextPage?.end ?? 0)") - for message in channelMessages { - print("Message content: \(message.payload)") - } - } - case let .failure(error): - print("Message History error received: \(error.localizedDescription)") - XCTFail("Message History returned an error") - } - historyExpect.fulfill() - } - } - - let listener = SubscriptionListener() - listener.didReceiveSubscription = { event in - switch event { - case let .messageReceived(message): - if message.publisher == configuration.uuid { - performMessageFetch() - } - case let .connectionStatusChanged(connection): - if connection == .connected { - client.publish(channel: "pubnub_onboarding_channel", - message: ["sender": configuration.uuid, - "content": "Hello From SDK_NAME SDK"]) { _ in - publishExpect.fulfill() - } - } - default: - break - } - } - - client.add(listener) - client.subscribe(to: ["pubnub_onboarding_channel"]) - - defer { listener.cancel() } - - wait(for: [publishExpect, historyExpect], timeout: 10.0) - } -} diff --git a/Tests/PubNubTests/Integration/PresenceEndpointIntegrationTests.swift b/Tests/PubNubTests/Integration/PresenceEndpointIntegrationTests.swift index 26443dbd..0e4bc83b 100644 --- a/Tests/PubNubTests/Integration/PresenceEndpointIntegrationTests.swift +++ b/Tests/PubNubTests/Integration/PresenceEndpointIntegrationTests.swift @@ -18,14 +18,11 @@ class PresenceEndpointIntegrationTests: XCTestCase { func testHereNow_SingleChannel_Stateless() { let hereNowExpect = expectation(description: "Here Now Response") - - var configuration = PubNubConfiguration(from: testsBundle) - configuration.durationUntilTimeout = 11 - let client = PubNub(configuration: configuration) let testChannel = "testHereNow_SingleChannel_Statelesssss" + let presenceConfig = presenceConfiguration() + let client = PubNub(configuration: presenceConfig) let performHereNow = { - // Here Now Test client.hereNow(on: [testChannel], includeState: true) { result in switch result { case let .success(channels): @@ -40,13 +37,14 @@ class PresenceEndpointIntegrationTests: XCTestCase { } let listener = SubscriptionListener() + listener.didReceivePresence = { event in - if event.channel == testChannel, event.actions.join(contains: configuration.uuid) { + if event.channel == testChannel, event.actions.join(contains: presenceConfig.userId) { performHereNow() } } + client.add(listener) - client.subscribe(to: [testChannel], withPresence: true) defer { listener.cancel() } @@ -56,22 +54,16 @@ class PresenceEndpointIntegrationTests: XCTestCase { func testHereNow_SingleChannel_State() { let hereNowExpect = expectation(description: "Here Now Response") let setStateExpect = expectation(description: "Set State Response") - - var configuration = PubNubConfiguration(from: testsBundle) - configuration.durationUntilTimeout = 11 - let client = PubNub(configuration: configuration) let testChannel = "testHereNow_SingleChannel_State" + let presenceConfig = presenceConfiguration() + let client = PubNub(configuration: presenceConfig) let performHereNow = { - // Here Now Test client.hereNow(on: [testChannel], includeState: true) { result in switch result { case let .success(channels): XCTAssertNotNil(channels[testChannel]) - XCTAssertEqual( - channels[testChannel]?.occupantsState[configuration.uuid]?.codableValue, - ["StateKey": "StateValue"] - ) + XCTAssertEqual(channels[testChannel]?.occupantsState[presenceConfig.userId]?.codableValue, ["StateKey": "StateValue"]) case let .failure(error): XCTFail("Failed due to error: \(error)") } @@ -79,7 +71,6 @@ class PresenceEndpointIntegrationTests: XCTestCase { } } let performSetState = { - // Here Now Test client.setPresence(state: ["StateKey": "StateValue"], on: [testChannel]) { result in switch result { case let .success(channels): @@ -93,13 +84,14 @@ class PresenceEndpointIntegrationTests: XCTestCase { } let listener = SubscriptionListener() + listener.didReceivePresence = { event in - if event.channel == testChannel, event.actions.join(contains: configuration.uuid) { + if event.channel == testChannel, event.actions.join(contains: presenceConfig.userId) { performSetState() } } + client.add(listener) - client.subscribe(to: [testChannel], withPresence: true) defer { listener.cancel() } @@ -108,11 +100,9 @@ class PresenceEndpointIntegrationTests: XCTestCase { func testHereNow_SingleChannel_EmptyPresence() { let hereNowExpect = expectation(description: "Here Now Response") - - var configuration = PubNubConfiguration(from: testsBundle) - configuration.durationUntilTimeout = 11 - let client = PubNub(configuration: configuration) let testChannel = "testHereNow_SingleChannel_EmptyPresence" + let presenceConfig = presenceConfiguration() + let client = PubNub(configuration: presenceConfig) client.hereNow(on: [testChannel], includeState: true) { result in switch result { @@ -132,15 +122,12 @@ class PresenceEndpointIntegrationTests: XCTestCase { func testHereNow_MultiChannel_Stateless() { let hereNowExpect = expectation(description: "Here Now Response") - - var configuration = PubNubConfiguration(from: testsBundle) - configuration.durationUntilTimeout = 11 - let client = PubNub(configuration: configuration) let testChannel = "testHereNow_MultiChannel_Stateless" let otherChannel = "testHereNow_MultiChannel_Stateless_Other" + let presenceConfig = presenceConfiguration() + let client = PubNub(configuration: presenceConfig) let performHereNow = { - // Here Now Test client.hereNow(on: [testChannel, otherChannel]) { result in switch result { case let .success(channels): @@ -153,13 +140,14 @@ class PresenceEndpointIntegrationTests: XCTestCase { } let listener = SubscriptionListener() + listener.didReceivePresence = { event in - if event.channel == testChannel, event.actions.join(contains: configuration.uuid) { + if event.channel == testChannel, event.actions.join(contains: presenceConfig.userId) { performHereNow() } } + client.add(listener) - client.subscribe(to: [testChannel], withPresence: true) defer { listener.cancel() } @@ -169,35 +157,26 @@ class PresenceEndpointIntegrationTests: XCTestCase { func testHereNow_MultiChannel_State() { let hereNowExpect = expectation(description: "Here Now Response") let setStateExpect = expectation(description: "Set State Response") - - var configuration = PubNubConfiguration(from: testsBundle) - configuration.durationUntilTimeout = 11 - let client = PubNub(configuration: configuration) let testChannel = "testHereNow_MultiChannel_State" let otherChannel = "testHereNow_MultiChannel_State_Other" + let presenceConfig = presenceConfiguration() + let client = PubNub(configuration: presenceConfig) let performHereNow = { - // Here Now Test client.hereNow(on: [testChannel, otherChannel], includeState: true) { result in switch result { case let .success(channels): XCTAssertNotNil(channels[testChannel]) - XCTAssertEqual( - channels[testChannel]?.occupantsState[configuration.uuid]?.codableValue, - ["StateKey": "StateValue"] - ) - XCTAssertEqual( - channels[otherChannel]?.occupantsState[configuration.uuid]?.codableValue, - ["StateKey": "StateValue"] - ) + XCTAssertEqual(channels[testChannel]?.occupantsState[presenceConfig.userId]?.codableValue, ["StateKey": "StateValue"]) + XCTAssertEqual(channels[otherChannel]?.occupantsState[presenceConfig.userId]?.codableValue, ["StateKey": "StateValue"]) case let .failure(error): XCTFail("Failed due to error: \(error)") } hereNowExpect.fulfill() } } + let performSetState = { - // Here Now Test client.setPresence(state: ["StateKey": "StateValue"], on: [testChannel, otherChannel]) { result in switch result { case let .success(channels): @@ -211,13 +190,14 @@ class PresenceEndpointIntegrationTests: XCTestCase { } let listener = SubscriptionListener() + listener.didReceivePresence = { event in - if event.channel == testChannel, event.actions.join(contains: configuration.uuid) { + if event.channel == testChannel, event.actions.join(contains: presenceConfig.userId) { performSetState() } } + client.add(listener) - client.subscribe(to: [testChannel, otherChannel], withPresence: true) defer { listener.cancel() } @@ -226,12 +206,10 @@ class PresenceEndpointIntegrationTests: XCTestCase { func testHereNow_MultiChannel_EmptyPresence() { let hereNowExpect = expectation(description: "Here Now Response") - - var configuration = PubNubConfiguration(from: testsBundle) - configuration.durationUntilTimeout = 11 - let client = PubNub(configuration: configuration) let testChannel = "testHereNow_MultiChannel_EmptyPresence" let otherChannel = "testHereNow_MultiChannel_EmptyPresence_Other" + let presenceConfig = presenceConfiguration() + let client = PubNub(configuration: presenceConfig) client.hereNow(on: [testChannel, otherChannel], includeState: true) { result in switch result { @@ -245,4 +223,51 @@ class PresenceEndpointIntegrationTests: XCTestCase { wait(for: [hereNowExpect], timeout: 10.0) } + + func testWhereNow() { + let whereNowExpect = expectation(description: "Where Now Response") + let testChannel1 = "testWhereNowChannel1" + let testChannel2 = "testWhereNowChannel2" + let presenceConfig = presenceConfiguration() + let client = PubNub(configuration: presenceConfig) + + let performWhereNow = { + client.whereNow(for: client.configuration.userId) { result in + switch result { + case let .success(channels): + let channelsByUserId = channels[client.configuration.userId] ?? [] + XCTAssertEqual(channelsByUserId.count, 2) + XCTAssertEqual(Set(channelsByUserId), Set([testChannel1, testChannel2])) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + whereNowExpect.fulfill() + } + } + + let listener = SubscriptionListener() + + listener.didReceivePresence = { event in + if event.channel == testChannel2, event.actions.join(contains: presenceConfig.userId) { + performWhereNow() + } + } + + client.add(listener) + client.subscribe(to: [testChannel1, testChannel2], withPresence: true) + + defer { listener.cancel() } + wait(for: [whereNowExpect], timeout: 30.0) + } +} + +private extension PresenceEndpointIntegrationTests { + func presenceConfiguration() -> PubNubConfiguration { + PubNubConfiguration( + publishKey: PubNubConfiguration(from: testsBundle).publishKey, + subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, + userId: randomString(), + durationUntilTimeout: 11 + ) + } } diff --git a/Tests/PubNubTests/Integration/PublishEndpointIntegrationTests.swift b/Tests/PubNubTests/Integration/PublishEndpointIntegrationTests.swift index ee3e092e..6c37e40a 100644 --- a/Tests/PubNubTests/Integration/PublishEndpointIntegrationTests.swift +++ b/Tests/PubNubTests/Integration/PublishEndpointIntegrationTests.swift @@ -16,12 +16,11 @@ class PublishEndpointIntegrationTests: XCTestCase { func testPublishEndpoint() { let publishExpect = expectation(description: "Publish Response") - // Instantiate PubNub let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) - - // Publish a simple message to the demo_tutorial channel - client.publish(channel: "SwiftITest", message: "TestPublish") { result in + let channelName = randomString() + + client.publish(channel: channelName, message: "TestPublish") { result in switch result { case .success: break @@ -30,19 +29,28 @@ class PublishEndpointIntegrationTests: XCTestCase { } publishExpect.fulfill() } + + defer { + waitForCompletion { + client.deleteMessageHistory( + from: channelName, + completion: $0 + ) + } + } wait(for: [publishExpect], timeout: 10.0) } func testSignalTooLong() { let publishExpect = expectation(description: "Publish Response") - // Instantiate PubNub let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) - + let testChannel = randomString() + client.signal( - channel: "SwiftITest", - message: ["$": "35.75", "HI": "b62", "t": "BO"] + channel: testChannel, + message: ["$": "35.75", "HI": "b62", "t": "BO", "X": "Lorem ipsum dolor sit amet"] ) { result in switch result { case .success: @@ -58,13 +66,12 @@ class PublishEndpointIntegrationTests: XCTestCase { func testCompressedPublishEndpoint() { let compressedPublishExpect = expectation(description: "Compressed Publish Response") - // Instantiate PubNub let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) - - // Publish a simple message to the demo_tutorial channel + let channelName = randomString() + client.publish( - channel: "SwiftITest", + channel: channelName, message: "TestCompressedPublish", shouldCompress: true ) { result in @@ -76,18 +83,26 @@ class PublishEndpointIntegrationTests: XCTestCase { } compressedPublishExpect.fulfill() } + + defer { + waitForCompletion { + client.deleteMessageHistory( + from: channelName, + completion: $0 + ) + } + } wait(for: [compressedPublishExpect], timeout: 10.0) } func testFireEndpoint() { let fireExpect = expectation(description: "Fire Response") - // Instantiate PubNub let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) + let channelName = randomString() - // Publish a simple message to the demo_tutorial channel - client.fire(channel: "SwiftITest", message: "TestFire") { result in + client.fire(channel: channelName, message: "TestFire") { result in switch result { case .success: break @@ -102,12 +117,11 @@ class PublishEndpointIntegrationTests: XCTestCase { func testSignalEndpoint() { let signalExpect = expectation(description: "Signal Response") - // Instantiate PubNub let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) + let channelName = randomString() - // Publish a simple message to the demo_tutorial channel - client.signal(channel: "SwiftITest", message: "TestSignal") { result in + client.signal(channel: channelName, message: "TestSignal") { result in switch result { case .success: break @@ -120,16 +134,14 @@ class PublishEndpointIntegrationTests: XCTestCase { wait(for: [signalExpect], timeout: 10.0) } - func testPushblishEscapedString() { + func testPublishEscapedString() { let message = "{\"text\": \"bob\", \"duckName\": \"swiftduck\"}" let publishExpect = expectation(description: "Publish Response") - - // Instantiate PubNub let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) + let channelName = randomString() - // Publish a simple message to the demo_tutorial channel - client.publish(channel: "SwiftITest", message: message) { result in + client.publish(channel: channelName, message: message) { result in switch result { case .success: XCTFail("Publish should fail") @@ -139,15 +151,23 @@ class PublishEndpointIntegrationTests: XCTestCase { publishExpect.fulfill() } + defer { + waitForCompletion { + client.deleteMessageHistory( + from: channelName, + completion: $0 + ) + } + } + wait(for: [publishExpect], timeout: 10.0) } func testPublishPushPayload() { let publishExpect = expectation(description: "Publish Response") - - // Instantiate PubNub let configuration = PubNubConfiguration(from: testsBundle) let client = PubNub(configuration: configuration) + let channelName = randomString() let pushMessage = PubNubPushMessage( apns: PubNubAPNSPayload( @@ -164,8 +184,7 @@ class PublishEndpointIntegrationTests: XCTestCase { additional: "Push Message from PubNub Swift SDK" ) - // Publish a simple message to the demo_tutorial channel - client.publish(channel: "SwiftITest", message: pushMessage) { result in + client.publish(channel: channelName, message: pushMessage) { result in switch result { case .success: break @@ -175,10 +194,19 @@ class PublishEndpointIntegrationTests: XCTestCase { publishExpect.fulfill() } + defer { + waitForCompletion { + client.deleteMessageHistory( + from: channelName, + completion: $0 + ) + } + } + wait(for: [publishExpect], timeout: 10.0) } - func testPublish_WithCryptoModulesFromDifferentClients() { + func testPublishWithCryptoModulesFromDifferentClients() { let firstClient = PubNub(configuration: PubNubConfiguration( publishKey: PubNubConfiguration(from: testsBundle).publishKey, subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, @@ -192,8 +220,8 @@ class PublishEndpointIntegrationTests: XCTestCase { cryptoModule: CryptoModule.aesCbcCryptoModule(with: "anotherKey") )) - let channelForFistClient = "ChannelA" - let channelForSecondClient = "ChannelB" + let channelForFistClient = randomString() + let channelForSecondClient = randomString() let publishExpect = expectation(description: "Publish Response") publishExpect.assertForOverFulfill = true @@ -201,7 +229,7 @@ class PublishEndpointIntegrationTests: XCTestCase { let subscribeExpect = expectation(description: "Subscribe Response") subscribeExpect.assertForOverFulfill = true - subscribeExpect.assertForOverFulfill = true + subscribeExpect.expectedFulfillmentCount = 2 for client in [firstClient, secondClient] { client.onConnectionStateChange = { [unowned client] newStatus in @@ -221,8 +249,13 @@ class PublishEndpointIntegrationTests: XCTestCase { } } - let subscription = firstClient.channel(channelForFistClient).subscription() - let subscriptionFromSecondClient = secondClient.channel(channelForSecondClient).subscription() + let subscription = firstClient + .channel(channelForFistClient) + .subscription() + + let subscriptionFromSecondClient = secondClient + .channel(channelForSecondClient) + .subscription() subscription.onMessage = { message in XCTAssertEqual(message.payload.stringOptional, "This is a message") @@ -232,9 +265,25 @@ class PublishEndpointIntegrationTests: XCTestCase { XCTAssertEqual(message.payload.stringOptional, "This is a message") subscribeExpect.fulfill() } + subscription.subscribe() subscriptionFromSecondClient.subscribe() + defer { + waitForCompletion { + firstClient.deleteMessageHistory( + from: channelForFistClient, + completion: $0 + ) + } + waitForCompletion { + secondClient.deleteMessageHistory( + from: channelForSecondClient, + completion: $0 + ) + } + } + wait(for: [publishExpect, subscribeExpect], timeout: 10.0) } } diff --git a/Tests/PubNubTests/Integration/PushIntegrationTests.swift b/Tests/PubNubTests/Integration/PushIntegrationTests.swift index d383a154..b37758d0 100644 --- a/Tests/PubNubTests/Integration/PushIntegrationTests.swift +++ b/Tests/PubNubTests/Integration/PushIntegrationTests.swift @@ -13,30 +13,36 @@ import PubNubSDK class PushIntegrationTests: XCTestCase { let testsBundle = Bundle(for: PushIntegrationTests.self) - let pushToken = Data(hexEncodedString: "7a043aa0085d31422cab58101d9237ad8ce6d77283d68639c6e71924c39fc5f8") - + let pushToken = Data(hexEncodedString: "7a043aa0085d31422cab58101d9237ad8ce6d77283d68639c6e71924c39fc5f8")! let channel = "SwiftPushITest" let pushTopic = "SwiftPushITest" func testModifyChannels() { let addExpect = expectation(description: "Adding Channel") - - guard let token = pushToken else { - return XCTFail("Could not create push data") - } - let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) - client.managePushChannelRegistrations(byRemoving: [], thenAdding: ["foo1", "foo2"], for: token) { result in + + client.managePushChannelRegistrations( + byRemoving: [], + thenAdding: ["foo1", "foo2"], + for: pushToken + ) { result in switch result { case let .success(channels): - print("Added APNS to \(channels)") - case let .failure(error): print("Could not add APNS on channels: \(self.channel). ERROR: \(error.localizedDescription)") } addExpect.fulfill() } + + defer { + waitForCompletion { + client.removeAllPushChannelRegistrations( + for: pushToken, + completion: $0 + ) + } + } wait(for: [addExpect], timeout: 10.0) } @@ -44,20 +50,54 @@ class PushIntegrationTests: XCTestCase { func testListAPNSChannels() { let addExpect = expectation(description: "Adding Channel") let listExpect = expectation(description: "Listing Channels") - let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) - guard let token = pushToken else { - return XCTFail("Could not create push data") - } - // Add a channel client.manageAPNSDevicesOnChannels( - byRemoving: [], thenAdding: [channel], device: token, on: pushTopic - ) { [unowned self] result in + byRemoving: [], + thenAdding: [channel], + device: pushToken, + on: pushTopic + ) { [unowned self, unowned client] result in + // List Channels + client.listAPNSPushChannelRegistrations(for: pushToken, on: pushTopic) { result in + switch result { + case let .success(channels): + XCTAssertEqual(channels.first, self.channel) + case let .failure(error): + XCTFail("List APNS call failed due to \(error.localizedDescription)") + } + listExpect.fulfill() + } + addExpect.fulfill() + } + + defer { + waitForCompletion { + client.removeAllAPNSPushDevice( + for: pushToken, + on: pushTopic, + completion: $0 + ) + } + } + + wait(for: [addExpect, listExpect], timeout: 10.0) + } + + func testAddAPNSDevicesOnChannels() { + let addExpect = expectation(description: "Adding Channel") + let listExpect = expectation(description: "Listing Channels") + let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + // Add a channel + client.addAPNSDevicesOnChannels( + [channel], + device: pushToken, + on: pushTopic + ) { [unowned self, unowned client] result in // List Channels - client.listAPNSPushChannelRegistrations(for: token, on: self.pushTopic) { result in + client.listAPNSPushChannelRegistrations(for: pushToken, on: pushTopic) { result in switch result { case let .success(channels): XCTAssertEqual(channels.first, self.channel) @@ -68,6 +108,16 @@ class PushIntegrationTests: XCTestCase { } addExpect.fulfill() } + + defer { + waitForCompletion { + client.removeAllAPNSPushDevice( + for: pushToken, + on: pushTopic, + completion: $0 + ) + } + } wait(for: [addExpect, listExpect], timeout: 10.0) } @@ -76,19 +126,17 @@ class PushIntegrationTests: XCTestCase { let addExpect = expectation(description: "Adding Channel") let removeAll = expectation(description: "Remove All Channel") let listExpect = expectation(description: "Listing Channels") - let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) - guard let token = pushToken else { - return XCTFail("Could not create push data") - } - // Add a channel client.manageAPNSDevicesOnChannels( - byRemoving: [], thenAdding: [channel], device: token, on: pushTopic - ) { [unowned self] result in - client.removeAllAPNSPushDevice(for: token, on: self.pushTopic) { _ in - client.listAPNSPushChannelRegistrations(for: token, on: self.pushTopic) { result in + byRemoving: [], + thenAdding: [channel], + device: pushToken, + on: pushTopic + ) { [unowned self, unowned client] result in + client.removeAllAPNSPushDevice(for: pushToken, on: self.pushTopic) { _ in + client.listAPNSPushChannelRegistrations(for: self.pushToken, on: self.pushTopic) { result in switch result { case let .success(channels): XCTAssertEqual(channels.isEmpty, true) @@ -104,4 +152,285 @@ class PushIntegrationTests: XCTestCase { wait(for: [addExpect, removeAll, listExpect], timeout: 10.0) } + + func testListPushChannelRegistrations() { + let addExpect = expectation(description: "Adding Channel") + let listExpect = expectation(description: "Listing Channels") + let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + + // Add a channel + client.managePushChannelRegistrations( + byRemoving: [], + thenAdding: [channel], + for: pushToken + ) { [unowned self, unowned client] result in + // List Channels + client.listPushChannelRegistrations(for: pushToken) { result in + switch result { + case let .success(channels): + XCTAssertEqual(channels.first, self.channel) + case let .failure(error): + XCTFail("List push registrations call failed due to \(error.localizedDescription)") + } + listExpect.fulfill() + } + addExpect.fulfill() + } + + defer { + waitForCompletion { + client.removeAllPushChannelRegistrations( + for: pushToken, + completion: $0 + ) + } + } + + wait(for: [addExpect, listExpect], timeout: 10.0) + } + + func testManageMultiplePushChannels() { + let initialAddExpect = expectation(description: "Initial Channel Addition") + let manageExpect = expectation(description: "Managing Channels") + let listExpect = expectation(description: "Listing Channels") + let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + + // First add initial channels + client.managePushChannelRegistrations( + byRemoving: [], + thenAdding: ["c1", "c2", "c3", "c4"], + for: pushToken + ) { [unowned self, unowned client] result in + // Then remove some and add new ones + client.managePushChannelRegistrations( + byRemoving: ["c1", "c2"], + thenAdding: ["c5", "c6"], + for: pushToken + ) { result in + // List final channels + client.listPushChannelRegistrations(for: self.pushToken) { result in + switch result { + case let .success(channels): + XCTAssertEqual(Set(channels), Set(["c3", "c4", "c5", "c6"])) + case let .failure(error): + XCTFail("List push registrations call failed due to \(error.localizedDescription)") + } + listExpect.fulfill() + } + manageExpect.fulfill() + } + initialAddExpect.fulfill() + } + + defer { + waitForCompletion { + client.removeAllPushChannelRegistrations( + for: pushToken, + completion: $0 + ) + } + } + + wait(for: [initialAddExpect, manageExpect, listExpect], timeout: 10.0) + } + + func testManageMultipleAPNSChannels() { + let initialAddExpect = expectation(description: "Initial Channel Addition") + let manageExpect = expectation(description: "Managing Channels") + let listExpect = expectation(description: "Listing Channels") + let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + + // First add initial channels + client.manageAPNSDevicesOnChannels( + byRemoving: [], + thenAdding: ["c1", "c2", "c3", "c4"], + device: pushToken, + on: pushTopic + ) { [unowned self, unowned client] result in + // Then remove some and add new ones + client.manageAPNSDevicesOnChannels( + byRemoving: ["c1", "c2"], + thenAdding: ["c5", "c6"], + device: pushToken, + on: pushTopic + ) { result in + // List final channels + client.listAPNSPushChannelRegistrations(for: self.pushToken, on: self.pushTopic) { result in + switch result { + case let .success(channels): + XCTAssertEqual(Set(channels), Set(["c3", "c4", "c5", "c6"])) + case let .failure(error): + XCTFail("List APNS call failed due to \(error.localizedDescription)") + } + listExpect.fulfill() + } + manageExpect.fulfill() + } + initialAddExpect.fulfill() + } + + defer { + waitForCompletion { + client.removeAllAPNSPushDevice( + for: pushToken, + on: pushTopic, + completion: $0 + ) + } + } + + wait(for: [initialAddExpect, manageExpect, listExpect], timeout: 10.0) + } + + func testRemovePushChannelRegistrations() { + let addExpect = expectation(description: "Adding Channels") + let removeExpect = expectation(description: "Removing Channels") + let listExpect = expectation(description: "Listing Channels") + let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + + // First add channels + client.managePushChannelRegistrations( + byRemoving: [], + thenAdding: ["c1", "c2", "c3"], + for: pushToken + ) { [unowned self, unowned client] result in + // Then remove specific channels + client.removePushChannelRegistrations(["c1", "c2"], for: pushToken) { result in + // List remaining channels + client.listPushChannelRegistrations(for: self.pushToken) { result in + switch result { + case let .success(channels): + XCTAssertEqual(Set(channels), Set(["c3"])) + case let .failure(error): + XCTFail("List push registrations call failed due to \(error.localizedDescription)") + } + listExpect.fulfill() + } + removeExpect.fulfill() + } + addExpect.fulfill() + } + + defer { + waitForCompletion { + client.removeAllPushChannelRegistrations( + for: pushToken, + completion: $0 + ) + } + } + + wait(for: [addExpect, removeExpect, listExpect], timeout: 10.0) + } + + func testAddPushChannelRegistrations() { + let addExpect = expectation(description: "Adding Channels") + let listExpect = expectation(description: "Listing Channels") + let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + + // Add multiple channels at once + client.addPushChannelRegistrations(["c1", "c2", "c3"], for: pushToken) { [unowned self, unowned client] result in + // List added channels + client.listPushChannelRegistrations(for: pushToken) { result in + switch result { + case let .success(channels): + XCTAssertEqual(Set(channels), Set(["c1", "c2", "c3"])) + case let .failure(error): + XCTFail("List push registrations call failed due to \(error.localizedDescription)") + } + listExpect.fulfill() + } + addExpect.fulfill() + } + + defer { + waitForCompletion { + client.removeAllPushChannelRegistrations( + for: pushToken, + completion: $0 + ) + } + } + + wait(for: [addExpect, listExpect], timeout: 10.0) + } + + func testRemoveAllPushChannelRegistrations() { + let addExpect = expectation(description: "Adding Channels") + let removeAllExpect = expectation(description: "Removing All Channels") + let listExpect = expectation(description: "Listing Channels") + let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + + // First add some channels + client.managePushChannelRegistrations( + byRemoving: [], + thenAdding: ["c1", "c2", "c3"], + for: pushToken + ) { [unowned self, unowned client] result in + // Then remove all channels + client.removeAllPushChannelRegistrations(for: pushToken) { result in + // List remaining channels (should be empty) + client.listPushChannelRegistrations(for: self.pushToken) { result in + switch result { + case let .success(channels): + XCTAssertTrue(channels.isEmpty) + case let .failure(error): + XCTFail("List push registrations call failed due to \(error.localizedDescription)") + } + listExpect.fulfill() + } + removeAllExpect.fulfill() + } + addExpect.fulfill() + } + + wait(for: [addExpect, removeAllExpect, listExpect], timeout: 10.0) + } + + func testRemoveAPNSDevicesOnChannels() { + let addExpect = expectation(description: "Adding Channels") + let removeExpect = expectation(description: "Removing Channels") + let listExpect = expectation(description: "Listing Channels") + let client = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + + // First add some channels + client.manageAPNSDevicesOnChannels( + byRemoving: [], + thenAdding: ["c1", "c2", "c3"], + device: pushToken, + on: pushTopic + ) { [unowned self, unowned client] result in + // Then remove specific channels + client.removeAPNSDevicesOnChannels( + ["c1", "c2"], + device: pushToken, + on: pushTopic + ) { result in + // List remaining channels + client.listAPNSPushChannelRegistrations(for: self.pushToken, on: self.pushTopic) { result in + switch result { + case let .success(channels): + XCTAssertEqual(Set(channels), Set(["c3"])) + case let .failure(error): + XCTFail("List APNS call failed due to \(error.localizedDescription)") + } + listExpect.fulfill() + } + removeExpect.fulfill() + } + addExpect.fulfill() + } + + defer { + waitForCompletion { + client.removeAllAPNSPushDevice( + for: pushToken, + on: pushTopic, + completion: $0 + ) + } + } + + wait(for: [addExpect, removeExpect, listExpect], timeout: 10.0) + } } diff --git a/Tests/PubNubTests/Integration/SubscriptionIntegrationTests.swift b/Tests/PubNubTests/Integration/SubscriptionIntegrationTests.swift index c6d74e8e..2375f741 100644 --- a/Tests/PubNubTests/Integration/SubscriptionIntegrationTests.swift +++ b/Tests/PubNubTests/Integration/SubscriptionIntegrationTests.swift @@ -13,360 +13,295 @@ import XCTest class SubscriptionIntegrationTests: XCTestCase { let testsBundle = Bundle(for: SubscriptionIntegrationTests.self) - let testChannel = "SwiftSubscriptionITestsChannel" func testSubscribeError() { let configuration = PubNubConfiguration( publishKey: "", subscribeKey: "", - userId: UUID().uuidString, - enableEventEngine: false - ) - let eeConfiguration = PubNubConfiguration( - publishKey: "", - subscribeKey: "", - userId: UUID().uuidString, - enableEventEngine: true + userId: UUID().uuidString ) - for config in [configuration, eeConfiguration] { - XCTContext.runActivity(named: "Testing configuration with enableEventEngine=\(config.enableEventEngine)") { _ in - let subscribeExpect = expectation(description: "Subscribe Expectation") - let disconnectedExpect = expectation(description: "Disconnected Expectation") - disconnectedExpect.assertForOverFulfill = true - disconnectedExpect.expectedFulfillmentCount = 1 - - // Should return subscription key error - let pubnub = PubNub(configuration: config) - let listener = SubscriptionListener() - - listener.didReceiveSubscription = { event in - switch event { - case let .connectionStatusChanged(status): - switch status { - case .disconnectedUnexpectedly: - disconnectedExpect.fulfill() - case .connectionError: - disconnectedExpect.fulfill() - default: - XCTFail("Only should emit these two states") - } - case .subscribeError: - subscribeExpect.fulfill() // 8E988B17-C0AA-42F1-A6F9-1461BF51C82C - default: - break - } + let subscribeExpect = expectation(description: "Subscribe Expectation") + subscribeExpect.assertForOverFulfill = true + subscribeExpect.expectedFulfillmentCount = 1 + + let disconnectedExpect = expectation(description: "Disconnected Expectation") + disconnectedExpect.assertForOverFulfill = true + disconnectedExpect.expectedFulfillmentCount = 1 + + // Should return subscription key error + let pubnub = PubNub(configuration: configuration) + let listener = SubscriptionListener() + + listener.didReceiveSubscription = { event in + switch event { + case let .connectionStatusChanged(status): + switch status { + case .disconnectedUnexpectedly: + disconnectedExpect.fulfill() + case .connectionError: + disconnectedExpect.fulfill() + default: + XCTFail("Only should emit these two states") } - - pubnub.add(listener) - pubnub.subscribe(to: [testChannel]) - - defer { pubnub.disconnect() } - wait(for: [subscribeExpect, disconnectedExpect], timeout: 10.0) + case .subscribeError: + subscribeExpect.fulfill() + default: + break } } + + pubnub.add(listener) + pubnub.subscribe(to: [randomString()]) + + defer { pubnub.disconnect() } + wait(for: [subscribeExpect, disconnectedExpect], timeout: 10.0) } - + // swiftlint:disable:next function_body_length cyclomatic_complexity func testUnsubscribeResubscribe() { - let configurationFromBundle = PubNubConfiguration( - publishKey: PubNubConfiguration(from: testsBundle).publishKey, - subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, - userId: PubNubConfiguration(from: testsBundle).userId, - enableEventEngine: false - ) - let configWithEventEngineEnabled = PubNubConfiguration( - publishKey: configurationFromBundle.publishKey, - subscribeKey: configurationFromBundle.subscribeKey, - userId: configurationFromBundle.userId, - enableEventEngine: true - ) + let totalLoops = 10 + let testChannel = randomString() + + let subscribeExpect = expectation(description: "Subscribe Expectation") + subscribeExpect.assertForOverFulfill = true + subscribeExpect.expectedFulfillmentCount = totalLoops - for config in [configurationFromBundle, configWithEventEngineEnabled] { - XCTContext.runActivity(named: "Testing configuration with enableEventEngine=\(config.enableEventEngine)") { _ in - let totalLoops = 10 - let subscribeExpect = expectation(description: "Subscribe Expectation") - subscribeExpect.expectedFulfillmentCount = totalLoops - let unsubscribeExpect = expectation(description: "Unsubscribe Expectation") - unsubscribeExpect.expectedFulfillmentCount = totalLoops - let publishExpect = expectation(description: "Publish Expectation") - publishExpect.expectedFulfillmentCount = totalLoops - let connectedExpect = expectation(description: "Connected Expectation") - connectedExpect.expectedFulfillmentCount = totalLoops - let disconnectedExpect = expectation(description: "Disconnected Expectation") - disconnectedExpect.expectedFulfillmentCount = totalLoops - - let pubnub = PubNub(configuration: config) - var connectedCount = 0 - - let listener = SubscriptionListener() - listener.didReceiveSubscription = { [unowned self, unowned pubnub] event in - switch event { - case let .subscriptionChanged(status): - switch status { - case let .subscribed(channels, _): - XCTAssertTrue(channels.contains(where: { $0.id == self.testChannel })) - XCTAssertTrue(pubnub.subscribedChannels.contains(self.testChannel)) - subscribeExpect.fulfill() - case let .responseHeader(channels, _, _, next): - XCTAssertTrue(channels.contains(where: { $0.id == self.testChannel })) - XCTAssertEqual(pubnub.previousTimetoken, next?.timetoken) - case let .unsubscribed(channels, _): - XCTAssertTrue(channels.contains(where: { $0.id == self.testChannel })) - XCTAssertFalse(pubnub.subscribedChannels.contains(self.testChannel)) - unsubscribeExpect.fulfill() - } - case .messageReceived: - pubnub.unsubscribe(from: [self.testChannel]) - publishExpect.fulfill() - case let .connectionStatusChanged(status): - switch status { - case .connected: - pubnub.publish(channel: self.testChannel, message: "Test") { _ in } - connectedCount += 1 - connectedExpect.fulfill() - case .disconnected: - // Stop reconneced after N attempts - if connectedCount < totalLoops { - pubnub.subscribe(to: [self.testChannel]) - } - disconnectedExpect.fulfill() - default: - break - } - case let .subscribeError(error): - XCTFail("An error was returned: \(error)") - default: - break - } - } - - pubnub.add(listener) - pubnub.subscribe(to: [testChannel]) - - defer { pubnub.disconnect() } - wait(for: [subscribeExpect, unsubscribeExpect, publishExpect, connectedExpect, disconnectedExpect], timeout: 30.0) - } - } - } - - func test_MixedSubscriptions() { - let configurationFromBundle = PubNubConfiguration( - publishKey: PubNubConfiguration(from: testsBundle).publishKey, - subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, - userId: PubNubConfiguration(from: testsBundle).userId, - enableEventEngine: false - ) - let configWithEventEngineEnabled = PubNubConfiguration( - publishKey: configurationFromBundle.publishKey, - subscribeKey: configurationFromBundle.subscribeKey, - userId: configurationFromBundle.userId, - enableEventEngine: true - ) + let unsubscribeExpect = expectation(description: "Unsubscribe Expectation") + unsubscribeExpect.assertForOverFulfill = true + unsubscribeExpect.expectedFulfillmentCount = totalLoops - for config in [configurationFromBundle, configWithEventEngineEnabled] { - XCTContext.runActivity(named: "Testing with enableEventEngine=\(config.enableEventEngine)") { _ in - let subscribedEventExpect = XCTestExpectation(description: "SubscribedEvent") - subscribedEventExpect.assertForOverFulfill = true - subscribedEventExpect.expectedFulfillmentCount = 1 - - let responseHeaderExpect = XCTestExpectation(description: "ResponseReceivedEvent") - responseHeaderExpect.assertForOverFulfill = true - responseHeaderExpect.expectedFulfillmentCount = 1 - - let usubscribeEventExpect = XCTestExpectation(description: "UnsubscribedEvent") - usubscribeEventExpect.assertForOverFulfill = true - usubscribeEventExpect.expectedFulfillmentCount = 1 + let publishExpect = expectation(description: "Publish Expectation") + publishExpect.assertForOverFulfill = true + publishExpect.expectedFulfillmentCount = totalLoops + + let connectedExpect = expectation(description: "Connected Expectation") + connectedExpect.assertForOverFulfill = true + connectedExpect.expectedFulfillmentCount = totalLoops + + let disconnectedExpect = expectation(description: "Disconnected Expectation") + disconnectedExpect.assertForOverFulfill = true + disconnectedExpect.expectedFulfillmentCount = totalLoops - let pubnub = PubNub(configuration: config) - let listener = SubscriptionListener() - var firstSubscription: Subscription? = pubnub.channel(testChannel).subscription() - var secondSubscription: Subscription? = pubnub.channel(testChannel).subscription() - var subscriptionSet: SubscriptionSet? = pubnub.subscription(entities: [pubnub.channel(testChannel)]) - - listener.didReceiveSubscription = { [unowned self, unowned pubnub] event in - switch event { - case let .subscriptionChanged(status): - switch status { - case let .subscribed(channels, _): - XCTAssertTrue(channels.contains(where: { $0.id == self.testChannel })) - XCTAssertTrue(pubnub.subscribedChannels.contains(self.testChannel)) - subscribedEventExpect.fulfill() - case let .responseHeader(channels, _, _, _): - XCTAssertTrue(channels.contains(where: { $0.id == self.testChannel })) - responseHeaderExpect.fulfill() - case let .unsubscribed(channels, _): - XCTAssertTrue(channels.contains(where: { $0.id == self.testChannel })) - XCTAssertFalse(pubnub.subscribedChannels.contains(self.testChannel)) - usubscribeEventExpect.fulfill() - } - case let .connectionStatusChanged(status): - switch status { - case .connected: - firstSubscription = nil - secondSubscription = nil - pubnub.unsubscribe(from: [self.testChannel]) - subscriptionSet = nil - default: - break - } - case let .subscribeError(error): - XCTFail("An error was returned: \(error)") - default: - break + // Stores the current number of times the client has connected + var connectedCount = 0 + + let pubnub = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + let listener = SubscriptionListener() + + listener.didReceiveSubscription = { [unowned pubnub] event in + switch event { + case let .subscriptionChanged(status): + switch status { + case let .subscribed(channels, _): + XCTAssertTrue(channels.contains(where: { $0.id == testChannel })) + XCTAssertTrue(pubnub.subscribedChannels.contains(testChannel)) + subscribeExpect.fulfill() + case let .responseHeader(channels, _, _, next): + XCTAssertTrue(channels.contains(where: { $0.id == testChannel })) + XCTAssertEqual(pubnub.previousTimetoken, next?.timetoken) + case let .unsubscribed(channels, _): + XCTAssertTrue(channels.contains(where: { $0.id == testChannel })) + XCTAssertFalse(pubnub.subscribedChannels.contains(testChannel)) + unsubscribeExpect.fulfill() + } + case .messageReceived: + pubnub.unsubscribe(from: [testChannel]) + publishExpect.fulfill() + case let .connectionStatusChanged(status): + switch status { + case .connected: + pubnub.publish(channel: testChannel, message: "Test") { _ in } + connectedCount += 1 + connectedExpect.fulfill() + case .disconnected: + // Stop reconneced after N attempts + if connectedCount < totalLoops { + pubnub.subscribe(to: [testChannel]) } + disconnectedExpect.fulfill() + default: + break } - - pubnub.add(listener) - pubnub.subscribe(to: [testChannel]) - firstSubscription?.subscribe() - secondSubscription?.subscribe() - subscriptionSet?.subscribe() - - defer { pubnub.disconnect() } - wait(for: [subscribedEventExpect, responseHeaderExpect, usubscribeEventExpect], timeout: 30.0) + case let .subscribeError(error): + XCTFail("An error was returned: \(error)") + default: + break } } + + pubnub.add(listener) + pubnub.subscribe(to: [testChannel]) + + defer { pubnub.disconnect() } + wait(for: [subscribeExpect, unsubscribeExpect, publishExpect, connectedExpect, disconnectedExpect], timeout: 30.0) } - func test_GlobalSubscription() { - let configurationFromBundle = PubNubConfiguration( - publishKey: PubNubConfiguration(from: testsBundle).publishKey, - subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, - userId: PubNubConfiguration(from: testsBundle).userId, - enableEventEngine: false - ) - let configWithEventEngineEnabled = PubNubConfiguration( - publishKey: configurationFromBundle.publishKey, - subscribeKey: configurationFromBundle.subscribeKey, - userId: configurationFromBundle.userId, - enableEventEngine: true - ) + func testMixedSubscriptionsToTheSameChannel() { + let subscribedEventExpect = expectation(description: "Subscribed Event Expect") + subscribedEventExpect.assertForOverFulfill = true + subscribedEventExpect.expectedFulfillmentCount = 1 + + let responseHeaderExpect = expectation(description: "Response Received Event Expect") + responseHeaderExpect.assertForOverFulfill = true + responseHeaderExpect.expectedFulfillmentCount = 1 + + let usubscribeEventExpect = expectation(description: "Unsubscribed Event Expect") + usubscribeEventExpect.assertForOverFulfill = true + usubscribeEventExpect.expectedFulfillmentCount = 1 - for config in [configurationFromBundle, configWithEventEngineEnabled] { - XCTContext.runActivity(named: "Testing with enableEventEngine=\(config.enableEventEngine)") { _ in - let messageExpect = XCTestExpectation(description: "Message") - messageExpect.assertForOverFulfill = true - messageExpect.expectedFulfillmentCount = 1 - - let statusExpect = XCTestExpectation(description: "StatusExpect") - statusExpect.assertForOverFulfill = true - statusExpect.expectedFulfillmentCount = 2 + let disconnectedStatusExpect = expectation(description: "Disconnected Status Expect") + disconnectedStatusExpect.assertForOverFulfill = true + disconnectedStatusExpect.expectedFulfillmentCount = 1 - let pubnub = PubNub(configuration: config) - var statusCounter = 0 - - pubnub.onMessage = { [unowned pubnub] message in - XCTAssertTrue(message.payload.stringOptional == "This is a message") - messageExpect.fulfill() - pubnub.unsubscribe(from: [self.testChannel]) + let pubnub = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + let listener = SubscriptionListener() + let testChannelName = randomString() + + var firstSubscription: Subscription? = pubnub.channel(testChannelName).subscription() + var secondSubscription: Subscription? = pubnub.channel(testChannelName).subscription() + var subscriptionSet: SubscriptionSet? = pubnub.subscription(entities: [pubnub.channel(testChannelName)]) + + listener.didReceiveSubscription = { [unowned pubnub] event in + switch event { + case let .subscriptionChanged(status): + switch status { + case let .subscribed(channels, _): + XCTAssertTrue(channels.contains(where: { $0.id == testChannelName })) + XCTAssertTrue(pubnub.subscribedChannels.contains(testChannelName)) + subscribedEventExpect.fulfill() + case let .responseHeader(channels, _, _, _): + XCTAssertTrue(channels.contains(where: { $0.id == testChannelName })) + responseHeaderExpect.fulfill() + case let .unsubscribed(channels, _): + XCTAssertTrue(channels.contains(where: { $0.id == testChannelName })) + XCTAssertFalse(pubnub.subscribedChannels.contains(testChannelName)) + usubscribeEventExpect.fulfill() } - pubnub.onConnectionStateChange = { [unowned pubnub, unowned self] change in - if statusCounter == 0 { - XCTAssertTrue(change == .connected) - pubnub.publish(channel: self.testChannel, message: "This is a message", completion: nil) - } else if statusCounter == 1 { - XCTAssertTrue(change == .disconnected) - } else { - XCTFail("Unexpected condition") - } - statusCounter += 1 - statusExpect.fulfill() + case let .connectionStatusChanged(status): + switch status { + case .connected: + firstSubscription = nil + secondSubscription = nil + subscriptionSet = nil + pubnub.unsubscribe(from: [testChannelName]) + case .disconnected: + disconnectedStatusExpect.fulfill() + default: + break } - pubnub.subscribe(to: [testChannel]) - - defer { pubnub.disconnect() } - wait(for: [statusExpect, messageExpect], timeout: 30.0) + case let .subscribeError(error): + XCTFail("An error was returned: \(error)") + default: + break } } + + pubnub.add(listener) + pubnub.subscribe(to: [testChannelName]) + firstSubscription?.subscribe() + secondSubscription?.subscribe() + subscriptionSet?.subscribe() + + defer { pubnub.disconnect() } + wait(for: [subscribedEventExpect, responseHeaderExpect, usubscribeEventExpect, disconnectedStatusExpect], timeout: 30.0, enforceOrder: true) } - func test_SimultaneousSubscriptionToDifferentChannels() { - let expectation = XCTestExpectation(description: "Expectation") - expectation.assertForOverFulfill = true - expectation.expectedFulfillmentCount = 3 - - let publishExpectation = XCTestExpectation(description: "Publish") - publishExpectation.assertForOverFulfill = true - publishExpectation.expectedFulfillmentCount = 1 - - let pubnub = PubNub(configuration: PubNubConfiguration( - publishKey: PubNubConfiguration(from: testsBundle).publishKey, - subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, - userId: PubNubConfiguration(from: testsBundle).userId - )) - let timetoken = Timetoken( - Int(Date().timeIntervalSince1970 * 10000000) - ) + func testGlobalPubNubSubscription() { + let messageExpect = expectation(description: "Message Expect") + messageExpect.assertForOverFulfill = true + messageExpect.expectedFulfillmentCount = 1 + + let statusExpect = expectation(description: "Status Expect") + statusExpect.assertForOverFulfill = true + statusExpect.expectedFulfillmentCount = 2 - pubnub.publish(channel: testChannel, message: "Message", completion: { [unowned pubnub, unowned self] _ in - pubnub.publish(channel: self.testChannel, message: "Second message", completion: { _ in - publishExpectation.fulfill() - }) - }) + let pubnub = PubNub(configuration: PubNubConfiguration(from: testsBundle)) + let testChannelName = randomString() - wait(for: [publishExpectation], timeout: 1.5) + // Tracks the number of times the status has changed + var statusCounter = 0 - let anotherChannel = testChannel.appending("2") - let listener = SubscriptionListener() - - listener.didReceiveMessage = { _ in - expectation.fulfill() + pubnub.onMessage = { [unowned pubnub] message in + XCTAssertEqual(message.payload.stringOptional, "This is a message") + messageExpect.fulfill() + pubnub.unsubscribe(from: [testChannelName]) + } + pubnub.onConnectionStateChange = { [unowned pubnub] change in + if statusCounter == 0 { + XCTAssertTrue(change == .connected) + pubnub.publish(channel: testChannelName, message: "This is a message", completion: nil) + } else if statusCounter == 1 { + XCTAssertTrue(change == .disconnected) + } else { + XCTFail("Unexpected condition") + } + statusCounter += 1 + statusExpect.fulfill() } - pubnub.add(listener) - pubnub.subscribe(to: [testChannel], at: timetoken) - pubnub.publish(channel: testChannel, message: "Third message", completion: nil) - pubnub.subscribe(to: [anotherChannel]) + pubnub.subscribe(to: [testChannelName]) defer { pubnub.disconnect() } - wait(for: [expectation], timeout: 10) + wait(for: [statusExpect, messageExpect], timeout: 30.0) } - - func test_SimultaneousSubscriptionsToTheSameChannel() { - let expectation = XCTestExpectation(description: "Test Simultaneous Subscriptions") + + func testSubscriptionsWithCustomTimetoken() { + let expectation = expectation(description: "Expectation") expectation.assertForOverFulfill = true - expectation.expectedFulfillmentCount = 1 - - let pubnub = PubNub(configuration: PubNubConfiguration( - publishKey: PubNubConfiguration(from: testsBundle).publishKey, - subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, - userId: PubNubConfiguration(from: testsBundle).userId - )) + expectation.expectedFulfillmentCount = 4 + + let pubnub = PubNub(configuration: .init(from: testsBundle)) + let customTimetoken = Timetoken(Int(Date().timeIntervalSince1970 * 10000000)) - let channelName = "channel" + let testChannelName = randomString() + let anotherTestChannelName = testChannelName.appending("2") + let listener = SubscriptionListener() + let expectedMessagesSet = ["First message", "Second message", "Third message", "Fourth message"] - pubnub.onConnectionStateChange = { newStatus in - switch newStatus { - case .connected: - expectation.fulfill() - default: - XCTFail("Unexpected connection status") - } + // Listens for messages on all currently subscribed channels. + // We expect the listener to receive all the messages newer than the `customTimetoken` property. + listener.didReceiveMessage = { message in + XCTAssertTrue(expectedMessagesSet.contains(message.payload.stringOptional ?? "")) + XCTAssertTrue([testChannelName, anotherTestChannelName].contains(message.channel)) + expectation.fulfill() } - pubnub.subscribe(to: [channelName]) - pubnub.subscribe(to: [channelName]) + // Closure to call after the channel is populated with messages + let performSubscribeCall = { [unowned pubnub] in + // Adds the listener to the PubNub client + pubnub.add(listener) + // Subscribes to the channel with the timetoken prior to populating the channel with test messages + pubnub.subscribe(to: [testChannelName], at: customTimetoken) + // Adds another channel to subscribe to + pubnub.subscribe(to: [anotherTestChannelName]) + pubnub.publish(channel: testChannelName, message: "Third message", completion: nil) + pubnub.publish(channel: anotherTestChannelName, message: "Fourth message", completion: nil) + } + + // Populates the channel with messages + pubnub.publish(channel: testChannelName, message: "First message", completion: { [unowned pubnub] _ in + pubnub.publish(channel: testChannelName, message: "Second message", completion: { _ in + performSubscribeCall() + }) + }) - XCTAssertEqual(pubnub.subscribedChannels, [channelName]) - wait(for: [expectation], timeout: 5.0) + defer { pubnub.disconnect() } + wait(for: [expectation], timeout: 10.0) } - func test_SimultaneousSubscriptionsToTheSameChannelWithTimetoken() { - let expectation = XCTestExpectation(description: "Test Simultaneous Subscriptions With Timetoken") + func testSimultaneousSubscriptionsToTheSameChannel() { + let expectation = expectation(description: "Test Simultaneous Subscriptions") expectation.assertForOverFulfill = true expectation.expectedFulfillmentCount = 1 - let pubnub = PubNub(configuration: PubNubConfiguration( - publishKey: PubNubConfiguration(from: testsBundle).publishKey, - subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, - userId: PubNubConfiguration(from: testsBundle).userId - )) - - let channelName = "channel" + let pubnub = PubNub(configuration: .init(from: testsBundle)) + let testChannelName = randomString() - pubnub.onConnectionStateChange = { newStatus in - switch newStatus { + // We expect the long-polling connection won't be interrupted by the second subscription, which is + // subscribing to the same channel + pubnub.onConnectionStateChange = { + switch $0 { case .connected: expectation.fulfill() default: @@ -374,58 +309,53 @@ class SubscriptionIntegrationTests: XCTestCase { } } - pubnub.subscribe(to: [channelName]) - pubnub.subscribe(to: [channelName], at: Timetoken(Int(Date().timeIntervalSince1970 * 10000000))) + pubnub.subscribe(to: [testChannelName]) + pubnub.subscribe(to: [testChannelName]) + XCTAssertEqual(pubnub.subscribedChannels, [testChannelName]) wait(for: [expectation], timeout: 5.0) } - func test_AddingNextLegacyListenerInTheMeantime() { - let expectation = XCTestExpectation(description: "Message expectation") + func testAddingNextLegacyListenerInTheMeantime() { + let expectation = expectation(description: "Message expectation") expectation.assertForOverFulfill = true expectation.expectedFulfillmentCount = 2 - let pubnub = PubNub(configuration: PubNubConfiguration( - publishKey: PubNubConfiguration(from: testsBundle).publishKey, - subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, - userId: PubNubConfiguration(from: testsBundle).userId - )) - + let pubnub = PubNub(configuration: .init(from: testsBundle)) let listener = SubscriptionListener() let secondListener = SubscriptionListener() - + let testChannelName = randomString() + listener.didReceiveMessage = { message in expectation.fulfill() } secondListener.didReceiveMessage = { message in expectation.fulfill() } - listener.didReceiveStatus = { [unowned pubnub, unowned self] statusChange in + + listener.didReceiveStatus = { [unowned pubnub] statusChange in if case .success(let status) = statusChange, status == .connected { pubnub.add(secondListener) - pubnub.publish(channel: testChannel, message: "Message", completion: nil) + pubnub.publish(channel: testChannelName, message: "Message", completion: nil) } } pubnub.add(listener) - pubnub.subscribe(to: [testChannel]) + pubnub.subscribe(to: [testChannelName]) - wait(for: [expectation], timeout: 5.0) + wait(for: [expectation], timeout: 10.0) } - func test_AddingNextListenerUsingSubscriptionObjects() { + func testAddingNextListenerUsingSubscriptionObjects() { let expectation = XCTestExpectation(description: "Message expectation") expectation.assertForOverFulfill = true expectation.expectedFulfillmentCount = 2 - let pubnub = PubNub(configuration: PubNubConfiguration( - publishKey: PubNubConfiguration(from: testsBundle).publishKey, - subscribeKey: PubNubConfiguration(from: testsBundle).subscribeKey, - userId: PubNubConfiguration(from: testsBundle).userId - )) - - let firstSubscription = pubnub.channel(testChannel).subscription() - let secondSubscription = pubnub.channel(testChannel).subscription() + let testChannelName = randomString() + let pubnub = PubNub(configuration: .init(from: testsBundle)) + + let firstSubscription = pubnub.channel(testChannelName).subscription() + let secondSubscription = pubnub.channel(testChannelName).subscription() firstSubscription.onMessage = { message in expectation.fulfill() @@ -433,10 +363,11 @@ class SubscriptionIntegrationTests: XCTestCase { secondSubscription.onMessage = { message in expectation.fulfill() } - pubnub.onConnectionStateChange = { [unowned pubnub, unowned self] newStatus in + + pubnub.onConnectionStateChange = { [unowned pubnub] newStatus in if newStatus == .connected { secondSubscription.subscribe() - pubnub.publish(channel: testChannel, message: "Message", completion: nil) + pubnub.publish(channel: testChannelName, message: "Message", completion: nil) } } diff --git a/Tests/PubNubTests/Integration/UUIDObjectsEndpointIntegrationTests.swift b/Tests/PubNubTests/Integration/UUIDObjectsEndpointIntegrationTests.swift deleted file mode 100644 index bdb01f4f..00000000 --- a/Tests/PubNubTests/Integration/UUIDObjectsEndpointIntegrationTests.swift +++ /dev/null @@ -1,195 +0,0 @@ -// -// UUIDObjectsEndpointIntegrationTests.swift -// -// Copyright (c) PubNub Inc. -// All rights reserved. -// -// This source code is licensed under the license found in the -// LICENSE file in the root directory of this source tree. -// - -import PubNubSDK -import XCTest - -class UUIDObjectsEndpointIntegrationTests: XCTestCase { - let config = PubNubConfiguration(from: Bundle(for: UUIDObjectsEndpointIntegrationTests.self)) - - func testFetchAllEndpoint() { - let fetchAllExpect = expectation(description: "Fetch All Expectation") - - let client = PubNub(configuration: config) - - client.allUUIDMetadata(sort: [.init(property: .updated)]) { result in - switch result { - case let .success((users, nextPage)): - XCTAssertTrue(nextPage?.totalCount ?? 0 >= users.count) - case let .failure(error): - XCTFail("Failed due to error: \(error)") - } - fetchAllExpect.fulfill() - } - - wait(for: [fetchAllExpect], timeout: 10.0) - } - - func testUserCreateAndFetchEndpoint() { - let fetchExpect = expectation(description: "Fetch User Expectation") - - let client = PubNub(configuration: config) - - let testUser = PubNubUserMetadataBase( - metadataId: "testUserCreateAndFetchEndpoint", name: "Swift ITest", profileURL: "http://example.com" - ) - - client.set(uuid: testUser) { _ in - client.fetch(uuid: testUser.metadataId) { result in - switch result { - case let .success(user): - XCTAssertEqual(user.metadataId, testUser.metadataId) - XCTAssertEqual(user.profileURL, testUser.profileURL) - case let .failure(error): - XCTFail("Failed due to error: \(error)") - } - fetchExpect.fulfill() - } - } - - wait(for: [fetchExpect], timeout: 10.0) - } - - func testUserDeleteAndCreateEndpoint() { - let fetchExpect = expectation(description: "Create User Expectation") - - let client = PubNub(configuration: config) - - let testUser = PubNubUserMetadataBase( - metadataId: "testUserDeleteAndCreateEndpoint", name: "Swift ITest" - ) - - client.remove(uuid: testUser.metadataId) { _ in - client.set(uuid: testUser) { result in - switch result { - case let .success(user): - XCTAssertEqual(user.metadataId, testUser.metadataId) - case let .failure(error): - XCTFail("Failed due to error: \(error)") - } - fetchExpect.fulfill() - } - } - - wait(for: [fetchExpect], timeout: 10.0) - } - - func testUserCreateAndDeleteEndpoint() { - let fetchExpect = expectation(description: "Delete User Expectation") - - let client = PubNub(configuration: config) - - let testUser = PubNubUserMetadataBase( - metadataId: "testUserCreateAndDeleteEndpoint", name: "Swift ITest" - ) - - client.set(uuid: testUser) { _ in - client.remove(uuid: testUser.metadataId) { result in - switch result { - case let .success(userMetadataId): - XCTAssertTrue(userMetadataId == testUser.metadataId) - case let .failure(error): - XCTFail("Failed due to error: \(error)") - } - fetchExpect.fulfill() - } - } - - wait(for: [fetchExpect], timeout: 10.0) - } - - func testUserFetchMemberships() { - let fetchMembershipExpect = expectation(description: "Fetch Membership Expectation") - - let client = PubNub(configuration: config) - - let testUser = PubNubUserMetadataBase( - metadataId: "testUserFetchMemberships", name: "Swift ITest" - ) - let testChannel = PubNubChannelMetadataBase( - metadataId: "testUserFetchMembershipsSpace", name: "Swift Membership ITest" - ) - let membership = PubNubMembershipMetadataBase( - uuidMetadataId: testUser.metadataId, - channelMetadataId: testChannel.metadataId, - uuid: testUser, channel: testChannel - ) - - client.set(uuid: testUser) { _ in - client.set(channel: testChannel) { _ in - client.setMemberships(uuid: testUser.metadataId, channels: [membership]) { _ in - client.fetchMemberships( - uuid: testUser.metadataId, - include: .init(channelFields: true, channelCustomFields: true), - sort: [.init(property: .object(.id), ascending: false), .init(property: .updated)] - ) { result in - switch result { - case let .success((memberships, _)): - XCTAssertTrue( - memberships.contains(where: { - $0.channelMetadataId == testChannel.metadataId && $0.uuidMetadataId == testUser.metadataId - } - ) - ) - case let .failure(error): - XCTFail("Failed due to error: \(error)") - } - fetchMembershipExpect.fulfill() - } - } - } - } - - wait(for: [fetchMembershipExpect], timeout: 10.0) - } - - func testUpdateMemberships() { - let updateMembershipExpect = expectation(description: "Update Membership Expectation") - - let client = PubNub(configuration: config) - - let testUser = PubNubUserMetadataBase( - metadataId: "testUpdateMemberships", name: "Swift ITest" - ) - let testChannel = PubNubChannelMetadataBase( - metadataId: "testUpdateMembershipsSpace", name: "Swift Membership ITest" - ) - let membership = PubNubMembershipMetadataBase( - uuidMetadataId: testUser.metadataId, - channelMetadataId: testChannel.metadataId, - uuid: testUser, channel: testChannel - ) - - client.set(uuid: testUser) { _ in - client.set(channel: testChannel) { _ in - client.manageMemberships( - uuid: testUser.metadataId, - setting: [membership], - removing: [membership] - ) { result in - switch result { - case let .success((memberships, _)): - XCTAssertTrue( - memberships.contains(where: { - $0.channelMetadataId == testChannel.metadataId && $0.uuidMetadataId == testUser.metadataId - } - ) - ) - case let .failure(error): - XCTFail("Failed due to error: \(error)") - } - updateMembershipExpect.fulfill() - } - } - } - - wait(for: [updateMembershipExpect], timeout: 10.0) - } -} diff --git a/Tests/PubNubTests/Integration/UserObjectsEndpointIntegrationTests.swift b/Tests/PubNubTests/Integration/UserObjectsEndpointIntegrationTests.swift new file mode 100644 index 00000000..762a199d --- /dev/null +++ b/Tests/PubNubTests/Integration/UserObjectsEndpointIntegrationTests.swift @@ -0,0 +1,533 @@ +// +// UserObjectsEndpointIntegrationTests.swift +// +// Copyright (c) PubNub Inc. +// All rights reserved. +// +// This source code is licensed under the license found in the +// LICENSE file in the root directory of this source tree. +// + +import PubNubSDK +import XCTest + +class UserObjectsEndpointIntegrationTests: XCTestCase { + let config = PubNubConfiguration(from: Bundle(for: UserObjectsEndpointIntegrationTests.self)) + + func testFetchAllEndpoint() { + let fetchAllExpect = expectation(description: "Fetch All Expectation") + let client = PubNub(configuration: config) + let expectedUsers = setupTestUsers(client: client) + + client.allUserMetadata(filter: "id LIKE 'swift-*'") { result in + switch result { + case let .success((users, _)): + let expectedIds = expectedUsers.map { $0.metadataId }.sorted() + let actualIds = users.map { $0.metadataId }.sorted() + XCTAssertEqual(expectedIds, actualIds) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + fetchAllExpect.fulfill() + } + + defer { + for user in expectedUsers { + waitForCompletion { + client.removeUserMetadata( + user.metadataId, + completion: $0 + ) + } + } + } + + wait(for: [fetchAllExpect], timeout: 10.0) + } + + func testUserCreateAndFetchEndpoint() { + let fetchExpect = expectation(description: "Fetch User Expectation") + let client = PubNub(configuration: config) + let testUser = PubNubUserMetadataBase(metadataId: "testUserCreateAndFetchEndpoint", name: "Swift ITest") + + client.setUserMetadata(testUser) { [unowned client] setResult in + client.fetchUserMetadata(testUser.metadataId) { result in + switch result { + case let .success(user): + XCTAssertEqual(user.metadataId, testUser.metadataId) + XCTAssertEqual(user.profileURL, testUser.profileURL) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + fetchExpect.fulfill() + } + } + + defer { + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + } + + wait(for: [fetchExpect], timeout: 10.0) + } + + func testUserCreateAndDeleteEndpoint() { + let fetchExpect = expectation(description: "Fetch User Expectation") + let client = PubNub(configuration: config) + let testUser = PubNubUserMetadataBase(metadataId: "testUserCreateAndDeleteEndpoint", name: "Swift ITest") + + client.setUserMetadata(testUser) { [unowned client] _ in + client.removeUserMetadata(testUser.metadataId) { result in + switch result { + case let .success(userMetadataId): + XCTAssertTrue(userMetadataId == testUser.metadataId) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + fetchExpect.fulfill() + } + } + + defer { + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + } + + wait(for: [fetchExpect], timeout: 10.0) + } + + func testFetchNotExistingUser() { + let fetchExpect = expectation(description: "Fetch User Expectation") + let client = PubNub(configuration: config) + let testUser = PubNubUserMetadataBase(metadataId: "testFetchNotExistingUser", name: "Swift ITest") + + client.fetchUserMetadata(testUser.metadataId) { result in + switch result { + case .success: + XCTFail("Test should fail") + case let .failure(error): + XCTAssertNotNil(error.pubNubError) + XCTAssertEqual(error.pubNubError?.reason, .resourceNotFound) + } + fetchExpect.fulfill() + } + + defer { + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + } + + wait(for: [fetchExpect], timeout: 10.0) + } + + func testSetUserWithEntityTag() { + let setExpect = expectation(description: "Delete User Expectation") + let client = PubNub(configuration: config) + + var testUser = PubNubUserMetadataBase( + metadataId: randomString(), + name: "Swift ITest", + externalId: "ABC", + profileURL: "https://example.com" + ) + + client.setUserMetadata(testUser) { [unowned client] firstResult in + // Update the user metadata + testUser.profileURL = "https://example2.com" + testUser.externalId = "XYZ" + // Set the user metadata with the ifMatchesEtag parameter + client.setUserMetadata(testUser, ifMatchesEtag: "12345") { result in + switch result { + case .success: + XCTFail("Test should fail") + case let .failure(error): + XCTAssertNotNil(error.pubNubError) + XCTAssertEqual(error.pubNubError?.reason, .preconditionFailed) + } + setExpect.fulfill() + } + } + + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + + wait(for: [setExpect], timeout: 10.0) + } + + func testUserFetchMemberships() { + let fetchMembershipExpect = expectation(description: "Fetch Membership Expectation") + let client = PubNub(configuration: config) + + let testUser = PubNubUserMetadataBase( + metadataId: "testUserFetchMemberships", + name: "Swift ITest" + ) + let testChannel = PubNubChannelMetadataBase( + metadataId: "testUserFetchMembershipsSpace", + name: "Swift Membership ITest" + ) + + let membership = PubNubMembershipMetadataBase( + userMetadataId: testUser.metadataId, + channelMetadataId: testChannel.metadataId, + user: testUser, + channel: testChannel + ) + + client.setUserMetadata(testUser) { [unowned client] _ in + client.setChannelMetadata(testChannel) { _ in + client.setMemberships(userId: testUser.metadataId, channels: [membership]) { _ in + client.fetchMemberships( + userId: testUser.metadataId, + include: .init(channelFields: true, channelCustomFields: true), + sort: [.init(property: .object(.id), ascending: false), .init(property: .updated)] + ) { result in + switch result { + case let .success((memberships, _)): + XCTAssertEqual(memberships.count, 1) + XCTAssertTrue(memberships.allSatisfy { + $0.channelMetadataId == testChannel.metadataId && $0.userMetadataId == testUser.metadataId + } + ) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + fetchMembershipExpect.fulfill() + } + } + } + } + + defer { + waitForCompletion { + client.removeMemberships( + userId: testUser.metadataId, + channels: [membership], + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + + wait(for: [fetchMembershipExpect], timeout: 10.0) + } + + func testUpdateMembership() { + let updateMembershipExpect = expectation(description: "Update Membership Expectation") + let client = PubNub(configuration: config) + + let testUser = PubNubUserMetadataBase( + metadataId: "testUpdateMemberships", + name: "Swift ITest" + ) + let testChannel = PubNubChannelMetadataBase( + metadataId: "testUpdateMembershipsSpace", + name: "Swift Membership ITest" + ) + let membership = PubNubMembershipMetadataBase( + userMetadataId: testUser.metadataId, + channelMetadataId: testChannel.metadataId, + user: testUser, + channel: testChannel + ) + + client.setUserMetadata(testUser) { [unowned client] _ in + client.setChannelMetadata(testChannel) { _ in + client.setMemberships(userId: testUser.metadataId, channels: [membership]) { result in + switch result { + case let .success((memberships, _)): + XCTAssertEqual(memberships.count, 1) + XCTAssertTrue( + memberships.allSatisfy { + $0.channelMetadataId == testChannel.metadataId && $0.userMetadataId == testUser.metadataId + } + ) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + updateMembershipExpect.fulfill() + } + } + } + + defer { + waitForCompletion { + client.removeMemberships( + userId: testUser.metadataId, + channels: [membership], + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + + wait(for: [updateMembershipExpect], timeout: 10.0) + } + + func testRemoveMembership() { + let removeMembershipExpect = expectation(description: "Remove Membership Expectation") + let client = PubNub(configuration: config) + + let testUser = PubNubUserMetadataBase( + metadataId: "testUpdateMemberships", + name: "Swift ITest" + ) + let testChannel = PubNubChannelMetadataBase( + metadataId: "testUpdateMembershipsSpace", + name: "Swift Membership ITest" + ) + let membership = PubNubMembershipMetadataBase( + userMetadataId: testUser.metadataId, + channelMetadataId: testChannel.metadataId, + user: testUser, + channel: testChannel + ) + + client.setUserMetadata(testUser) { [unowned client] _ in + client.setChannelMetadata(testChannel) { _ in + client.removeMemberships(userId: testUser.metadataId, channels: [membership]) { result in + switch result { + case let .success((memberships, _)): + XCTAssertTrue(memberships.isEmpty) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + removeMembershipExpect.fulfill() + } + } + } + + defer { + waitForCompletion { + client.removeMemberships( + userId: testUser.metadataId, + channels: [membership], + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel.metadataId, + completion: $0 + ) + } + } + + wait(for: [removeMembershipExpect], timeout: 10.0) + } + + func testManageMemberships() { + let manageMembershipExpect = expectation(description: "Manage Membership Expectation") + let client = PubNub(configuration: config) + + let testUser = PubNubUserMetadataBase( + metadataId: "testManageMemberships", + name: "Swift ITest" + ) + let testChannel1 = PubNubChannelMetadataBase( + metadataId: "testManageMembershipsSpace1", + name: "Swift Membership ITest 1" + ) + let testChannel2 = PubNubChannelMetadataBase( + metadataId: "testManageMembershipsSpace2", + name: "Swift Membership ITest 2" + ) + let testChannel3 = PubNubChannelMetadataBase( + metadataId: "testManageMembershipsSpace3", + name: "Swift Membership ITest 3" + ) + + let membership1 = PubNubMembershipMetadataBase( + userMetadataId: testUser.metadataId, + channelMetadataId: testChannel1.metadataId, + user: testUser, + channel: testChannel1 + ) + let membership2 = PubNubMembershipMetadataBase( + userMetadataId: testUser.metadataId, + channelMetadataId: testChannel2.metadataId, + user: testUser, + channel: testChannel2 + ) + let membership3 = PubNubMembershipMetadataBase( + userMetadataId: testUser.metadataId, + channelMetadataId: testChannel3.metadataId, + user: testUser, + channel: testChannel3 + ) + + // First set up initial memberships + client.setUserMetadata(testUser) { [unowned client] _ in + client.setChannelMetadata(testChannel1) { _ in + client.setChannelMetadata(testChannel2) { _ in + client.setChannelMetadata(testChannel3) { _ in + client.setMemberships(userId: testUser.metadataId, channels: [membership1, membership2]) { _ in + client.manageMemberships( + userId: testUser.metadataId, + setting: [membership3], + removing: [membership1] + ) { result in + switch result { + case let .success((memberships, _)): + XCTAssertEqual(memberships.count, 2) + XCTAssertTrue(memberships.contains { $0.channelMetadataId == testChannel2.metadataId }) + XCTAssertTrue(memberships.contains { $0.channelMetadataId == testChannel3.metadataId }) + XCTAssertFalse(memberships.contains { $0.channelMetadataId == testChannel1.metadataId }) + case let .failure(error): + XCTFail("Failed due to error: \(error)") + } + manageMembershipExpect.fulfill() + } + } + } + } + } + } + + defer { + waitForCompletion { + client.removeMemberships( + userId: testUser.metadataId, + channels: [membership1, membership2, membership3], + completion: $0 + ) + } + waitForCompletion { + client.removeUserMetadata( + testUser.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel1.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel2.metadataId, + completion: $0 + ) + } + waitForCompletion { + client.removeChannelMetadata( + testChannel3.metadataId, + completion: $0 + ) + } + } + + wait(for: [manageMembershipExpect], timeout: 10.0) + } +} + +private extension UserObjectsEndpointIntegrationTests { + func userStubs() -> [PubNubUserMetadataBase] { + [ + PubNubUserMetadataBase( + metadataId: randomString(), + name: "Test User One", + profileURL: "https://example.com/user1", + custom: ["role": "admin", "department": "engineering"] + ), + PubNubUserMetadataBase( + metadataId: randomString(), + name: "Test User Two", + profileURL: "https://example.com/user2", + custom: ["role": "user", "department": "marketing"] + ), + PubNubUserMetadataBase( + metadataId: randomString(), + name: "Test User Three", + profileURL: "https://example.com/user3", + custom: ["role": "manager", "department": "sales"] + ), + PubNubUserMetadataBase( + metadataId: randomString(), + name: "Test User Four", + profileURL: "https://example.com/user4", + custom: ["role": "developer", "department": "mobile"] + ), + PubNubUserMetadataBase( + metadataId: randomString(), + name: "Test User Five", + profileURL: "https://example.com/user5", + custom: ["role": "designer", "department": "ux"] + ), + PubNubUserMetadataBase( + metadataId: randomString(), + name: "Test User Six", + profileURL: "https://example.com/user6", + custom: ["role": "qa", "department": "testing"] + ) + ] + } + + func setupTestUsers(client: PubNub) -> [PubNubUserMetadata] { + let setupExpect = expectation(description: "Setup Test Users Expectation") + let testUsers = userStubs() + + testUsers.enumerated().forEach { index, user in + client.setUserMetadata(user) { result in + if case let .failure(error) = result { + XCTFail("Failed to setup test user \(user.metadataId): \(error)") + } + if index == testUsers.count - 1 { + setupExpect.fulfill() + } + } + } + + wait( + for: [setupExpect], + timeout: 10.0 + ) + + return testUsers + } +} diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 32ccc6de..192bafd3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -56,6 +56,29 @@ lane :contract_test do ) end +desc "Executes Integration Tests" +lane :integration_test do + pub_key = ENV['SDK_PUB_KEY'] + sub_key = ENV['SDK_SUB_KEY'] + + set_info_plist_value( + path: "PubNub.xcodeproj/PubNubTests_Info.plist", + key: "PubNubPublishKey", + value: pub_key + ) + set_info_plist_value( + path: "PubNub.xcodeproj/PubNubTests_Info.plist", + key: "PubNubSubscribeKey", + value: sub_key + ) + + Dir.chdir("..") do + # Invoking the integration tests using xcodebuild directly to avoid an issue with the scan method, + # which is unable to detect the simulator: + sh "set -o pipefail && env NSUnbufferedIO=YES xcodebuild -workspace PubNub.xcworkspace -scheme PubNubIntegration -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.4' -parallel-testing-enabled NO test 2>&1 | xcpretty" + end +end + desc "Generates Code Coverage Files" lane :code_coverage do scan(