diff --git a/.github/workflows/cron-checks.yml b/.github/workflows/cron-checks.yml index 0bcffa4bbff..1cf41fefdc8 100644 --- a/.github/workflows/cron-checks.yml +++ b/.github/workflows/cron-checks.yml @@ -80,7 +80,7 @@ jobs: name: Test Data E2E (iOS ${{ matrix.ios }}) path: | fastlane/recordings - fastlane/sinatra_log.txt + fastlane/stream-chat-test-mock-server/logs/* fastlane/test_output/logs/*/Diagnostics/**/*.txt fastlane/test_output/logs/*/Diagnostics/simctl_diagnostics/DiagnosticReports/* diff --git a/.github/workflows/smoke-checks.yml b/.github/workflows/smoke-checks.yml index d8fc6d954a2..80a9d481300 100644 --- a/.github/workflows/smoke-checks.yml +++ b/.github/workflows/smoke-checks.yml @@ -222,7 +222,7 @@ jobs: name: Test Data E2E path: | fastlane/recordings - fastlane/sinatra_log.txt + fastlane/stream-chat-test-mock-server/logs/* fastlane/test_output/logs/*/Diagnostics/**/*.txt fastlane/test_output/logs/*/Diagnostics/simctl_diagnostics/DiagnosticReports/* diff --git a/.github/workflows/sync-mock-server.yml b/.github/workflows/sync-mock-server.yml deleted file mode 100644 index 78d7056110d..00000000000 --- a/.github/workflows/sync-mock-server.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Mock Server - -on: - schedule: - # Runs "At 00:00 on day-of-month 1" - - cron: '0 0 1 * *' - - workflow_dispatch: - -env: - HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI - -jobs: - sync: - name: Sync - runs-on: macos-15 - steps: - - uses: actions/checkout@v4.1.1 - - uses: ./.github/actions/bootstrap - - uses: ./.github/actions/ruby-cache - - run: bundle exec fastlane sync_mock_server - timeout-minutes: 5 - env: - GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN }} - - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - text: "You shall not pass!" - job_name: "${{ github.workflow }}: ${{ github.job }}" - fields: repo,commit,author,workflow - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - if: failure() diff --git a/.gitignore b/.gitignore index 3b590688190..b83ec646a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,7 @@ app-thinning.plist *.dmg *.pkg* *LinkMap.txt +stream-chat-test-mock-server # gcloud google-cloud-sdk diff --git a/Gemfile b/Gemfile index 6618b799d5c..3786c7a7900 100644 --- a/Gemfile +++ b/Gemfile @@ -26,7 +26,6 @@ group :sinatra_dependencies do gem 'faye-websocket' gem 'puma' gem 'rackup' - gem 'stream-chat-ruby', '3.0.0' end group :rubocop_dependencies do diff --git a/Gemfile.lock b/Gemfile.lock index 3716e8f7651..a60f5775db2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -126,7 +126,7 @@ GEM drb (2.2.3) emoji_regex (3.2.3) escape (0.0.4) - ethon (0.16.0) + ethon (0.15.0) ffi (>= 1.15.0) eventmachine (1.2.7) excon (0.112.0) @@ -219,7 +219,7 @@ GEM faye-websocket (0.12.0) eventmachine (>= 0.12.0) websocket-driver (>= 0.8.0) - ffi (1.17.2) + ffi (1.17.2-arm64-darwin) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) @@ -295,10 +295,8 @@ GEM nanaimo (0.4.0) nap (1.1.0) naturally (2.3.0) - net-http-persistent (4.0.6) - connection_pool (~> 2.2, >= 2.2.4) netrc (0.11.0) - nio4r (2.7.4) + nio4r (2.7.5) nkf (0.2.0) nokogiri (1.18.9) mini_portile2 (~> 2.8.2) @@ -321,18 +319,18 @@ GEM method_source (~> 1.0) pstore (0.2.0) public_suffix (4.0.7) - puma (6.6.1) + puma (7.1.0) nio4r (~> 2.0) racc (1.8.1) - rack (3.2.3) - rack-protection (4.2.0) + rack (3.2.4) + rack-protection (4.2.1) base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) rack-session (2.1.1) base64 (>= 0.1.0) rack (>= 3.0.0) - rackup (2.2.1) + rackup (2.3.1) rack (>= 3) rainbow (3.1.1) rake (13.3.0) @@ -380,11 +378,11 @@ GEM simctl (1.6.10) CFPropertyList naturally - sinatra (4.2.0) + sinatra (4.2.1) logger (>= 1.6.0) mustermann (~> 3.0) rack (>= 3.0.0, < 4) - rack-protection (= 4.2.0) + rack-protection (= 4.2.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) slather (2.8.5) @@ -393,14 +391,6 @@ GEM clamp (~> 1.3) nokogiri (>= 1.14.3) xcodeproj (~> 1.27) - sorbet-runtime (0.5.12368) - stream-chat-ruby (3.0.0) - faraday - faraday-multipart - faraday-net_http_persistent - jwt - net-http-persistent - sorbet-runtime sysrandom (1.0.5) terminal-notifier (2.0.0) terminal-table (3.0.2) @@ -411,8 +401,8 @@ GEM tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) - typhoeus (1.4.1) - ethon (>= 0.9.0) + typhoeus (1.5.0) + ethon (>= 0.9.0, < 0.16.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) @@ -463,7 +453,6 @@ DEPENDENCIES rubocop-require_tools sinatra slather - stream-chat-ruby (= 3.0.0) xctest_list BUNDLED WITH diff --git a/README.md b/README.md index e965d3cc9be..e2468735fc7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@

-

diff --git a/StreamChat.xcodeproj/project.pbxproj b/StreamChat.xcodeproj/project.pbxproj index ae6e1136d0a..6e80a485054 100644 --- a/StreamChat.xcodeproj/project.pbxproj +++ b/StreamChat.xcodeproj/project.pbxproj @@ -582,15 +582,12 @@ 82120C312B6AB3B400347A35 /* StreamChatUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 790881FD25432B7200896F03 /* StreamChatUI.framework */; }; 82120C342B6AB41100347A35 /* StreamChat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 799C941B247D2F80001F1104 /* StreamChat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 82120C352B6AB41100347A35 /* StreamChatUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 790881FD25432B7200896F03 /* StreamChatUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 8223EE7A2937A099006138B9 /* http_add_member.json in Resources */ = {isa = PBXBuildFile; fileRef = 8223EE792937A099006138B9 /* http_add_member.json */; }; - 8223EE7C2937A0E9006138B9 /* http_channel_creation.json in Resources */ = {isa = PBXBuildFile; fileRef = 8223EE7B2937A0E9006138B9 /* http_channel_creation.json */; }; - 8223EE7E2937A1F5006138B9 /* http_channel_removal.json in Resources */ = {isa = PBXBuildFile; fileRef = 8223EE7D2937A1F5006138B9 /* http_channel_removal.json */; }; - 8223EE802937A21E006138B9 /* ws_message.json in Resources */ = {isa = PBXBuildFile; fileRef = 8223EE7F2937A21E006138B9 /* ws_message.json */; }; + 822D36202EF5EDAC00AC3B37 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 822D361F2EF5EDA800AC3B37 /* DataTypes.swift */; }; + 822D36222EF5EDE600AC3B37 /* LaunchArgument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 822D36212EF5EDE400AC3B37 /* LaunchArgument.swift */; }; 822F265727D8F75D00E454FB /* UserRobot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8298C8E827D22C3E004082D3 /* UserRobot.swift */; }; 822F266027D9FDB500E454FB /* URLProtocol_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79DDF816249CE38B002F4412 /* URLProtocol_Mock.swift */; }; 822F266227D9FE5E00E454FB /* RequestRecorderURLProtocol_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79DDF817249CE38B002F4412 /* RequestRecorderURLProtocol_Mock.swift */; }; 8232B84F28635C4A0032C7DB /* Attachments_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8232B84E28635C4A0032C7DB /* Attachments_Tests.swift */; }; - 82390E7B28C609A700829581 /* push_notification.json in Resources */ = {isa = PBXBuildFile; fileRef = 82390E7A28C609A700829581 /* push_notification.json */; }; 823A1ADA28C74C1400F7CADA /* SpringBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 823A1AD928C74C1400F7CADA /* SpringBoard.swift */; }; 823F5B1A2A8D0294000C3081 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 823F5B192A8D0294000C3081 /* PrivacyInfo.xcprivacy */; }; 823F5B1B2A8D0294000C3081 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 823F5B192A8D0294000C3081 /* PrivacyInfo.xcprivacy */; }; @@ -598,38 +595,10 @@ 825A32CD27DBB46F000402A9 /* ChannelListPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825A32CC27DBB46F000402A9 /* ChannelListPage.swift */; }; 825A32CF27DBB48D000402A9 /* StartPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825A32CE27DBB48D000402A9 /* StartPage.swift */; }; 8263464C2B0BACF600122D0E /* Difference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8263464B2B0BACF600122D0E /* Difference.swift */; }; - 826346622B0BAE3800122D0E /* Socket+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8263464E2B0BAE3600122D0E /* Socket+File.swift */; }; - 826346632B0BAE3800122D0E /* String+File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8263464F2B0BAE3600122D0E /* String+File.swift */; }; - 826346642B0BAE3800122D0E /* HttpParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346502B0BAE3600122D0E /* HttpParser.swift */; }; - 826346652B0BAE3800122D0E /* Socket+Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346512B0BAE3600122D0E /* Socket+Server.swift */; }; - 826346662B0BAE3800122D0E /* String+Misc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346522B0BAE3600122D0E /* String+Misc.swift */; }; - 826346672B0BAE3800122D0E /* Scopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346532B0BAE3600122D0E /* Scopes.swift */; }; - 826346682B0BAE3800122D0E /* String+SHA1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346542B0BAE3600122D0E /* String+SHA1.swift */; }; - 826346692B0BAE3800122D0E /* String+BASE64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346552B0BAE3600122D0E /* String+BASE64.swift */; }; - 8263466A2B0BAE3800122D0E /* MimeTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346562B0BAE3700122D0E /* MimeTypes.swift */; }; - 8263466B2B0BAE3800122D0E /* Files.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346572B0BAE3700122D0E /* Files.swift */; }; - 8263466C2B0BAE3800122D0E /* WebSockets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346582B0BAE3700122D0E /* WebSockets.swift */; }; - 8263466D2B0BAE3800122D0E /* HttpRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346592B0BAE3700122D0E /* HttpRouter.swift */; }; - 8263466E2B0BAE3800122D0E /* DemoServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8263465A2B0BAE3700122D0E /* DemoServer.swift */; }; - 8263466F2B0BAE3800122D0E /* HttpServerIO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8263465B2B0BAE3700122D0E /* HttpServerIO.swift */; }; - 826346702B0BAE3800122D0E /* HttpResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8263465C2B0BAE3700122D0E /* HttpResponse.swift */; }; - 826346712B0BAE3800122D0E /* HttpRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8263465D2B0BAE3700122D0E /* HttpRequest.swift */; }; - 826346722B0BAE3800122D0E /* Process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8263465E2B0BAE3700122D0E /* Process.swift */; }; - 826346732B0BAE3800122D0E /* HttpServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8263465F2B0BAE3800122D0E /* HttpServer.swift */; }; - 826346742B0BAE3800122D0E /* Errno.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346602B0BAE3800122D0E /* Errno.swift */; }; - 826346752B0BAE3800122D0E /* Socket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826346612B0BAE3800122D0E /* Socket.swift */; }; - 826992C82900628500D2D470 /* DeviceRemoteControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826992C72900628500D2D470 /* DeviceRemoteControl.swift */; }; - 826B1C3528895AFD005DDF13 /* http_youtube_link.json in Resources */ = {isa = PBXBuildFile; fileRef = 826B1C3428895AFD005DDF13 /* http_youtube_link.json */; }; - 826B1C3728895BB5005DDF13 /* http_unsplash_link.json in Resources */ = {isa = PBXBuildFile; fileRef = 826B1C3628895BB5005DDF13 /* http_unsplash_link.json */; }; - 826B1C39288FD756005DDF13 /* http_truncate.json in Resources */ = {isa = PBXBuildFile; fileRef = 826B1C38288FD756005DDF13 /* http_truncate.json */; }; 826EF2B1291C01C1005A9EEF /* Authentication_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 826EF2B0291C01C1005A9EEF /* Authentication_Tests.swift */; }; 827414272ACDE941009CD13C /* StreamChatTestMockServer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A3A0C999283E952900B18DA4 /* StreamChatTestMockServer.framework */; }; - 827414412ACDF6C2009CD13C /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 827414402ACDF6C2009CD13C /* String.swift */; }; - 827414432ACDF76C009CD13C /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 827414422ACDF76C009CD13C /* Dictionary.swift */; }; - 8274181F2ACDE85E004A23DA /* StreamSwiftTestHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = 8274181E2ACDE85E004A23DA /* StreamSwiftTestHelpers */; }; 827418212ACDE86F004A23DA /* StreamSwiftTestHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = 827418202ACDE86F004A23DA /* StreamSwiftTestHelpers */; }; 8274A7962B7FAC3900D8696B /* ChannelListScrollTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8274A7952B7FAC3900D8696B /* ChannelListScrollTime.swift */; }; - 8279706F29689680006741A3 /* UserDetails_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8279706E29689680006741A3 /* UserDetails_Tests.swift */; }; 827DD1A0289D5B3300910AC5 /* MessageActionsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 827DD19F289D5B3300910AC5 /* MessageActionsVC.swift */; }; 82865DA42EC4B87B007D7053 /* Backend_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82865DA32EC4B874007D7053 /* Backend_Tests.swift */; }; 8292D6DB29B78476007A17D1 /* QuotedReply_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8292D6DA29B78476007A17D1 /* QuotedReply_Tests.swift */; }; @@ -643,13 +612,11 @@ 82BE0ACE2C009A17008DA9DC /* BlockedUserDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82BE0ACC2C009A17008DA9DC /* BlockedUserDetails.swift */; }; 82C18FDC2C10C8E600C5283C /* BlockedUserPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82C18FDB2C10C8E600C5283C /* BlockedUserPayload.swift */; }; 82C18FDD2C10C8E600C5283C /* BlockedUserPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82C18FDB2C10C8E600C5283C /* BlockedUserPayload.swift */; }; - 82CBE5682861BF300039C35C /* AttachmentResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CBE5672861BF300039C35C /* AttachmentResponses.swift */; }; 82CED1C827DF492F006E967A /* ThreadPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82CED1C727DF492F006E967A /* ThreadPage.swift */; }; 82DCB3A92A4AE8FB00738933 /* StreamChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 799C941B247D2F80001F1104 /* StreamChat.framework */; }; 82DCB3AA2A4AE8FB00738933 /* StreamChat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 799C941B247D2F80001F1104 /* StreamChat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 82DCB3AD2A4AE8FB00738933 /* StreamChatUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 790881FD25432B7200896F03 /* StreamChatUI.framework */; }; 82DCB3AE2A4AE8FB00738933 /* StreamChatUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 790881FD25432B7200896F03 /* StreamChatUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 82E43AC128637651007BB6CD /* http_attachment.json in Resources */ = {isa = PBXBuildFile; fileRef = 82E43AC028637651007BB6CD /* http_attachment.json */; }; 82E655332B06748400D64906 /* Spy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E655322B06748400D64906 /* Spy.swift */; }; 82E655352B06751D00D64906 /* QueueAwareDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E655342B06751D00D64906 /* QueueAwareDelegate.swift */; }; 82E655372B06756A00D64906 /* AssertTestQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E655362B06756A00D64906 /* AssertTestQueue.swift */; }; @@ -985,7 +952,6 @@ 8AE335A824FCF999002B6677 /* Reachability_Vendor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE335A524FCF999002B6677 /* Reachability_Vendor.swift */; }; 8AE335A924FCF999002B6677 /* InternetConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE335A624FCF999002B6677 /* InternetConnection.swift */; }; 8AE335AA24FCF99E002B6677 /* InternetConnection_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE335A424FCF999002B6677 /* InternetConnection_Tests.swift */; }; - 9041E4AD2AE9768800CA2A2A /* MembersResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9041E4AC2AE9768800CA2A2A /* MembersResponse.swift */; }; A30C3F20276B428F00DA5968 /* UnknownUserEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A30C3F1F276B428F00DA5968 /* UnknownUserEvent.swift */; }; A30C3F22276B4F8800DA5968 /* UnknownUserEvent_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A30C3F21276B4F8800DA5968 /* UnknownUserEvent_Tests.swift */; }; A311B3CE27E8B98C00CFCF6D /* CurrentUserCustomRole.json in Resources */ = {isa = PBXBuildFile; fileRef = 43ABF8B626C513D20034BD62 /* CurrentUserCustomRole.json */; }; @@ -1163,7 +1129,6 @@ A35715F4283E98110014E3B0 /* StreamChatTestMockServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3A0C9C0283E967600B18DA4 /* StreamChatTestMockServer.swift */; }; A35715FB283E9A080014E3B0 /* StreamChatTestMockServer.h in Headers */ = {isa = PBXBuildFile; fileRef = A35715FA283E9A080014E3B0 /* StreamChatTestMockServer.h */; settings = {ATTRIBUTES = (Public, ); }; }; A35757C72613081B00DC914C /* ComposerKeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35757C62613081B00DC914C /* ComposerKeyboardHandler.swift */; }; - A3600B3C283F639700E1C930 /* StreamTestCase+Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3600B3B283F639700E1C930 /* StreamTestCase+Tags.swift */; }; A368E71627F33E16009063C1 /* MissingEventsPayload-IncompleteChannel.json in Resources */ = {isa = PBXBuildFile; fileRef = C1CE8EFD27F20C3A0091097B /* MissingEventsPayload-IncompleteChannel.json */; }; A3698D802820187200814143 /* DebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3698D7F2820187200814143 /* DebugMenu.swift */; }; A3698DD828215E2F00814143 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3698DD728215E2F00814143 /* Settings.swift */; }; @@ -1178,24 +1143,11 @@ A3960E0D27DA5973003AB2B0 /* ConnectionRecoveryHandler_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3960E0C27DA5973003AB2B0 /* ConnectionRecoveryHandler_Tests.swift */; }; A39A8AE7263825F4003453D9 /* ChatMessageLayoutOptionsResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A39A8AE6263825F4003453D9 /* ChatMessageLayoutOptionsResolver.swift */; }; A39B040B27F196F200D6B18A /* StreamChatUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A39B040A27F196F200D6B18A /* StreamChatUITests.swift */; }; - A3A0C9A1283E955200B18DA4 /* ChannelResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824445AA27EA364300DB2FD8 /* ChannelResponses.swift */; }; - A3A0C9A3283E955200B18DA4 /* Swifter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8224FD85280EC09800B32D43 /* Swifter.swift */; }; - A3A0C9A5283E955200B18DA4 /* TestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825A32AD27DA686C000402A9 /* TestData.swift */; }; - A3A0C9A6283E955200B18DA4 /* WebsocketResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E4CBDB27F240C60013B02D /* WebsocketResponses.swift */; }; - A3A0C9A9283E955200B18DA4 /* MessageResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B350FC27EA2A3900FEB6A0 /* MessageResponses.swift */; }; - A3A0C9AA283E955200B18DA4 /* ChannelConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3813B49282595960076E838 /* ChannelConfig.swift */; }; - A3A0C9AD283E955200B18DA4 /* MessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E4CBDD27F30E7D0013B02D /* MessageList.swift */; }; A3A0C9B0283E955200B18DA4 /* StreamMockServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82D2E81E27D10F4300169ADA /* StreamMockServer.swift */; }; - A3A0C9B1283E955200B18DA4 /* LaunchArgument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82AD02BA27D8E433000611B7 /* LaunchArgument.swift */; }; - A3A0C9B2283E955200B18DA4 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82E4CBDF27F322EA0013B02D /* User.swift */; }; - A3A0C9B3283E955200B18DA4 /* EventResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B3510027EA2B1400FEB6A0 /* EventResponses.swift */; }; - A3A0C9B4283E955200B18DA4 /* MockServerAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B350F827EA294900FEB6A0 /* MockServerAttributes.swift */; }; - A3A0C9B5283E955200B18DA4 /* ReactionResponses.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82B350FA27EA298800FEB6A0 /* ReactionResponses.swift */; }; A3A52B6627EB61FC00311DFC /* EventPayload_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 794927F0249E3DE6009D7EB7 /* EventPayload_Tests.swift */; }; A3A644B327BF99D400F92494 /* ChannelTruncateRequestPayload_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3A644B227BF99D400F92494 /* ChannelTruncateRequestPayload_Tests.swift */; }; A3AFEAA72816F1A200A79A6A /* MessageDeliveryStatus_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3AFEAA62816F1A200A79A6A /* MessageDeliveryStatus_Tests.swift */; }; A3B0CFA227BBF52600F352F9 /* ChannelTruncateRequestPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B0CFA127BBF52600F352F9 /* ChannelTruncateRequestPayload.swift */; }; - A3B68B07284503AD00DF4321 /* http_message_ephemeral.json in Resources */ = {isa = PBXBuildFile; fileRef = A3B68B06284503AC00DF4321 /* http_message_ephemeral.json */; }; A3B78F18282A675700348AD1 /* MessageDeliveryStatus+ChannelList_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B78F17282A675700348AD1 /* MessageDeliveryStatus+ChannelList_Tests.swift */; }; A3B78F1A282A6A8F00348AD1 /* UserRobot+Asserts.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3B78F19282A6A8F00348AD1 /* UserRobot+Asserts.swift */; }; A3BB3FFF261DA74D00365496 /* ContainerStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BB3FFE261DA74D00365496 /* ContainerStackView.swift */; }; @@ -1204,16 +1156,6 @@ A3BD4850281AC16C0090D511 /* ChannelVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BD484F281AC16C0090D511 /* ChannelVC.swift */; }; A3BD486B281FD4500090D511 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = A3BD486A281FD4500090D511 /* OHHTTPStubs */; }; A3BEB6AF27F3235600D6D80D /* Bundle+Target.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BEB6AE27F3235600D6D80D /* Bundle+Target.swift */; }; - A3C049D52840BDA700E25E38 /* http_events.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049C92840BDA700E25E38 /* http_events.json */; }; - A3C049D62840BDA700E25E38 /* http_reaction.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049CA2840BDA700E25E38 /* http_reaction.json */; }; - A3C049D72840BDA700E25E38 /* ws_events_member.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049CB2840BDA700E25E38 /* ws_events_member.json */; }; - A3C049D82840BDA700E25E38 /* http_message.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049CC2840BDA700E25E38 /* http_message.json */; }; - A3C049D92840BDA700E25E38 /* http_member.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049CD2840BDA700E25E38 /* http_member.json */; }; - A3C049DA2840BDA700E25E38 /* ws_reaction.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049CE2840BDA700E25E38 /* ws_reaction.json */; }; - A3C049DB2840BDA700E25E38 /* ws_health_check.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049CF2840BDA700E25E38 /* ws_health_check.json */; }; - A3C049DD2840BDA700E25E38 /* ws_events.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049D12840BDA700E25E38 /* ws_events.json */; }; - A3C049DE2840BDA700E25E38 /* ws_events_channel.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049D22840BDA700E25E38 /* ws_events_channel.json */; }; - A3C049DF2840BDA700E25E38 /* http_channels.json in Resources */ = {isa = PBXBuildFile; fileRef = A3C049D32840BDA700E25E38 /* http_channels.json */; }; A3C0D774261CA25700A8A1A2 /* ContainerStackView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C0D773261CA25700A8A1A2 /* ContainerStackView_Tests.swift */; }; A3C2700427E1DB2B0057D5A8 /* MessageSearchController_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C2700127E1D82D0057D5A8 /* MessageSearchController_Tests.swift */; }; A3C3BC1927E87EFE00224761 /* ConnectionRepository_Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A344074E27D753530044F150 /* ConnectionRepository_Mock.swift */; }; @@ -3648,52 +3590,19 @@ 79FA4A83263BFD1100EC33DA /* GalleryAttachmentViewInjector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryAttachmentViewInjector.swift; sourceTree = ""; }; 79FC85E624ACCBC500A665ED /* Token.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; 8210AA2727FC916B005F0B32 /* ChannelList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelList.swift; sourceTree = ""; }; - 8223EE792937A099006138B9 /* http_add_member.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = http_add_member.json; sourceTree = ""; }; - 8223EE7B2937A0E9006138B9 /* http_channel_creation.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = http_channel_creation.json; sourceTree = ""; }; - 8223EE7D2937A1F5006138B9 /* http_channel_removal.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = http_channel_removal.json; sourceTree = ""; }; - 8223EE7F2937A21E006138B9 /* ws_message.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = ws_message.json; sourceTree = ""; }; - 8224FD85280EC09800B32D43 /* Swifter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Swifter.swift; sourceTree = ""; }; + 822D361F2EF5EDA800AC3B37 /* DataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypes.swift; sourceTree = ""; }; + 822D36212EF5EDE400AC3B37 /* LaunchArgument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchArgument.swift; sourceTree = ""; }; 8232B84E28635C4A0032C7DB /* Attachments_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachments_Tests.swift; sourceTree = ""; }; - 82390E7A28C609A700829581 /* push_notification.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = push_notification.json; sourceTree = ""; }; 823A1AD928C74C1400F7CADA /* SpringBoard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpringBoard.swift; sourceTree = ""; }; 823F5B192A8D0294000C3081 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - 824445AA27EA364300DB2FD8 /* ChannelResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelResponses.swift; sourceTree = ""; }; 82472AA528C2395F004A4ACD /* StreamChatUITestsApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StreamChatUITestsApp.entitlements; sourceTree = ""; }; - 825A32AD27DA686C000402A9 /* TestData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestData.swift; sourceTree = ""; }; 825A32CA27DBB463000402A9 /* MessageListPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListPage.swift; sourceTree = ""; }; 825A32CC27DBB46F000402A9 /* ChannelListPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListPage.swift; sourceTree = ""; }; 825A32CE27DBB48D000402A9 /* StartPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartPage.swift; sourceTree = ""; }; 8261340927F20B7A0034AC37 /* StreamChatUITestsApp.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = StreamChatUITestsApp.xctestplan; sourceTree = ""; }; 8263464B2B0BACF600122D0E /* Difference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Difference.swift; sourceTree = ""; }; - 8263464E2B0BAE3600122D0E /* Socket+File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Socket+File.swift"; sourceTree = ""; }; - 8263464F2B0BAE3600122D0E /* String+File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+File.swift"; sourceTree = ""; }; - 826346502B0BAE3600122D0E /* HttpParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpParser.swift; sourceTree = ""; }; - 826346512B0BAE3600122D0E /* Socket+Server.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Socket+Server.swift"; sourceTree = ""; }; - 826346522B0BAE3600122D0E /* String+Misc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Misc.swift"; sourceTree = ""; }; - 826346532B0BAE3600122D0E /* Scopes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scopes.swift; sourceTree = ""; }; - 826346542B0BAE3600122D0E /* String+SHA1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SHA1.swift"; sourceTree = ""; }; - 826346552B0BAE3600122D0E /* String+BASE64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+BASE64.swift"; sourceTree = ""; }; - 826346562B0BAE3700122D0E /* MimeTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MimeTypes.swift; sourceTree = ""; }; - 826346572B0BAE3700122D0E /* Files.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Files.swift; sourceTree = ""; }; - 826346582B0BAE3700122D0E /* WebSockets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebSockets.swift; sourceTree = ""; }; - 826346592B0BAE3700122D0E /* HttpRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRouter.swift; sourceTree = ""; }; - 8263465A2B0BAE3700122D0E /* DemoServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DemoServer.swift; sourceTree = ""; }; - 8263465B2B0BAE3700122D0E /* HttpServerIO.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServerIO.swift; sourceTree = ""; }; - 8263465C2B0BAE3700122D0E /* HttpResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpResponse.swift; sourceTree = ""; }; - 8263465D2B0BAE3700122D0E /* HttpRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpRequest.swift; sourceTree = ""; }; - 8263465E2B0BAE3700122D0E /* Process.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Process.swift; sourceTree = ""; }; - 8263465F2B0BAE3800122D0E /* HttpServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpServer.swift; sourceTree = ""; }; - 826346602B0BAE3800122D0E /* Errno.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errno.swift; sourceTree = ""; }; - 826346612B0BAE3800122D0E /* Socket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socket.swift; sourceTree = ""; }; - 826992C72900628500D2D470 /* DeviceRemoteControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRemoteControl.swift; sourceTree = ""; }; - 826B1C3428895AFD005DDF13 /* http_youtube_link.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = http_youtube_link.json; sourceTree = ""; }; - 826B1C3628895BB5005DDF13 /* http_unsplash_link.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = http_unsplash_link.json; sourceTree = ""; }; - 826B1C38288FD756005DDF13 /* http_truncate.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = http_truncate.json; sourceTree = ""; }; 826EF2B0291C01C1005A9EEF /* Authentication_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authentication_Tests.swift; sourceTree = ""; }; - 827414402ACDF6C2009CD13C /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; - 827414422ACDF76C009CD13C /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; 8274A7952B7FAC3900D8696B /* ChannelListScrollTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListScrollTime.swift; sourceTree = ""; }; - 8279706E29689680006741A3 /* UserDetails_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetails_Tests.swift; sourceTree = ""; }; 827DD19F289D5B3300910AC5 /* MessageActionsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActionsVC.swift; sourceTree = ""; }; 82865DA12EC4B84F007D7053 /* Backend.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Backend.xctestplan; path = StreamChatUITestsAppUITests/Backend.xctestplan; sourceTree = ""; }; 82865DA32EC4B874007D7053 /* Backend_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backend_Tests.swift; sourceTree = ""; }; @@ -3705,22 +3614,12 @@ 829CD5CB2848C8D6003C3877 /* BackendRobot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackendRobot.swift; sourceTree = ""; }; 82A6F5BF27E2031000F4A2F6 /* Reactions_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reactions_Tests.swift; sourceTree = ""; }; 82AA16CF28A400F8009816CD /* StreamChatFlakyTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = StreamChatFlakyTests.xctestplan; sourceTree = ""; }; - 82AD02BA27D8E433000611B7 /* LaunchArgument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchArgument.swift; sourceTree = ""; }; 82AD02BC27D8E44B000611B7 /* StreamTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamTestCase.swift; sourceTree = ""; }; - 82B350F827EA294900FEB6A0 /* MockServerAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockServerAttributes.swift; sourceTree = ""; }; - 82B350FA27EA298800FEB6A0 /* ReactionResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionResponses.swift; sourceTree = ""; }; - 82B350FC27EA2A3900FEB6A0 /* MessageResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageResponses.swift; sourceTree = ""; }; - 82B3510027EA2B1400FEB6A0 /* EventResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventResponses.swift; sourceTree = ""; }; 82BA52EE27E1EF7B00951B87 /* MessageList_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageList_Tests.swift; sourceTree = ""; }; 82BE0ACC2C009A17008DA9DC /* BlockedUserDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUserDetails.swift; sourceTree = ""; }; 82C18FDB2C10C8E600C5283C /* BlockedUserPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUserPayload.swift; sourceTree = ""; }; - 82CBE5672861BF300039C35C /* AttachmentResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentResponses.swift; sourceTree = ""; }; 82CED1C727DF492F006E967A /* ThreadPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPage.swift; sourceTree = ""; }; 82D2E81E27D10F4300169ADA /* StreamMockServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamMockServer.swift; sourceTree = ""; }; - 82E43AC028637651007BB6CD /* http_attachment.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = http_attachment.json; sourceTree = ""; }; - 82E4CBDB27F240C60013B02D /* WebsocketResponses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsocketResponses.swift; sourceTree = ""; }; - 82E4CBDD27F30E7D0013B02D /* MessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageList.swift; sourceTree = ""; }; - 82E4CBDF27F322EA0013B02D /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 82E655322B06748400D64906 /* Spy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Spy.swift; sourceTree = ""; }; 82E655342B06751D00D64906 /* QueueAwareDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueAwareDelegate.swift; sourceTree = ""; }; 82E655362B06756A00D64906 /* AssertTestQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssertTestQueue.swift; sourceTree = ""; }; @@ -4073,7 +3972,6 @@ 8AE335A424FCF999002B6677 /* InternetConnection_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternetConnection_Tests.swift; sourceTree = ""; }; 8AE335A524FCF999002B6677 /* Reachability_Vendor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability_Vendor.swift; sourceTree = ""; }; 8AE335A624FCF999002B6677 /* InternetConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InternetConnection.swift; sourceTree = ""; }; - 9041E4AC2AE9768800CA2A2A /* MembersResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MembersResponse.swift; sourceTree = ""; }; A30C3F1F276B428F00DA5968 /* UnknownUserEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnknownUserEvent.swift; sourceTree = ""; }; A30C3F21276B4F8800DA5968 /* UnknownUserEvent_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnknownUserEvent_Tests.swift; sourceTree = ""; }; A30DEC98260B47DE0066E8CE /* TitleContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleContainerView.swift; sourceTree = ""; }; @@ -4150,13 +4048,11 @@ A34ECB5527F5CC1E00A804C1 /* EntityChange_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityChange_Tests.swift; sourceTree = ""; }; A35715FA283E9A080014E3B0 /* StreamChatTestMockServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StreamChatTestMockServer.h; sourceTree = ""; }; A35757C62613081B00DC914C /* ComposerKeyboardHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComposerKeyboardHandler.swift; sourceTree = ""; }; - A3600B3B283F639700E1C930 /* StreamTestCase+Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StreamTestCase+Tags.swift"; sourceTree = ""; }; A3698D7F2820187200814143 /* DebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugMenu.swift; sourceTree = ""; }; A3698DD728215E2F00814143 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; A36C39F42860680A0004EB7E /* URL+EnrichedURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+EnrichedURL.swift"; sourceTree = ""; }; A36C39F728606B5D0004EB7E /* URL_EnrichedURL_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL_EnrichedURL_Tests.swift; sourceTree = ""; }; A36F99792818459C0078260D /* InternetConnectionMonitor_Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetConnectionMonitor_Mock.swift; sourceTree = ""; }; - A3813B49282595960076E838 /* ChannelConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelConfig.swift; sourceTree = ""; }; A3813B4B2825C8030076E838 /* CustomChatMessageListRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomChatMessageListRouter.swift; sourceTree = ""; }; A3813B4D2825C8A30076E838 /* ThreadVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadVC.swift; sourceTree = ""; }; A382131D2805C8AC0068D30E /* TestsEnvironmentSetup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestsEnvironmentSetup.swift; sourceTree = ""; }; @@ -4171,7 +4067,6 @@ A3AFEAA62816F1A200A79A6A /* MessageDeliveryStatus_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDeliveryStatus_Tests.swift; sourceTree = ""; }; A3B0CFA127BBF52600F352F9 /* ChannelTruncateRequestPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelTruncateRequestPayload.swift; sourceTree = ""; }; A3B0CFA327BCF66A00F352F9 /* ChannelTruncated_with_message.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ChannelTruncated_with_message.json; sourceTree = ""; }; - A3B68B06284503AC00DF4321 /* http_message_ephemeral.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = http_message_ephemeral.json; sourceTree = ""; }; A3B78F17282A675700348AD1 /* MessageDeliveryStatus+ChannelList_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageDeliveryStatus+ChannelList_Tests.swift"; sourceTree = ""; }; A3B78F19282A6A8F00348AD1 /* UserRobot+Asserts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserRobot+Asserts.swift"; sourceTree = ""; }; A3BB3FFE261DA74D00365496 /* ContainerStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerStackView.swift; sourceTree = ""; }; @@ -4180,16 +4075,6 @@ A3BD484F281AC16C0090D511 /* ChannelVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelVC.swift; sourceTree = ""; }; A3BEB6AE27F3235600D6D80D /* Bundle+Target.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Target.swift"; sourceTree = ""; }; A3BEB6B227F3245E00D6D80D /* XCTestCase+MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+MockData.swift"; sourceTree = ""; }; - A3C049C92840BDA700E25E38 /* http_events.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = http_events.json; sourceTree = ""; }; - A3C049CA2840BDA700E25E38 /* http_reaction.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = http_reaction.json; sourceTree = ""; }; - A3C049CB2840BDA700E25E38 /* ws_events_member.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ws_events_member.json; sourceTree = ""; }; - A3C049CC2840BDA700E25E38 /* http_message.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = http_message.json; sourceTree = ""; }; - A3C049CD2840BDA700E25E38 /* http_member.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = http_member.json; sourceTree = ""; }; - A3C049CE2840BDA700E25E38 /* ws_reaction.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ws_reaction.json; sourceTree = ""; }; - A3C049CF2840BDA700E25E38 /* ws_health_check.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ws_health_check.json; sourceTree = ""; }; - A3C049D12840BDA700E25E38 /* ws_events.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ws_events.json; sourceTree = ""; }; - A3C049D22840BDA700E25E38 /* ws_events_channel.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ws_events_channel.json; sourceTree = ""; }; - A3C049D32840BDA700E25E38 /* http_channels.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = http_channels.json; sourceTree = ""; }; A3C0D773261CA25700A8A1A2 /* ContainerStackView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerStackView_Tests.swift; sourceTree = ""; }; A3C2700127E1D82D0057D5A8 /* MessageSearchController_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageSearchController_Tests.swift; sourceTree = ""; }; A3C3BC4B27E87FEC00224761 /* Token+Unique.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Token+Unique.swift"; sourceTree = ""; }; @@ -6292,33 +6177,6 @@ path = Difference; sourceTree = ""; }; - 8263464D2B0BADFF00122D0E /* Swifter */ = { - isa = PBXGroup; - children = ( - 8263465A2B0BAE3700122D0E /* DemoServer.swift */, - 826346602B0BAE3800122D0E /* Errno.swift */, - 826346572B0BAE3700122D0E /* Files.swift */, - 826346502B0BAE3600122D0E /* HttpParser.swift */, - 8263465D2B0BAE3700122D0E /* HttpRequest.swift */, - 8263465C2B0BAE3700122D0E /* HttpResponse.swift */, - 826346592B0BAE3700122D0E /* HttpRouter.swift */, - 8263465F2B0BAE3800122D0E /* HttpServer.swift */, - 8263465B2B0BAE3700122D0E /* HttpServerIO.swift */, - 826346562B0BAE3700122D0E /* MimeTypes.swift */, - 8263465E2B0BAE3700122D0E /* Process.swift */, - 826346532B0BAE3600122D0E /* Scopes.swift */, - 826346612B0BAE3800122D0E /* Socket.swift */, - 8263464E2B0BAE3600122D0E /* Socket+File.swift */, - 826346512B0BAE3600122D0E /* Socket+Server.swift */, - 826346552B0BAE3600122D0E /* String+BASE64.swift */, - 8263464F2B0BAE3600122D0E /* String+File.swift */, - 826346522B0BAE3600122D0E /* String+Misc.swift */, - 826346542B0BAE3600122D0E /* String+SHA1.swift */, - 826346582B0BAE3700122D0E /* WebSockets.swift */, - ); - path = Swifter; - sourceTree = ""; - }; 82865DA22EC4B86A007D7053 /* Backend */ = { isa = PBXGroup; children = ( @@ -6339,32 +6197,13 @@ 82AD02B427D8E3C0000611B7 /* MockServer */ = { isa = PBXGroup; children = ( - A3813B49282595960076E838 /* ChannelConfig.swift */, - 824445AA27EA364300DB2FD8 /* ChannelResponses.swift */, - 82B3510027EA2B1400FEB6A0 /* EventResponses.swift */, - 82E4CBDD27F30E7D0013B02D /* MessageList.swift */, - 82B350FC27EA2A3900FEB6A0 /* MessageResponses.swift */, - 82CBE5672861BF300039C35C /* AttachmentResponses.swift */, - 82B350F827EA294900FEB6A0 /* MockServerAttributes.swift */, - 82B350FA27EA298800FEB6A0 /* ReactionResponses.swift */, + 822D36212EF5EDE400AC3B37 /* LaunchArgument.swift */, + 822D361F2EF5EDA800AC3B37 /* DataTypes.swift */, 82D2E81E27D10F4300169ADA /* StreamMockServer.swift */, - 82E4CBDF27F322EA0013B02D /* User.swift */, - 82E4CBDB27F240C60013B02D /* WebsocketResponses.swift */, - 826992C72900628500D2D470 /* DeviceRemoteControl.swift */, - 9041E4AC2AE9768800CA2A2A /* MembersResponse.swift */, ); path = MockServer; sourceTree = ""; }; - 82AD02B527D8E3C7000611B7 /* Utilities */ = { - isa = PBXGroup; - children = ( - 82AD02BA27D8E433000611B7 /* LaunchArgument.swift */, - 825A32AD27DA686C000402A9 /* TestData.swift */, - ); - path = Utilities; - sourceTree = ""; - }; 82AD02B627D8E3D4000611B7 /* Robots */ = { isa = PBXGroup; children = ( @@ -6394,11 +6233,9 @@ 82A6F5BF27E2031000F4A2F6 /* Reactions_Tests.swift */, 8232B84E28635C4A0032C7DB /* Attachments_Tests.swift */, A33FA817282E559A00DC40E8 /* SlowMode_Tests.swift */, - A3600B3B283F639700E1C930 /* StreamTestCase+Tags.swift */, A3CB2B9F2858C06B00DCAE3E /* Ephemeral_Messages_Tests.swift */, 829762DF28C7587500B953E8 /* PushNotification_Tests.swift */, 826EF2B0291C01C1005A9EEF /* Authentication_Tests.swift */, - 8279706E29689680006741A3 /* UserDetails_Tests.swift */, 8292D6DA29B78476007A17D1 /* QuotedReply_Tests.swift */, ); path = Tests; @@ -8259,26 +8096,12 @@ children = ( A35715FA283E9A080014E3B0 /* StreamChatTestMockServer.h */, A3A0C9C0283E967600B18DA4 /* StreamChatTestMockServer.swift */, - A3A2D3432837D99E00D45F6A /* Extensions */, 82AD02B427D8E3C0000611B7 /* MockServer */, 829CD5C22848C244003C3877 /* Robots */, - A3C049C62840BDA700E25E38 /* Fixtures */, - 82AD02B527D8E3C7000611B7 /* Utilities */, - 8263464D2B0BADFF00122D0E /* Swifter */, ); path = StreamChatTestMockServer; sourceTree = ""; }; - A3A2D3432837D99E00D45F6A /* Extensions */ = { - isa = PBXGroup; - children = ( - 8224FD85280EC09800B32D43 /* Swifter.swift */, - 827414402ACDF6C2009CD13C /* String.swift */, - 827414422ACDF76C009CD13C /* Dictionary.swift */, - ); - path = Extensions; - sourceTree = ""; - }; A3B78F16282A670600348AD1 /* Message Delivery Status */ = { isa = PBXGroup; children = ( @@ -8316,41 +8139,6 @@ path = QueueAware; sourceTree = ""; }; - A3C049C62840BDA700E25E38 /* Fixtures */ = { - isa = PBXGroup; - children = ( - A3C049C72840BDA700E25E38 /* JSONs */, - ); - path = Fixtures; - sourceTree = ""; - }; - A3C049C72840BDA700E25E38 /* JSONs */ = { - isa = PBXGroup; - children = ( - A3C049D32840BDA700E25E38 /* http_channels.json */, - A3C049C92840BDA700E25E38 /* http_events.json */, - A3C049CD2840BDA700E25E38 /* http_member.json */, - A3B68B06284503AC00DF4321 /* http_message_ephemeral.json */, - A3C049CC2840BDA700E25E38 /* http_message.json */, - A3C049CA2840BDA700E25E38 /* http_reaction.json */, - 82E43AC028637651007BB6CD /* http_attachment.json */, - 826B1C3428895AFD005DDF13 /* http_youtube_link.json */, - 826B1C3628895BB5005DDF13 /* http_unsplash_link.json */, - 826B1C38288FD756005DDF13 /* http_truncate.json */, - A3C049D22840BDA700E25E38 /* ws_events_channel.json */, - A3C049CB2840BDA700E25E38 /* ws_events_member.json */, - A3C049D12840BDA700E25E38 /* ws_events.json */, - A3C049CF2840BDA700E25E38 /* ws_health_check.json */, - A3C049CE2840BDA700E25E38 /* ws_reaction.json */, - 82390E7A28C609A700829581 /* push_notification.json */, - 8223EE792937A099006138B9 /* http_add_member.json */, - 8223EE7B2937A0E9006138B9 /* http_channel_creation.json */, - 8223EE7D2937A1F5006138B9 /* http_channel_removal.json */, - 8223EE7F2937A21E006138B9 /* ws_message.json */, - ); - path = JSONs; - sourceTree = ""; - }; A3C2700027E1D8030057D5A8 /* MessageSearchController */ = { isa = PBXGroup; children = ( @@ -10772,26 +10560,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - A3C049DB2840BDA700E25E38 /* ws_health_check.json in Resources */, - 8223EE802937A21E006138B9 /* ws_message.json in Resources */, - A3C049DF2840BDA700E25E38 /* http_channels.json in Resources */, - 826B1C3728895BB5005DDF13 /* http_unsplash_link.json in Resources */, - A3C049DE2840BDA700E25E38 /* ws_events_channel.json in Resources */, - A3C049D52840BDA700E25E38 /* http_events.json in Resources */, - A3C049DD2840BDA700E25E38 /* ws_events.json in Resources */, - 82390E7B28C609A700829581 /* push_notification.json in Resources */, - A3C049DA2840BDA700E25E38 /* ws_reaction.json in Resources */, - A3C049D72840BDA700E25E38 /* ws_events_member.json in Resources */, - 8223EE7A2937A099006138B9 /* http_add_member.json in Resources */, - 826B1C3528895AFD005DDF13 /* http_youtube_link.json in Resources */, - 82E43AC128637651007BB6CD /* http_attachment.json in Resources */, - A3C049D82840BDA700E25E38 /* http_message.json in Resources */, - A3B68B07284503AD00DF4321 /* http_message_ephemeral.json in Resources */, - 8223EE7C2937A0E9006138B9 /* http_channel_creation.json in Resources */, - A3C049D62840BDA700E25E38 /* http_reaction.json in Resources */, - 8223EE7E2937A1F5006138B9 /* http_channel_removal.json in Resources */, - A3C049D92840BDA700E25E38 /* http_member.json in Resources */, - 826B1C39288FD756005DDF13 /* http_truncate.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -12564,7 +12332,6 @@ A3B78F1A282A6A8F00348AD1 /* UserRobot+Asserts.swift in Sources */, 825A32CD27DBB46F000402A9 /* ChannelListPage.swift in Sources */, A36D1EA9283F755F008D6110 /* StreamTestCase.swift in Sources */, - 8279706F29689680006741A3 /* UserDetails_Tests.swift in Sources */, A3B78F18282A675700348AD1 /* MessageDeliveryStatus+ChannelList_Tests.swift in Sources */, 826EF2B1291C01C1005A9EEF /* Authentication_Tests.swift in Sources */, 829762E028C7587500B953E8 /* PushNotification_Tests.swift in Sources */, @@ -12573,7 +12340,6 @@ 825A32CB27DBB463000402A9 /* MessageListPage.swift in Sources */, A33FA816282D595C00DC40E8 /* ChannelList_Tests.swift in Sources */, 822F266227D9FE5E00E454FB /* RequestRecorderURLProtocol_Mock.swift in Sources */, - A3600B3C283F639700E1C930 /* StreamTestCase+Tags.swift in Sources */, 82A6F5C027E2031000F4A2F6 /* Reactions_Tests.swift in Sources */, 823A1ADA28C74C1400F7CADA /* SpringBoard.swift in Sources */, ); @@ -12583,46 +12349,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 826346632B0BAE3800122D0E /* String+File.swift in Sources */, - A3A0C9A6283E955200B18DA4 /* WebsocketResponses.swift in Sources */, - 8263466A2B0BAE3800122D0E /* MimeTypes.swift in Sources */, - 826346642B0BAE3800122D0E /* HttpParser.swift in Sources */, A35715F4283E98110014E3B0 /* StreamChatTestMockServer.swift in Sources */, - 826346712B0BAE3800122D0E /* HttpRequest.swift in Sources */, - 826346702B0BAE3800122D0E /* HttpResponse.swift in Sources */, - 827414432ACDF76C009CD13C /* Dictionary.swift in Sources */, - A3A0C9A5283E955200B18DA4 /* TestData.swift in Sources */, - A3A0C9A3283E955200B18DA4 /* Swifter.swift in Sources */, - 826346732B0BAE3800122D0E /* HttpServer.swift in Sources */, - 826346722B0BAE3800122D0E /* Process.swift in Sources */, - 827414412ACDF6C2009CD13C /* String.swift in Sources */, + 822D36222EF5EDE600AC3B37 /* LaunchArgument.swift in Sources */, 829CD5C52848C2EA003C3877 /* ParticipantRobot.swift in Sources */, - 826346622B0BAE3800122D0E /* Socket+File.swift in Sources */, - 826346682B0BAE3800122D0E /* String+SHA1.swift in Sources */, - 8263466D2B0BAE3800122D0E /* HttpRouter.swift in Sources */, - 8263466C2B0BAE3800122D0E /* WebSockets.swift in Sources */, - A3A0C9B3283E955200B18DA4 /* EventResponses.swift in Sources */, - A3A0C9A1283E955200B18DA4 /* ChannelResponses.swift in Sources */, - 826346672B0BAE3800122D0E /* Scopes.swift in Sources */, - A3A0C9A9283E955200B18DA4 /* MessageResponses.swift in Sources */, - 826346742B0BAE3800122D0E /* Errno.swift in Sources */, - 9041E4AD2AE9768800CA2A2A /* MembersResponse.swift in Sources */, - 8263466B2B0BAE3800122D0E /* Files.swift in Sources */, - 826992C82900628500D2D470 /* DeviceRemoteControl.swift in Sources */, - 826346692B0BAE3800122D0E /* String+BASE64.swift in Sources */, - A3A0C9AD283E955200B18DA4 /* MessageList.swift in Sources */, - A3A0C9B1283E955200B18DA4 /* LaunchArgument.swift in Sources */, - 826346652B0BAE3800122D0E /* Socket+Server.swift in Sources */, + 822D36202EF5EDAC00AC3B37 /* DataTypes.swift in Sources */, A3A0C9B0283E955200B18DA4 /* StreamMockServer.swift in Sources */, - A3A0C9B4283E955200B18DA4 /* MockServerAttributes.swift in Sources */, - 826346752B0BAE3800122D0E /* Socket.swift in Sources */, - 826346662B0BAE3800122D0E /* String+Misc.swift in Sources */, - 8263466F2B0BAE3800122D0E /* HttpServerIO.swift in Sources */, - 8263466E2B0BAE3800122D0E /* DemoServer.swift in Sources */, - A3A0C9B2283E955200B18DA4 /* User.swift in Sources */, - A3A0C9B5283E955200B18DA4 /* ReactionResponses.swift in Sources */, - A3A0C9AA283E955200B18DA4 /* ChannelConfig.swift in Sources */, - 82CBE5682861BF300039C35C /* AttachmentResponses.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/StreamChatUITestsApp/ViewController.swift b/StreamChatUITestsApp/ViewController.swift index dfb4778457b..6de49868b72 100644 --- a/StreamChatUITestsApp/ViewController.swift +++ b/StreamChatUITestsApp/ViewController.swift @@ -210,8 +210,8 @@ extension StreamChatWrapper { func mockTokenProvider(for userCredentials: UserCredentials) -> TokenProvider { return { completion in if ProcessInfo.processInfo.arguments.contains("MOCK_JWT") { - let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"] ?? "" - let urlString = "http://localhost:4567/jwt/\(udid)?api_key=\(apiKeyString)&user_name=\(userCredentials.id)" + let port = ProcessInfo.processInfo.environment["MOCK_SERVER_PORT"]! + let urlString = "http://localhost:\(port)/jwt/get?platform=ios" guard let url = URL(string: urlString) else { return } var request = URLRequest(url: url) diff --git a/StreamChatUITestsAppUITests/Pages/ChannelListPage.swift b/StreamChatUITestsAppUITests/Pages/ChannelListPage.swift index fa1970d0369..fd211e82728 100644 --- a/StreamChatUITestsAppUITests/Pages/ChannelListPage.swift +++ b/StreamChatUITestsAppUITests/Pages/ChannelListPage.swift @@ -55,7 +55,10 @@ enum ChannelListPage { cell.staticTexts["unreadCountLabel"] } - static func statusCheckmark(for status: MessageDeliveryStatus?, in cell: XCUIElement) -> XCUIElement { + static func statusCheckmark( + for status: StreamChatTestMockServer.MessageDeliveryStatus?, + in cell: XCUIElement + ) -> XCUIElement { var identifier = "There is no status checkmark" if let status = status { identifier = "imageView_\(status.rawValue)" diff --git a/StreamChatUITestsAppUITests/Pages/MessageListPage.swift b/StreamChatUITestsAppUITests/Pages/MessageListPage.swift index ab8c9f9f2ab..b23d48ae425 100644 --- a/StreamChatUITestsAppUITests/Pages/MessageListPage.swift +++ b/StreamChatUITestsAppUITests/Pages/MessageListPage.swift @@ -208,7 +208,10 @@ class MessageListPage { messageCell.staticTexts["messageReadСountsLabel"] } - static func statusCheckmark(for status: MessageDeliveryStatus?, in messageCell: XCUIElement) -> XCUIElement { + static func statusCheckmark( + for status: StreamChatTestMockServer.MessageDeliveryStatus?, + in messageCell: XCUIElement + ) -> XCUIElement { var identifier = "There is no status checkmark" if let status = status { identifier = "imageView_\(status.rawValue)" diff --git a/StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift b/StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift index 2b003eeec4d..eab93ca9668 100644 --- a/StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift +++ b/StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift @@ -3,7 +3,6 @@ // import Foundation -@testable import StreamChat @testable import StreamChatUI import XCTest @@ -36,6 +35,25 @@ extension UserRobot { ) return channelCells.element(boundBy: index) } + + @discardableResult + func assertChannelPreviewIsEmpty( + at cellIndex: Int? = nil, + file: StaticString = #filePath, + line: UInt = #line + ) -> Self { + let emptyChannelPreviewText = "No messages" + let cell = channelCell(withIndex: cellIndex, file: file, line: line) + let message = channelAttributes.lastMessage(in: cell) + let actualText = message.waitForText(emptyChannelPreviewText, mustBeEqual: true).text + XCTAssertEqual( + actualText, + emptyChannelPreviewText, + file: file, + line: line + ) + return self + } @discardableResult func assertLastMessageInChannelPreview( @@ -75,7 +93,7 @@ extension UserRobot { @discardableResult func assertMessageDeliveryStatusInChannelPreview( - _ deliveryStatus: MessageDeliveryStatus?, + _ deliveryStatus: StreamChatTestMockServer.MessageDeliveryStatus?, at cellIndex: Int? = nil, file: StaticString = #filePath, line: UInt = #line @@ -229,8 +247,8 @@ extension UserRobot { @discardableResult func assertPushNotification( - withText text: String, - from sender: String, + title: String, + body: String, file: StaticString = #filePath, line: UInt = #line ) -> Self { @@ -244,14 +262,14 @@ extension UserRobot { let pushNotificationContent = pushNotification.text XCTAssertTrue( - pushNotificationContent.contains(text), - "\(pushNotificationContent) does not contain \(text)", + pushNotificationContent.contains(body), + "\(pushNotificationContent) does not contain \(body)", file: file, line: line ) XCTAssertTrue( - pushNotificationContent.contains(sender), - "\(pushNotificationContent) does not contain \(sender)", + pushNotificationContent.contains(title), + "\(pushNotificationContent) does not contain \(title)", file: file, line: line ) @@ -326,6 +344,19 @@ extension UserRobot { XCTAssertEqual(text, actualText, file: file, line: line) return self } + + @discardableResult + func assertFirstMessageIsVisible( + _ text: String, + file: StaticString = #filePath, + line: UInt = #line + ) -> Self { + let messageCell = messageCell(withIndex: cells.count - 1, file: file, line: line) + let message = attributes.text(text, in: messageCell).wait() + let actualText = message.waitForText(text).text + XCTAssertEqual(text, actualText, file: file, line: line) + return self + } @discardableResult func assertMessageIsNotVisible( @@ -519,7 +550,7 @@ extension UserRobot { @discardableResult func waitForMessageDeliveryStatus( - _ deliveryStatus: MessageDeliveryStatus?, + _ deliveryStatus: StreamChatTestMockServer.MessageDeliveryStatus?, at messageCellIndex: Int? = nil, file: StaticString = #filePath, line: UInt = #line @@ -535,7 +566,7 @@ extension UserRobot { @discardableResult func assertMessageDeliveryStatus( - _ deliveryStatus: MessageDeliveryStatus?, + _ deliveryStatus: StreamChatTestMockServer.MessageDeliveryStatus?, at messageCellIndex: Int? = nil, file: StaticString = #filePath, line: UInt = #line @@ -678,9 +709,8 @@ extension UserRobot { } @discardableResult - func assertMentionWasApplied(file: StaticString = #filePath, line: UInt = #line) -> Self { + func assertMentionWasApplied(userName: String, file: StaticString = #filePath, line: UInt = #line) -> Self { let additionalSpace = " " - let userName = UserDetails.countDookuName let expectedText = "@\(userName)\(additionalSpace)" let actualText = MessageListPage.Composer.textView.waitForText(expectedText).text XCTAssertEqual(expectedText, actualText, file: file, line: line) @@ -848,7 +878,7 @@ extension UserRobot { @discardableResult func assertThreadReplyDeliveryStatus( - _ deliveryStatus: MessageDeliveryStatus?, + _ deliveryStatus: StreamChatTestMockServer.MessageDeliveryStatus?, at messageCellIndex: Int? = nil, file: StaticString = #filePath, line: UInt = #line @@ -907,15 +937,6 @@ extension UserRobot { XCTAssertFalse(sendButton.exists, "Send button is visible", file: file, line: line) return self } - - @discardableResult - func assertThreadReplyCountButton( - at messageCellIndex: Int? = nil, - file: StaticString = #filePath, - line: UInt = #line - ) -> Self { - assertThreadReplyCountButton(at: messageCellIndex, replies: 0, file: file, line: line) - } @discardableResult func assertThreadReplyCountButton( @@ -932,10 +953,9 @@ extension UserRobot { file: file, line: line ) - if replies > 0 { - let expectedText = "\(replies) Thread Replies" - XCTAssertEqual(expectedText, threadReplyCountButton.waitForText(expectedText).text) - } + + let expectedText = replies == 1 ? "1 Thread Reply" : "\(replies) Thread Replies" + XCTAssertEqual(expectedText, threadReplyCountButton.waitForText(expectedText).text) return self } @@ -1037,7 +1057,7 @@ extension UserRobot { line: UInt = #line ) -> Self { let cell = messageCell(withIndex: messageCellIndex, file: file, line: line).wait() - let expectedText = Message.message(withInvalidCommand: invalidCommand) + let expectedText = "Sorry, command \(invalidCommand) doesn't exist. Try posting your message without the starting /" let actualText = attributes.text(in: cell).waitForText(expectedText).text XCTAssertEqual(actualText, expectedText, file: file, line: line) return self @@ -1120,35 +1140,3 @@ extension UserRobot { return self } } - -// MARK: UserDetails - -extension UserRobot { - @discardableResult - func assertUserDetails(_ details: [String: Any]?) -> Self { - let userDetails = details?[WebSocketConnectPayload.CodingKeys.userDetails.rawValue] as? [String: Any] - - let serverDeterminesConnectionId = details?[WebSocketConnectPayload.CodingKeys.serverDeterminesConnectionId.rawValue] as? Bool - XCTAssertEqual(true, serverDeterminesConnectionId) - - let userId = details?[WebSocketConnectPayload.CodingKeys.userId.rawValue] as? String - XCTAssertEqual(UserDetails.lukeSkywalkerId, userId) - - let id = userDetails?[UserWebSocketPayload.CodingKeys.id.rawValue] as? String - XCTAssertEqual(UserDetails.lukeSkywalkerId, id) - - let isInvisible = userDetails?[UserWebSocketPayload.CodingKeys.isInvisible.rawValue] as? Bool - XCTAssertEqual(nil, isInvisible) - - let name = userDetails?[UserWebSocketPayload.CodingKeys.name.rawValue] as? String - XCTAssertEqual(UserDetails.lukeSkywalkerName, name) - - let imageURL = userDetails?[UserWebSocketPayload.CodingKeys.imageURL.rawValue] as? String - XCTAssertEqual(UserDetails.lukeSkywalkerImageURL, imageURL) - - let birthland = userDetails?["birthland"] as? String - XCTAssertEqual("Tatooine", birthland) - - return self - } -} diff --git a/StreamChatUITestsAppUITests/Robots/UserRobot.swift b/StreamChatUITestsAppUITests/Robots/UserRobot.swift index c8d21e883d7..63ae8633a6c 100644 --- a/StreamChatUITestsAppUITests/Robots/UserRobot.swift +++ b/StreamChatUITestsAppUITests/Robots/UserRobot.swift @@ -33,13 +33,11 @@ final class UserRobot: Robot { func waitForChannelListToLoad() -> Self { let timeout = 15.0 let cells = ChannelListPage.cells.waitCount(1, timeout: timeout) - + // TODO: CIS-1737 if !cells.firstMatch.exists { for _ in 0...10 { app.terminate() - server.stop() - _ = server.start(port: MockServerConfiguration.port) sleep(1) app.launch() login() @@ -47,7 +45,7 @@ final class UserRobot: Robot { if cells.firstMatch.exists { break } } } - + XCTAssertGreaterThanOrEqual(cells.count, 1, "Channel list has not been loaded") return self } @@ -61,7 +59,14 @@ final class UserRobot: Robot { @discardableResult public func waitForJwtToExpire() -> Self { - let sleepTime = UInt32(StreamMockServer.jwtTimeout * 1_000_000) + let sleepTime = UInt32((StreamMockServer.jwtTimeout + 2) * 1_000_000) + usleep(sleepTime) + return self + } + + @discardableResult + public func sleep(_ seconds: Double) -> Self { + let sleepTime = UInt32(seconds * 1_000_000) usleep(sleepTime) return self } @@ -94,15 +99,10 @@ extension UserRobot { file: StaticString = #filePath, line: UInt = #line ) -> Self { - server.channelsEndpointWasCalled = false - typeText(text) composer.sendButton.safeTap() if waitForAppearance { - server.waitForWebsocketMessage(withText: text) - server.waitForHttpMessage(withText: text) - let cell = messageCell(withIndex: messageCellIndex, file: file, line: line).wait() let textView = attributes.text(in: cell) _ = textView.waitForText(text) @@ -153,7 +153,7 @@ extension UserRobot { @discardableResult private func reactionAction( - reactionType: TestData.Reactions, + reactionType: ReactionType, eventType: EventType, messageCellIndex: Int ) -> Self { @@ -179,7 +179,7 @@ extension UserRobot { } @discardableResult - func addReaction(type: TestData.Reactions, messageCellIndex: Int = 0) -> Self { + func addReaction(type: ReactionType, messageCellIndex: Int = 0) -> Self { reactionAction( reactionType: type, eventType: .reactionNew, @@ -188,7 +188,7 @@ extension UserRobot { } @discardableResult - func deleteReaction(type: TestData.Reactions, messageCellIndex: Int = 0) -> Self { + func deleteReaction(type: ReactionType, messageCellIndex: Int = 0) -> Self { reactionAction( reactionType: type, eventType: .reactionDeleted, @@ -220,12 +220,14 @@ extension UserRobot { ) return self } - + @discardableResult - func openThread(messageCellIndex: Int = 0) -> Self { + func openThread(messageCellIndex: Int = 0, waitForThreadIcon: Bool = false) -> Self { let messageCell = messageCell(withIndex: messageCellIndex) let threadButton = MessageListPage.Attributes.threadReplyCountButton(in: messageCell) - if threadButton.waitForExistence(timeout: 5) { + if waitForThreadIcon { threadButton.wait() } + + if threadButton.exists { threadButton.tap() } else { selectOptionFromContextMenu(option: .threadReply, forMessageAtIndex: messageCellIndex) @@ -277,7 +279,7 @@ extension UserRobot { } @discardableResult - func replyToMessageInThread( + func sendMessageInThread( _ text: String, alsoSendInChannel: Bool = false, messageCellIndex: Int = 0, @@ -377,7 +379,7 @@ extension UserRobot { } @discardableResult - func sendGiphy(text: String = "Test", useComposerCommand: Bool = false, send: Bool = true) -> Self { + func uploadGiphy(text: String = "Test", useComposerCommand: Bool = false, send: Bool = true) -> Self { if useComposerCommand { openComposerCommands() MessageListPage.ComposerCommands.giphyImage.wait().safeTap() @@ -394,7 +396,7 @@ extension UserRobot { func replyWithGiphy(useComposerCommand: Bool = false, messageCellIndex: Int = 0) -> Self { return self .selectOptionFromContextMenu(option: .reply, forMessageAtIndex: messageCellIndex) - .sendGiphy(useComposerCommand: useComposerCommand) + .uploadGiphy(useComposerCommand: useComposerCommand) } @discardableResult @@ -410,7 +412,7 @@ extension UserRobot { if alsoSendInChannel { threadCheckbox.wait().safeTap() } - return sendGiphy(useComposerCommand: useComposerCommand) + return uploadGiphy(useComposerCommand: useComposerCommand) } @discardableResult @@ -476,8 +478,8 @@ extension UserRobot { } @discardableResult - func mentionParticipant(manually: Bool = false) -> Self { - let text = "@\(UserDetails.countDookuId)" + func mentionParticipant(_ userId: String, manually: Bool = false) -> Self { + let text = "@\(userId)" if manually { typeText(text) } else { @@ -498,7 +500,7 @@ extension UserRobot { } @discardableResult - func addParticipant(withUserId userId: String = UserDetails.leiaOrganaId) -> Self { + func addParticipant(withUserId userId: String = "leia_organa") -> Self { tapOnDebugMenu() debugAlert.addMember.firstMatch.safeTap() debugAlert.addMemberTextField.firstMatch @@ -509,7 +511,7 @@ extension UserRobot { } @discardableResult - func removeParticipant(withUserId userId: String = UserDetails.leiaOrganaId) -> Self { + func removeParticipant(withUserId userId: String = "leia_organa") -> Self { tapOnDebugMenu() debugAlert.removeMember.firstMatch.safeTap() debugAlert.selectMember(withUserId: userId).firstMatch.safeTap() diff --git a/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift b/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift index e918e90326f..78f5d010e86 100644 --- a/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift @@ -4,24 +4,8 @@ import XCTest +// NOTE: Attachments tests used to freeze the test app on iOS > 18" final class Attachments_Tests: StreamTestCase { - override func setUpWithError() throws { - try XCTSkipIf( - ProcessInfo().operatingSystemVersion.majorVersion >= 18, - "Attachments tests freeze the test app on iOS > 18" - ) - - try super.setUpWithError() - addTags([.coreFeatures]) - assertMockServer() - } - - override func tearDownWithError() throws { - if ProcessInfo().operatingSystemVersion.majorVersion < 18 { - try super.tearDownWithError() - } - } - func test_uploadImage() throws { linkToScenario(withId: 28) @@ -63,6 +47,20 @@ final class Attachments_Tests: StreamTestCase { userRobot.assertVideo(isPresent: true) } } + + func test_participantUploadsFile() throws { + linkToScenario(withId: 33) + + GIVEN("user opens the channel") { + userRobot.login().openChannel() + } + WHEN("participant uploads a file") { + participantRobot.uploadAttachment(type: .file) + } + THEN("user can see uploaded file") { + userRobot.assertFile(isPresent: true) + } + } func test_restartImageUpload() throws { linkToScenario(withId: 2195) diff --git a/StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift b/StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift index f9d35a6421c..c028a1319a9 100644 --- a/StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift @@ -4,20 +4,107 @@ import XCTest -// Requires running a standalone Sinatra server final class Authentication_Tests: StreamTestCase { override func setUpWithError() throws { - mockServerEnabled = false - switchApiKey = "8br4watad788" app.setLaunchArguments(.jwt) try super.setUpWithError() } + + func test_tokenInvalidatesBeforeUserLogsIn() { + linkToScenario(withId: 9674) + + GIVEN("token is invalid") { + backendRobot.invalidateToken() + } + WHEN("user tries to log in") { + userRobot.login() + } + THEN("app requests a token refresh") { + userRobot.assertConnectionStatus(.connected) + } + } + + func test_tokenInvalidatesAfterUserLogsIn() { + linkToScenario(withId: 9675) + + GIVEN("user logs in") { + userRobot + .login() + .assertConnectionStatus(.connected) + } + WHEN("token invalidates") { + backendRobot.invalidateToken() + } + THEN("app requests a token refresh") { + userRobot.assertConnectionStatus(.connected) + } + } + + func test_tokenDateInvalidatesBeforeUserLogsIn() { + linkToScenario(withId: 9676) + + GIVEN("token is invalid") { + backendRobot.invalidateTokenDate() + } + WHEN("user tries to log in") { + userRobot.login() + } + THEN("app requests a token refresh") { + userRobot.assertConnectionStatus(.connected) + } + } + + func test_tokenDateInvalidatesAfterUserLogsIn() { + linkToScenario(withId: 9677) + + GIVEN("user logs in") { + userRobot + .login() + .assertConnectionStatus(.connected) + } + WHEN("token invalidates") { + backendRobot.invalidateTokenDate() + } + THEN("app requests a token refresh") { + userRobot.assertConnectionStatus(.connected) + } + } + + func test_tokenSignatureInvalidatesBeforeUserLogsIn() { + linkToScenario(withId: 9678) + + GIVEN("token is invalid") { + backendRobot.invalidateTokenSignature() + } + WHEN("user tries to log in") { + userRobot.login() + } + THEN("app requests a token refresh") { + userRobot.assertConnectionStatus(.connected) + } + } + + func test_tokenSignatureInvalidatesAfterUserLogsIn() { + linkToScenario(withId: 9679) + + GIVEN("user logs in") { + userRobot + .login() + .assertConnectionStatus(.connected) + } + WHEN("token invalidates") { + backendRobot.invalidateTokenSignature() + } + THEN("app requests a token refresh") { + userRobot.assertConnectionStatus(.connected) + } + } func test_tokenExpiriesBeforeUserLogsIn() { linkToScenario(withId: 650) GIVEN("token expires") { - server.revokeJwt() + backendRobot.revokeToken() } WHEN("user tries to log in") { userRobot.login() @@ -91,7 +178,7 @@ final class Authentication_Tests: StreamTestCase { linkToScenario(withId: 654) GIVEN("JWT generation breaks on server side") { - server.breakJwt() + backendRobot.breakTokenGeneration() } AND("user tries to log in") { userRobot.login() diff --git a/StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift b/StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift index 0725256f81b..35b80244a05 100644 --- a/StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift @@ -6,7 +6,7 @@ import XCTest final class Backend_Tests: StreamTestCase { override func setUpWithError() throws { - mockServerEnabled = false + useMockServer = false switchApiKey = "8br4watad788" try super.setUpWithError() } diff --git a/StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift b/StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift index 9d8ec2eb693..98d9bead260 100644 --- a/StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift +++ b/StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift @@ -4,7 +4,6 @@ import XCTest -// Application let app = XCUIApplication() class StreamTestCase: XCTestCase { @@ -12,32 +11,27 @@ class StreamTestCase: XCTestCase { var userRobot: UserRobot! var backendRobot: BackendRobot! var participantRobot: ParticipantRobot! - var server: StreamMockServer! - var recordVideo = false - var mockServerEnabled = true - var mockServerCrashed = false + var mockServer: StreamMockServer! + var useMockServer = true var switchApiKey: String? override func setUpWithError() throws { continueAfterFailure = false - startMockServer() - participantRobot = ParticipantRobot(server) - backendRobot = BackendRobot(server) - userRobot = UserRobot(server) try super.setUpWithError() + mockServer = StreamMockServer(driverPort: "4566", testName: testName) + backendRobot = BackendRobot(mockServer) + participantRobot = ParticipantRobot(mockServer) + userRobot = UserRobot(mockServer) alertHandler() - useMockServer() - startVideo() + backendHandler() app.launch() } override func tearDownWithError() throws { attachElementTree() - stopVideo() app.terminate() - server.stop() - backendRobot.delayServerResponse(byTimeInterval: 0.0) + mockServer.stop() try super.tearDownWithError() app.launchArguments.removeAll() @@ -46,21 +40,15 @@ class StreamTestCase: XCTestCase { } extension StreamTestCase { - func assertMockServer() { - XCTAssertFalse(mockServerCrashed, "Mock server failed on start") - } - - private func useMockServer() { - if mockServerEnabled { - // Leverage web socket server + private func backendHandler() { + app.setEnvironmentVariables([ + .websocketHost: "ws://localhost", + .httpHost: "http://localhost", + .port: StreamMockServer.port! + ]) + + if useMockServer { app.setLaunchArguments(.useMockServer) - - // Configure web socket host - app.setEnvironmentVariables([ - .websocketHost: "\(MockServerConfiguration.websocketHost)", - .httpHost: "\(MockServerConfiguration.httpHost)", - .port: "\(MockServerConfiguration.port)" - ]) } else if let switchApiKey { app.setEnvironmentVariables([.customApiKey: switchApiKey]) } @@ -84,34 +72,6 @@ extension StreamTestCase { } } - private func startMockServer() { - server = StreamMockServer() - server.configure() - - for _ in 0...3 { - let serverHasStarted = server.start(port: MockServerConfiguration.port) - if serverHasStarted { - return - } - server.stop() - MockServerConfiguration.port = UInt16(Int.random(in: 61000..<62000)) - } - - mockServerCrashed = true - } - - private func startVideo() { - if recordVideo { - server.recordVideo(name: testName) - } - } - - private func stopVideo() { - if recordVideo { - server.recordVideo(name: testName, delete: !isTestFailed(), stop: true) - } - } - private func isTestFailed() -> Bool { if let testRun = testRun { let failureCount = testRun.failureCount + testRun.unexpectedExceptionCount diff --git a/StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift b/StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift index 18c1b8655e6..f3a9d0d4e67 100644 --- a/StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift @@ -7,12 +7,6 @@ import XCTest final class ChannelList_Tests: StreamTestCase { let message = "message" - override func setUpWithError() throws { - try super.setUpWithError() - addTags([.coreFeatures]) - assertMockServer() - } - func test_newMessageShownInChannelPreview_whenComingBackFromChannel() { linkToScenario(withId: 79) @@ -47,7 +41,7 @@ final class ChannelList_Tests: StreamTestCase { WHEN("participant sends a new message") { participantRobot .sendMessage(message) - .wait(2.0) + .sleep(2.0) } AND("user becomes online") { userRobot.setConnectivity(to: .on) @@ -56,14 +50,14 @@ final class ChannelList_Tests: StreamTestCase { userRobot.assertLastMessageInChannelPreview(message) } } - + func test_paginationOnChannelList() { linkToScenario(withId: 276) let channelsCount = 30 WHEN("user opens the channel list") { - backendRobot.generateChannels(count: channelsCount) + backendRobot.generateChannels(channelsCount: channelsCount) userRobot.login() } THEN("user makes sure that all channels are loaded") { @@ -189,7 +183,7 @@ extension ChannelList_Tests { userRobot.sendMessage(channelMessage) } AND("user adds thread reply to this message") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } WHEN("user goes back to the channel list") { userRobot.moveToChannelListFromThreadReplies() @@ -240,7 +234,7 @@ extension ChannelList_Tests { let message = "Channel truncated" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 42) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 42) userRobot.login().openChannel() } WHEN("user truncates the channel with system message") { @@ -263,4 +257,31 @@ extension ChannelList_Tests { userRobot.assertLastMessageTimestampInChannelPreview(isHidden: false) } } + + func test_messageList_and_channelPreview_AreUpdatedWhenChannelTruncatedWithoutMessage() { + linkToScenario(withId: 284) + + GIVEN("user opens the channel") { + backendRobot.generateChannels(channelsCount: 1, messagesCount: 42) + userRobot.login().openChannel() + } + WHEN("user truncates the channel without system message") { + userRobot.truncateChannel(withMessage: false) + } + THEN("user observes only the system message") { + userRobot + .assertMessageCount(0) + .assertScrollToBottomButton(isVisible: false) + .assertScrollToBottomButtonUnreadCount(0) + } + WHEN("user goes to channel list") { + userRobot.tapOnBackButton() + } + THEN("the channel preview is empty") { + userRobot.assertChannelPreviewIsEmpty() + } + AND("last message timestamp is not shown") { + userRobot.assertLastMessageTimestampInChannelPreview(isHidden: true) + } + } } diff --git a/StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift b/StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift index 93306125c47..86161b463c0 100644 --- a/StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift @@ -5,18 +5,9 @@ import XCTest final class Ephemeral_Messages_Tests: StreamTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - assertMockServer() - } - + // NOTE: There used to be a problem with tapping on a Send button on iOS > 16 func test_userObservesAnimatedGiphy_whenUserAddsGiphyMessage() throws { linkToScenario(withId: 67) - - try XCTSkipIf( - ProcessInfo().operatingSystemVersion.majorVersion > 16, - "The test cannot tap on a `Send` button on iOS 17" - ) GIVEN("user opens a channel") { userRobot @@ -24,7 +15,7 @@ final class Ephemeral_Messages_Tests: StreamTestCase { .openChannel() } WHEN("user sends a giphy using giphy command") { - userRobot.sendGiphy() + userRobot.uploadGiphy() } THEN("user observes the animated gif") { userRobot.assertGiphyImage() @@ -40,7 +31,7 @@ final class Ephemeral_Messages_Tests: StreamTestCase { .openChannel() } WHEN("participant sends a giphy") { - participantRobot.sendGiphy() + participantRobot.uploadGiphy() } THEN("user observes the animated gif") { userRobot.assertGiphyImage() @@ -85,7 +76,7 @@ final class Ephemeral_Messages_Tests: StreamTestCase { .openChannel() } WHEN("user runs a giphy command") { - userRobot.sendGiphy(send: false) + userRobot.uploadGiphy(send: false) } WHEN("user goes back to channel list") { userRobot.tapOnBackButton() @@ -104,7 +95,7 @@ final class Ephemeral_Messages_Tests: StreamTestCase { .openChannel() } WHEN("user runs a giphy command") { - userRobot.sendGiphy(send: false) + userRobot.uploadGiphy(send: false) } THEN("delivery status is hidden for ephemeral messages") { userRobot @@ -117,13 +108,13 @@ final class Ephemeral_Messages_Tests: StreamTestCase { linkToScenario(withId: 183) GIVEN("user opens a channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } WHEN("user runs a giphy command in thread") { userRobot .openThread() - .sendGiphy(send: false) + .uploadGiphy(send: false) } THEN("delivery status is hidden for ephemeral messages") { userRobot @@ -131,14 +122,10 @@ final class Ephemeral_Messages_Tests: StreamTestCase { .assertMessageReadCount(readBy: 0) } } - + + // NOTE: There used to be a problem with tapping on a Send button on iOS > 16 func test_userObservesAnimatedGiphy_afterAddingGiphyThroughComposerMenu() throws { linkToScenario(withId: 278) - - try XCTSkipIf( - ProcessInfo().operatingSystemVersion.majorVersion > 16, - "The test cannot tap on a `Send` button on iOS 17" - ) GIVEN("user opens a channel") { userRobot @@ -146,7 +133,7 @@ final class Ephemeral_Messages_Tests: StreamTestCase { .openChannel() } WHEN("user sends a giphy using giphy command") { - userRobot.sendGiphy(useComposerCommand: true) + userRobot.uploadGiphy(useComposerCommand: true) } THEN("user observes the animated gif") { userRobot.assertGiphyImage() diff --git a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift index e3784d4f342..2546c3a6115 100644 --- a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift @@ -12,12 +12,6 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { var pendingThreadReply: String { "pending \(threadReply)" } var failedThreadReply: String { "failed \(threadReply)" } - override func setUpWithError() throws { - try super.setUpWithError() - addTags([.messageDeliveryStatus]) - assertMockServer() - } - func test_deliveryStatusClocksShownInPreview_whenTheLastMessageIsInPendingState() { linkToScenario(withId: 166) @@ -25,9 +19,9 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { userRobot .login() .openChannel() - backendRobot.delayServerResponse(byTimeInterval: 10.0) } AND("user sends new message") { + backendRobot.delayNewMessages(by: 10) userRobot.sendMessage(message, waitForAppearance: false) } WHEN("user retuns to the channel list before the message is sent") { @@ -100,7 +94,7 @@ final class MessageDeliveryStatus_ChannelList_Tests: StreamTestCase { userRobot.tapOnBackButton() } WHEN("participant reads the user's message") { - participantRobot.readMessageAfterDelay() + participantRobot.readMessage() } THEN("user spots double checkmark next to the message") { userRobot.assertMessageDeliveryStatusInChannelPreview(.read) @@ -169,7 +163,7 @@ extension MessageDeliveryStatus_ChannelList_Tests { participantRobot.sendMessage(message) } AND("user replies to the message in thread") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } WHEN("user retuns to the channel list") { userRobot.moveToChannelListFromThreadReplies() @@ -197,7 +191,7 @@ extension MessageDeliveryStatus_ChannelList_Tests { userRobot.setConnectivity(to: .off) } AND("user replies to message in thread") { - userRobot.replyToMessageInThread(failedThreadReply, waitForAppearance: false) + userRobot.sendMessageInThread(failedThreadReply, waitForAppearance: false) } WHEN("user retuns to the channel list") { userRobot.moveToChannelListFromThreadReplies() @@ -222,10 +216,10 @@ extension MessageDeliveryStatus_ChannelList_Tests { participantRobot.sendMessage(message) } AND("user replies to message in thread") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } AND("participant reads the user's thread reply") { - participantRobot.readMessageAfterDelay() + participantRobot.readMessage() } WHEN("user retuns to the channel list") { userRobot.moveToChannelListFromThreadReplies() @@ -253,7 +247,7 @@ extension MessageDeliveryStatus_ChannelList_Tests { participantRobot.sendMessage(message) } AND("user replies to message in thread") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } WHEN("user retuns to the channel list") { userRobot.moveToChannelListFromThreadReplies() @@ -278,7 +272,7 @@ extension MessageDeliveryStatus_ChannelList_Tests { participantRobot.sendMessage(message) } AND("participant replies to message in thread") { - participantRobot.replyToMessageInThread(threadReply) + participantRobot.sendMessageInThread(threadReply) } WHEN("user retuns to the channel list") { userRobot.moveToChannelListFromThreadReplies() diff --git a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift index 6b9512eb21b..7b3c94f9f5e 100644 --- a/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift @@ -13,12 +13,6 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { var pendingThreadReply: String { "pending \(threadReply)" } var failedThreadReply: String { "failed \(threadReply)" } - override func setUpWithError() throws { - try super.setUpWithError() - addTags([.messageDeliveryStatus]) - assertMockServer() - } - // MARK: Message List func test_singleCheckmarkShown_whenMessageIsSent() { @@ -48,7 +42,7 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { .openChannel() } WHEN("user sends a new message") { - backendRobot.delayServerResponse(byTimeInterval: 5.0) + backendRobot.delayNewMessages(by: 5) userRobot.sendMessage(pendingMessage, waitForAppearance: false) } THEN("message delivery status shows clocks") { @@ -90,7 +84,7 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { userRobot.sendMessage(message) } WHEN("participant reads the user's message") { - participantRobot.readMessageAfterDelay() + participantRobot.readMessage() } THEN("user spots double checkmark below the message") { userRobot.assertMessageDeliveryStatus(.read) @@ -112,7 +106,7 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { userRobot.sendMessage(message) } AND("message is read by more than 1 participant") { - participantRobot.readMessageAfterDelay() + participantRobot.readMessage() userRobot .assertMessageDeliveryStatus(.read) .assertMessageReadCount(readBy: 1) @@ -131,8 +125,6 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { func test_readByDecremented_whenParticipantIsRemoved() { linkToScenario(withId: 145) - let participantOne = participantRobot.currentUserId - GIVEN("user opens the channel") { userRobot .login() @@ -142,13 +134,13 @@ final class MessageDeliveryStatus_Tests: StreamTestCase { userRobot.sendMessage(message) } AND("is read by participant") { - participantRobot.readMessageAfterDelay() + participantRobot.readMessage() userRobot .assertMessageDeliveryStatus(.read) .assertMessageReadCount(readBy: 1) } WHEN("participant is removed from the channel") { - userRobot.removeParticipant(withUserId: participantOne) + userRobot.removeParticipant(withUserId: participantRobot.id) } THEN("user spots single checkmark below the message") { userRobot.assertMessageDeliveryStatus(.sent) @@ -258,8 +250,6 @@ extension MessageDeliveryStatus_Tests { } func test_doubleCheckmarkShown_whenMessageReadByParticipant_andPreviewedInThread() throws { - throw XCTSkip("https://github.com/GetStream/ios-issues-tracking/issues/491") - linkToScenario(withId: 150) GIVEN("user opens the channel") { @@ -274,7 +264,7 @@ extension MessageDeliveryStatus_Tests { userRobot.openThread() } WHEN("the message is read by participant") { - participantRobot.readMessageAfterDelay() + participantRobot.readMessage() } THEN("user spots double checkmark below the message in thread") { userRobot.assertMessageDeliveryStatus(.read) @@ -298,7 +288,7 @@ extension MessageDeliveryStatus_Tests { participantRobot.sendMessage(message) } AND("user replies to the message in thread") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } THEN("user spots single checkmark below the thread reply") { userRobot @@ -311,14 +301,14 @@ extension MessageDeliveryStatus_Tests { linkToScenario(withId: 152) GIVEN("user becomes offline") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot .login() .setConnectivity(to: .off) .openChannel() } WHEN("user replies to message in thread") { - userRobot.replyToMessageInThread(failedThreadReply, waitForAppearance: false) + userRobot.sendMessageInThread(failedThreadReply, waitForAppearance: false) } THEN("error indicator is shown for the thread reply") { userRobot.assertThreadReplyFailedToBeSent() @@ -342,10 +332,10 @@ extension MessageDeliveryStatus_Tests { participantRobot.sendMessage(message) } WHEN("user replies to message in thread") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } AND("participant reads the user's thread reply") { - participantRobot.readMessageInThreadAfterDelay() + participantRobot.readMessage() } THEN("user spots double checkmark below the message") { userRobot.assertMessageDeliveryStatus(.read) @@ -357,7 +347,7 @@ extension MessageDeliveryStatus_Tests { func test_noDoubleCheckmarkShownInThreadReply_whenNewParticipantAdded() throws { linkToScenario(withId: 154) - + GIVEN("user opens the channel") { userRobot .login() @@ -367,7 +357,7 @@ extension MessageDeliveryStatus_Tests { participantRobot.sendMessage(message) } AND("user replies to message in thread") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } WHEN("new participant is added to the channel") { userRobot.addParticipant() @@ -383,8 +373,6 @@ extension MessageDeliveryStatus_Tests { func test_readByDecrementedInThreadReply_whenParticipantIsRemoved() { linkToScenario(withId: 155) - let participantOne = participantRobot.currentUserId - GIVEN("user opens the channel") { userRobot .login() @@ -394,16 +382,16 @@ extension MessageDeliveryStatus_Tests { participantRobot.sendMessage(message) } AND("user replies to message in thread") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } AND("thread reply is read by participant") { - participantRobot.readMessageInThreadAfterDelay() + participantRobot.readMessage() userRobot .assertMessageDeliveryStatus(.read) .assertMessageReadCount(readBy: 1) } WHEN("participant is removed from the channel") { - userRobot.removeParticipant(withUserId: participantOne) + userRobot.removeParticipant(withUserId: participantRobot.id) } THEN("user spots single checkmark below the message") { userRobot.assertMessageDeliveryStatus(.sent) @@ -423,7 +411,7 @@ extension MessageDeliveryStatus_Tests { participantRobot.sendMessage(message) } AND("user replies to message in thread") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } AND("delivery status shows single checkmark") { userRobot.assertMessageDeliveryStatus(.sent) @@ -451,7 +439,7 @@ extension MessageDeliveryStatus_Tests { participantRobot.sendMessage(message) } AND("user replies to message in thread") { - userRobot.replyToMessageInThread(threadReply) + userRobot.sendMessageInThread(threadReply) } AND("delivery status shows single checkmark") { userRobot.assertMessageDeliveryStatus(.sent) @@ -524,7 +512,7 @@ extension MessageDeliveryStatus_Tests { .openChannel() } WHEN("user sends a new message") { - backendRobot.delayServerResponse(byTimeInterval: 5.0) + backendRobot.delayNewMessages(by: 5) userRobot.sendMessage(pendingMessage, waitForAppearance: false) } THEN("message delivery status shows clocks") { @@ -570,7 +558,7 @@ extension MessageDeliveryStatus_Tests { userRobot.sendMessage(message) } WHEN("participant reads the user's message") { - participantRobot.readMessageAfterDelay() + participantRobot.readMessage() } THEN("delivery status is hidden") { userRobot @@ -592,7 +580,7 @@ extension MessageDeliveryStatus_Tests { userRobot.sendMessage(message) } AND("message is read by more than 1 participant") { - participantRobot.readMessageAfterDelay() + participantRobot.readMessage() } WHEN("new participant is added to the channel") { userRobot.addParticipant() @@ -607,8 +595,6 @@ extension MessageDeliveryStatus_Tests { func test_deliveryStatusHidden_whenParticipantIsRemovedAndReadEventsIsDisabled() { linkToScenario(withId: 163) - let participantOne = participantRobot.currentUserId - GIVEN("user opens the channel") { backendRobot.setReadEvents(to: false) userRobot @@ -619,10 +605,10 @@ extension MessageDeliveryStatus_Tests { userRobot.sendMessage(message) } AND("is read by participant") { - participantRobot.readMessageAfterDelay() + participantRobot.readMessage() } WHEN("participant is removed from the channel") { - userRobot.removeParticipant(withUserId: participantOne) + userRobot.removeParticipant(withUserId: participantRobot.id) } AND("delivery status is hidden") { userRobot.assertMessageDeliveryStatus(nil) diff --git a/StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift b/StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift index f0edbe3411f..e488ae7f1ed 100644 --- a/StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift @@ -5,12 +5,6 @@ import XCTest final class MessageList_Tests: StreamTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - addTags([.coreFeatures]) - assertMockServer() - } - func test_messageListUpdates_whenUserSendsMessage() { linkToScenario(withId: 25) @@ -200,32 +194,26 @@ final class MessageList_Tests: StreamTestCase { func test_typingIndicator() { linkToScenario(withId: 73) - let isIpad = UIDevice.current.userInterfaceIdiom == .pad - let typingIndicatorTimeout = isIpad ? 10 : XCUIElement.waitTimeout - let typingEventsTimeout: Double = 3 let message = "message" GIVEN("user opens the channel") { + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot .login() .openChannel() .sendMessage(message) } WHEN("participant starts typing") { - participantRobot.wait(typingEventsTimeout).startTyping() + participantRobot.startTyping() } THEN("user observes typing indicator is shown") { - let typingUserName = UserDetails.userName(for: participantRobot.currentUserId) - userRobot.assertTypingIndicatorShown( - typingUserName: typingUserName, - waitTimeout: typingIndicatorTimeout - ) + userRobot.assertTypingIndicatorShown(typingUserName: participantRobot.name) } WHEN("participant stops typing") { - participantRobot.wait(typingEventsTimeout).stopTyping() + participantRobot.stopTyping() } THEN("user observes typing indicator has disappeared") { - userRobot.assertTypingIndicatorHidden(waitTimeout: typingIndicatorTimeout) + userRobot.assertTypingIndicatorHidden() } } @@ -233,7 +221,7 @@ final class MessageList_Tests: StreamTestCase { linkToScenario(withId: 98) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 30) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 30) userRobot.login().openChannel() } AND("user opens command suggestions") { @@ -253,10 +241,7 @@ final class MessageList_Tests: StreamTestCase { let message = "test message" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 40) - userRobot - .login() - .openChannel() + userRobot.login().openChannel() } AND("user becomes offline") { userRobot.setConnectivity(to: .off) @@ -272,8 +257,10 @@ final class MessageList_Tests: StreamTestCase { } } - func test_addMessageWhileOffline() { + func test_addMessageWhileOffline() throws { linkToScenario(withId: 36) + + throw XCTSkip("https://linear.app/stream/issue/IOS-1345") let message = "test message" @@ -326,7 +313,7 @@ final class MessageList_Tests: StreamTestCase { deviceRobot.moveApplication(to: .background) } WHEN("participant sends a new message") { - participantRobot.wait(1).sendMessage(message) + participantRobot.sleep(1).sendMessage(message) } AND("user comes back to the foreground") { deviceRobot.moveApplication(to: .foreground) @@ -346,7 +333,7 @@ extension MessageList_Tests { let newMessage = "New message" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 30) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 30) userRobot.login().openChannel() } WHEN("user scrolls up") { @@ -368,7 +355,7 @@ extension MessageList_Tests { let newMessage = "New message" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 30) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 30) userRobot.login().openChannel() } WHEN("participant sends a message") { @@ -387,7 +374,7 @@ extension MessageList_Tests { let newMessage = "New message" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 30) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 30) userRobot.login().openChannel() } WHEN("user scrolls up") { @@ -409,7 +396,7 @@ extension MessageList_Tests { let newMessage = "New message" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 30) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 30) userRobot.login().openChannel() } AND("user sends a new message") { @@ -432,14 +419,14 @@ extension MessageList_Tests { linkToScenario(withId: 289) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 30) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 30) userRobot.login().openChannel() } AND("user scrolls up") { userRobot.scrollMessageListUpSlow() } AND("participant sends some messages") { - participantRobot.sendMultipleMessages(repeatingText: "Some message", count: 16) + participantRobot.sendMultipleMessages("Some message", count: 16) } WHEN("user scrolls to the bottom") { userRobot.tapOnScrollToBottomButton() @@ -459,7 +446,7 @@ extension MessageList_Tests { let newMessage = "New message" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 30) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 30) userRobot.login().openChannel() } WHEN("user scrolls up") { @@ -497,7 +484,7 @@ extension MessageList_Tests { let messagesCount = 60 WHEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: messagesCount) + backendRobot.generateChannels(channelsCount: 1, messagesCount: messagesCount) userRobot.login().openChannel() } THEN("user makes sure that chat history is loaded") { @@ -511,7 +498,7 @@ extension MessageList_Tests { let replyCount = 60 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1, replyCount: replyCount) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1, repliesCount: replyCount) userRobot.login().openChannel() } WHEN("user opens the thread") { @@ -573,10 +560,10 @@ extension MessageList_Tests { userRobot.login().openChannel() } WHEN("user taps on participants name") { - userRobot.mentionParticipant() + userRobot.mentionParticipant(participantRobot.id) } THEN("composer fills in participants name") { - userRobot.assertMentionWasApplied() + userRobot.assertMentionWasApplied(userName: participantRobot.name) } } } @@ -700,11 +687,11 @@ extension MessageList_Tests { let threadReply = "thread reply" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } WHEN("participant adds a thread reply") { - participantRobot.replyToMessageInThread(threadReply) + participantRobot.sendMessageInThread(threadReply) } AND("user enters thread") { userRobot.openThread() @@ -720,17 +707,17 @@ extension MessageList_Tests { let threadReply = "thread reply" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } WHEN("participant adds a thread reply") { - participantRobot.replyToMessageInThread(threadReply, alsoSendInChannel: true) + participantRobot.sendMessageInThread(threadReply, alsoSendInChannel: true) } THEN("user observes the thread reply in channel") { userRobot.assertMessage(threadReply) } WHEN("user enters thread") { - userRobot.openThread() + userRobot.openThread(waitForThreadIcon: true) } THEN("user observes the thread reply in thread") { userRobot.assertThreadReply(threadReply) @@ -743,11 +730,11 @@ extension MessageList_Tests { let threadReply = "thread reply" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } WHEN("user adds a thread reply and sends it also to main channel") { - userRobot.replyToMessageInThread(threadReply, alsoSendInChannel: true) + userRobot.sendMessageInThread(threadReply, alsoSendInChannel: true) } THEN("user observes the thread reply in thread") { userRobot.assertThreadReply(threadReply) @@ -763,21 +750,20 @@ extension MessageList_Tests { linkToScenario(withId: 243) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("user opens the thread") { userRobot.openThread() } WHEN("participant starts typing in thread") { - participantRobot.wait(2).startTypingInThread() + participantRobot.startTypingInThread() } THEN("user observes typing indicator is shown") { - let typingUserName = UserDetails.userName(for: participantRobot.currentUserId) - userRobot.assertTypingIndicatorShown(typingUserName: typingUserName) + userRobot.assertTypingIndicatorShown(typingUserName: participantRobot.name) } WHEN("participant stops typing in thread") { - participantRobot.wait(2).stopTypingInThread() + participantRobot.stopTypingInThread() } THEN("user observes typing indicator has disappeared") { userRobot.assertTypingIndicatorHidden() @@ -792,7 +778,7 @@ extension MessageList_Tests { linkToScenario(withId: 218) let message = "Hey there" - let messageWithForbiddenContent = server.forbiddenWords.first ?? "" + let messageWithForbiddenContent = mockServer.forbiddenWord GIVEN("user opens the channel") { userRobot @@ -831,7 +817,7 @@ extension MessageList_Tests { } WHEN("user sends an ephemeral message") { userRobot - .sendGiphy(send: false) + .uploadGiphy(send: false) .scrollMessageListDown() // to hide the keyboard } THEN("messages are not grouped, 1st message shows the timestamp") { @@ -846,7 +832,7 @@ extension MessageList_Tests { GIVEN("user opens the channel") { backendRobot - .generateChannels(count: 1, messagesCount: 1) + .generateChannels(channelsCount: 1, messagesCount: 1) userRobot .login() .openChannel() @@ -913,11 +899,11 @@ extension MessageList_Tests { let threadReply = "thread reply" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("participant adds a thread reply and sends it also to main channel") { - participantRobot.replyToMessageInThread(threadReply, alsoSendInChannel: true) + participantRobot.sendMessageInThread(threadReply, alsoSendInChannel: true) } WHEN("participant removes the thread reply from channel") { participantRobot.deleteMessage() @@ -927,7 +913,7 @@ extension MessageList_Tests { } AND("user observes the thread reply removed in thread") { userRobot - .openThread(messageCellIndex: 1) + .openThread(messageCellIndex: 1, waitForThreadIcon: true) .assertDeletedMessage() } } @@ -938,11 +924,11 @@ extension MessageList_Tests { let threadReply = "thread reply" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("user adds a thread reply and sends it also to main channel") { - userRobot.replyToMessageInThread(threadReply, alsoSendInChannel: true) + userRobot.sendMessageInThread(threadReply, alsoSendInChannel: true) } WHEN("user removes thread reply from thread") { userRobot.deleteMessage() @@ -963,17 +949,17 @@ extension MessageList_Tests { let threadReply = "thread reply" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("participant adds a thread reply") { - participantRobot.replyToMessageInThread(threadReply, alsoSendInChannel: false) + participantRobot.sendMessageInThread(threadReply, alsoSendInChannel: false) } WHEN("participant removes the thread reply") { participantRobot.deleteMessage() } THEN("user observes a thread reply count button in channel") { - userRobot.assertThreadReplyCountButton() + userRobot.assertThreadReplyCountButton(replies: 1) } THEN("user observes the thread reply removed in thread") { userRobot.openThread().assertDeletedMessage() @@ -986,11 +972,11 @@ extension MessageList_Tests { let threadReply = "thread reply" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("user adds a thread reply and sends it also to main channel") { - userRobot.replyToMessageInThread(threadReply, alsoSendInChannel: true) + userRobot.sendMessageInThread(threadReply, alsoSendInChannel: true) } WHEN("user goes back to channel and removes thread reply") { userRobot @@ -1013,11 +999,11 @@ extension MessageList_Tests { let threadReply = "thread reply" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("user adds a thread reply") { - userRobot.replyToMessageInThread(threadReply, alsoSendInChannel: false) + userRobot.sendMessageInThread(threadReply, alsoSendInChannel: false) } WHEN("user removes the thread reply") { userRobot.deleteMessage() @@ -1028,7 +1014,7 @@ extension MessageList_Tests { AND("user observes a thread reply count button in channel") { userRobot .tapOnBackButton() - .assertThreadReplyCountButton() + .assertThreadReplyCountButton(replies: 1) } } @@ -1038,7 +1024,7 @@ extension MessageList_Tests { let message = "test message" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } WHEN("user sends the message: '\(message)'") { @@ -1058,7 +1044,7 @@ extension MessageList_Tests { let message = "test message" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } WHEN("participant sends the message: '\(message)'") { @@ -1068,7 +1054,7 @@ extension MessageList_Tests { userRobot.assertMessage(message) } AND("participant hard-deletes the message: '\(message)'") { - participantRobot.wait(2).deleteMessage(hard: true) + participantRobot.deleteMessage(hard: true) } THEN("the message is hard-deleted") { userRobot.assertHardDeletedMessage(withText: message) diff --git a/StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift b/StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift index 61af421d4f1..75f7a0b1c13 100644 --- a/StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift +++ b/StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift @@ -7,7 +7,7 @@ import XCTest @available(iOS 15.0, *) class ChannelListScrollTime: StreamTestCase { override func setUpWithError() throws { - mockServerEnabled = false + useMockServer = false switchApiKey = "zcgvnykxsfm8" try super.setUpWithError() } diff --git a/StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift b/StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift index e1cd5aaf177..482f234679c 100644 --- a/StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift +++ b/StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift @@ -8,7 +8,7 @@ import XCTest class MessageListScrollTime: StreamTestCase { func testMessageListScrollTime() { WHEN("user opens the message list") { - backendRobot.generateChannels(count: 1, messagesCount: 100, withAttachments: true) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 100, attachments: true) participantRobot.addReaction(type: .like) userRobot.login().openChannel() } diff --git a/StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift b/StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift index 1bddd8233e8..d36c298b0d2 100644 --- a/StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift @@ -4,33 +4,29 @@ import XCTest -// Requires running a standalone Sinatra server final class PushNotification_Tests: StreamTestCase { - let sender = UserDetails.countDookuName - let message = "How are you? 🙂" - override func setUpWithError() throws { try XCTSkipIf( - ProcessInfo().operatingSystemVersion.majorVersion < 14, - "Push notifications infra does not work on iOS < 14" + ProcessInfo().operatingSystemVersion.majorVersion < 17, + "Run push notification tests only on iOS 17 and above." ) try super.setUpWithError() - assertMockServer() } override func tearDownWithError() throws { - if ProcessInfo().operatingSystemVersion.majorVersion >= 14 { + if ProcessInfo().operatingSystemVersion.majorVersion >= 17 { try super.tearDownWithError() } } - + func test_pushNotificationFromMessageList() throws { linkToScenario(withId: 95) GIVEN("user goes to message list") { userRobot.login().openChannel() } - checkHappyPath(message: message, sender: sender) + + checkHappyPath() } func test_pushNotificationFromChannelList() throws { @@ -42,84 +38,43 @@ final class PushNotification_Tests: StreamTestCase { .openChannel() // this is required to let the mock server know .tapOnBackButton() // which channel to use for push notifications } - checkHappyPath(message: message, sender: sender) + checkHappyPath() } func test_pushNotification_optionalValuesEqualToNil() throws { linkToScenario(withId: 27) - mockPushNotification(body: message) - GIVEN("user goes to message list") { userRobot.login().openChannel() } - checkHappyPath(message: message, sender: app.label.uppercased()) + checkHappyPath(title: nil, rest: "null") } func test_pushNotification_optionalValuesAreEmpty() throws { linkToScenario(withId: 293) - mockPushNotification( - body: message, - title: "", - badge: 0, - mutableContent: 0, - category: "", - type: "", - sender: "", - version: "", - messageId: "", - cid: "" - ) - GIVEN("user goes to message list") { userRobot.login().openChannel() } - checkHappyPath(message: message, sender: app.label.uppercased()) + checkHappyPath(title: nil, rest: "empty") } func test_pushNotification_optionalValuesContainIncorrectType() throws { linkToScenario(withId: 294) - mockPushNotification( - body: message, - title: 42, - badge: "test", - mutableContent: "test", - category: 42, - type: 42, - sender: 42, - version: 42, - messageId: 42, - cid: 42 - ) - GIVEN("user goes to message list") { userRobot.login().openChannel() } - checkHappyPath(message: message, sender: app.label.uppercased()) + checkHappyPath(title: nil, rest: "incorrect_type") } func test_pushNotification_optionalValuesContainIncorrectData() throws { linkToScenario(withId: 295) - mockPushNotification( - body: message, - title: -1, - badge: -1, - mutableContent: -1, - category: "test", - type: "test", - sender: "test", - version: "test", - messageId: "test", - cid: "test" - ) - GIVEN("user goes to message list") { userRobot.login().openChannel() } - checkHappyPath(message: message, sender: app.label.uppercased()) + checkHappyPath(rest: "incorrect_data") } func test_pushNotification_requiredValuesAreInvalid() throws { @@ -131,146 +86,87 @@ final class PushNotification_Tests: StreamTestCase { AND("user goes to background") { deviceRobot.moveApplication(to: .background) } - - mockPushNotification(body: nil) WHEN("participant sends a message (push body param is nil)") { - participantRobot.wait(2).sendMessage( - "\(message)_0", - withPushNotification: true, - bundleIdForPushNotification: app.bundleId() - ) + let message = "null" + participantRobot + .sleep(1) + .sendMessage(message) + .sendPushNotification( + title: message, + body: message, + bundleId: app.bundleId(), + rest: message + ) } THEN("user does not receive a push notification") { userRobot.assertPushNotificationDoesNotAppear() } - - mockPushNotification(body: "") WHEN("participant sends a message (push body param is empty)") { - participantRobot.sendMessage( - "\(message)_1", - withPushNotification: true, - bundleIdForPushNotification: app.bundleId() - ) + let message = "empty" + participantRobot + .sendMessage(message) + .sendPushNotification( + title: message, + body: message, + bundleId: app.bundleId(), + rest: message + ) } THEN("user does not receive a push notification") { userRobot.assertPushNotificationDoesNotAppear() } - - mockPushNotification(body: 42) WHEN("participant sends a message (push body param contains incorrect type)") { - participantRobot.sendMessage( - "\(message)_2", - withPushNotification: true, - bundleIdForPushNotification: app.bundleId() - ) + let message = "42" + participantRobot + .sendMessage(message) + .sendPushNotification( + title: message, + body: message, + bundleId: app.bundleId(), + rest: "incorrect_type" + ) } THEN("user does not receive a push notification") { userRobot.assertPushNotificationDoesNotAppear() } - WHEN("user comes back to foreground") { deviceRobot.moveApplication(to: .foreground) } THEN("message list updates") { userRobot - .assertMessage("\(message)_0", at: 2) - .assertMessage("\(message)_1", at: 1) - .assertMessage("\(message)_2", at: 0) - } - } - - func test_appIconBadge() throws { - linkToScenario(withId: 292) - - throw XCTSkip("[CIS-2164] The test app is not yet ready for this test") - - GIVEN("user goes to message list") { - userRobot.login().openChannel() + .assertMessage("null", at: 2) + .assertMessage("empty", at: 1) + .assertMessage("42", at: 0) } - WHEN("user goes to background") { - deviceRobot.moveApplication(to: .background) - } - AND("participant sends a message") { - participantRobot.wait(2).sendMessage( - message, - withPushNotification: true, - bundleIdForPushNotification: app.bundleId() - ) - } - THEN("user observes an icon badge") { - userRobot.assertAppIconBadge(shouldBeVisible: true) - } - AND("user receives a push notification") { - userRobot.assertPushNotification(withText: message, from: sender) - } - WHEN("user taps on the push notification") { - userRobot.tapOnPushNotification().assertMessage(message) - } - THEN("message list updates") { - userRobot.assertMessage(message) - } - AND("user goes to background") { - deviceRobot.moveApplication(to: .background) - } - THEN("app icon badge should not be visible") { - userRobot.assertAppIconBadge(shouldBeVisible: false) - } - } - - func mockPushNotification( - body: Any?, - title: Any? = nil, - badge: Any? = nil, - mutableContent: Any? = nil, - category: Any? = nil, - type: Any? = nil, - sender: Any? = nil, - version: Any? = nil, - messageId: Any? = nil, - cid: Any? = nil - ) { - var json = TestData.toJson(.pushNotification) - - var aps = json[APNSKey.aps] as? [String: Any] - var alert = aps?[APNSKey.alert] as? [String: Any] - alert?[APNSKey.title] = title - alert?[APNSKey.body] = body - aps?[APNSKey.alert] = alert - aps?[APNSKey.badge] = badge - aps?[APNSKey.mutableContent] = mutableContent - aps?[APNSKey.category] = category - json[APNSKey.aps] = aps - - var stream = json[APNSKey.stream] as? [String: Any] - stream?[APNSKey.sender] = sender - stream?[APNSKey.type] = type - stream?[APNSKey.version] = version - stream?[APNSKey.messageId] = messageId - stream?[APNSKey.cid] = cid - json[APNSKey.stream] = stream - - server.pushNotificationPayload = json } - func checkHappyPath(message: String, sender: String) { + func checkHappyPath(title: String? = "Test title", body: String = "Test body", rest: String? = nil) { WHEN("user goes to background") { deviceRobot.moveApplication(to: .background) } AND("participant sends a message") { - participantRobot.wait(2).sendMessage( - message, - withPushNotification: true, - bundleIdForPushNotification: app.bundleId() - ) + participantRobot + .sleep(1) + .sendMessage(body) + .sendPushNotification( + title: title, + body: body, + bundleId: app.bundleId(), + rest: rest + ) } THEN("user receives a push notification") { - userRobot.assertPushNotification(withText: message, from: sender) + if let title { + userRobot.assertPushNotification(title: title, body: body) + } else { + userRobot.assertPushNotification(title: app.label, body: body) + } } WHEN("user taps on the push notification") { userRobot.tapOnPushNotification() } THEN("message list updates") { - userRobot.assertMessage(message) + userRobot.assertMessage(body) } } } diff --git a/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift b/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift index fb1a474a9a7..0e9eb5e869a 100644 --- a/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift @@ -11,17 +11,12 @@ final class QuotedReply_Tests: StreamTestCase { let quotedText = "1" let parentText = "test" let replyText = "quoted reply" - - override func setUpWithError() throws { - try super.setUpWithError() - assertMockServer() - } func test_whenSwipingMessage_thenMessageIsQuotedReply() { linkToScenario(withId: 2096) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } WHEN("user swipes a message") { @@ -40,27 +35,26 @@ final class QuotedReply_Tests: StreamTestCase { let messageCount = 20 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 20) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 20) userRobot.login().openChannel() } WHEN("user adds a quoted reply to participant message") { userRobot .scrollMessageListUp(times: 3) .quoteMessage(replyText, messageCellIndex: messageCount - 1) - .waitForMessageVisibility(at: 0) } THEN("user observes the quote reply in message list") { userRobot - .assertQuotedMessage(replyText: replyText, quotedText: quotedText) .assertScrollToBottomButton(isVisible: false) + .assertQuotedMessage(replyText: replyText, quotedText: quotedText) } WHEN("user taps on a quoted message") { userRobot.tapOnQuotedMessage(quotedText, at: 0) } THEN("user is scrolled up to the quoted message") { userRobot - .assertMessageIsVisible(at: messageCount) .assertScrollToBottomButton(isVisible: true) + .assertMessageIsVisible(at: messageCount) } } @@ -70,25 +64,24 @@ final class QuotedReply_Tests: StreamTestCase { let messageCount = 25 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: messageCount) + backendRobot.generateChannels(channelsCount: 1, messagesCount: messageCount) userRobot.login().openChannel() } WHEN("participant adds a quoted reply") { - participantRobot.quoteMessage(replyText, toLastMessage: false) - userRobot.waitForMessageVisibility(at: 0) + participantRobot.quoteMessage(replyText, last: false) } THEN("user observes the quote reply in message list") { userRobot - .assertQuotedMessage(replyText: replyText, quotedText: quotedText) .assertScrollToBottomButton(isVisible: false) + .assertQuotedMessage(replyText: replyText, quotedText: quotedText) } WHEN("user taps on a quoted message") { userRobot.tapOnQuotedMessage(quotedText, at: 0) } THEN("user is scrolled up to the quoted message") { userRobot - .assertMessageIsVisible(at: messageCount) .assertScrollToBottomButton(isVisible: true) + .assertMessageIsVisible(at: messageCount) } } @@ -96,7 +89,7 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 51) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: messageCount) + backendRobot.generateChannels(channelsCount: 1, messagesCount: messageCount) userRobot.login().openChannel() } WHEN("user adds a quoted reply to participant message") { @@ -107,16 +100,16 @@ final class QuotedReply_Tests: StreamTestCase { } THEN("user observes the quote reply in message list") { userRobot - .assertQuotedMessage(replyText: replyText, quotedText: quotedText) .assertScrollToBottomButton(isVisible: false) + .assertQuotedMessage(replyText: replyText, quotedText: quotedText) } WHEN("user taps on a quoted message") { userRobot.tapOnQuotedMessage(quotedText, at: 0) } THEN("user is scrolled up to the quoted message") { userRobot - .assertMessageIsVisible(at: messageCount) .assertScrollToBottomButton(isVisible: true) + .assertMessageIsVisible(at: messageCount) } } @@ -124,25 +117,24 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 52) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: messageCount) + backendRobot.generateChannels(channelsCount: 1, messagesCount: messageCount) userRobot.login().openChannel() } WHEN("participant adds a quoted reply") { - participantRobot.quoteMessage(replyText, toLastMessage: false) - userRobot.waitForMessageVisibility(at: 0) + participantRobot.quoteMessage(replyText, last: false) } THEN("user observes the quote reply in message list") { userRobot - .assertQuotedMessage(replyText: replyText, quotedText: quotedText) .assertScrollToBottomButton(isVisible: false) + .assertQuotedMessage(replyText: replyText, quotedText: quotedText) } WHEN("user taps on a quoted message") { userRobot.tapOnQuotedMessage(quotedText, at: 0) } THEN("user is scrolled up to the quoted message") { userRobot - .assertMessageIsVisible(at: messageCount) .assertScrollToBottomButton(isVisible: true) + .assertFirstMessageIsVisible(quotedText) } } @@ -150,26 +142,25 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 1568) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: messageCount) + backendRobot.generateChannels(channelsCount: 1, messagesCount: messageCount) userRobot.login().openChannel() } WHEN("participant sends file as a quoted reply") { - participantRobot.uploadAttachment(type: .file, asReplyToFirstMessage: true) - userRobot.waitForMessageVisibility(at: 0) + participantRobot.quoteMessageWithAttachment(type: .file, last: false) } THEN("user observes the quote reply in message list") { userRobot + .assertScrollToBottomButton(isVisible: false) .assertFile(isPresent: true) .assertQuotedMessage(quotedText: quotedText) - .assertScrollToBottomButton(isVisible: false) } WHEN("user taps on a quoted message") { userRobot.tapOnQuotedMessage(quotedText, at: 0) } THEN("user is scrolled up to the quoted message") { userRobot - .assertMessageIsVisible(at: messageCount) .assertScrollToBottomButton(isVisible: true) + .assertFirstMessageIsVisible(quotedText) } } @@ -177,26 +168,25 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 1571) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: messageCount) + backendRobot.generateChannels(channelsCount: 1, messagesCount: messageCount) userRobot.login().openChannel() } WHEN("participant sends giphy as a quoted reply") { - participantRobot.replyWithGiphy(toLastMessage: false) - userRobot.waitForMessageVisibility(at: 0) + participantRobot.quoteMessageWithGiphy(last: false) } THEN("user observes the quote reply in message list") { userRobot + .assertScrollToBottomButton(isVisible: false) .assertGiphyImage() .assertQuotedMessage(quotedText: quotedText) - .assertScrollToBottomButton(isVisible: false) } WHEN("user taps on a quoted message") { userRobot.tapOnQuotedMessage(quotedText, at: 0) } THEN("user is scrolled up to the quoted message") { userRobot - .assertMessageIsVisible(at: messageCount) .assertScrollToBottomButton(isVisible: true) + .assertFirstMessageIsVisible(quotedText) } } @@ -204,7 +194,7 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 108) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("participant adds a quoted reply") { @@ -245,7 +235,7 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 109) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("user adds a quoted reply") { @@ -263,7 +253,7 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 6644) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("user adds a quoted reply") { @@ -286,7 +276,7 @@ final class QuotedReply_Tests: StreamTestCase { let invalidCommand = "invalid command" GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: messageCount) + backendRobot.generateChannels(channelsCount: 1, messagesCount: messageCount) userRobot.login().openChannel() } WHEN("user adds a quoted reply to participant message") { @@ -317,7 +307,12 @@ final class QuotedReply_Tests: StreamTestCase { let replyToMessageIndex = messageCount - 1 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("user adds a quoted reply to participant message in thread") { @@ -348,11 +343,16 @@ final class QuotedReply_Tests: StreamTestCase { let messageCount = 25 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("participant adds a quoted reply") { - participantRobot.quoteMessageInThread(replyText, toLastMessage: false) + participantRobot.quoteMessageInThread(replyText, last: false) } THEN("user observes the quote reply in thread") { userRobot @@ -377,7 +377,12 @@ final class QuotedReply_Tests: StreamTestCase { let replyToMessageIndex = messageCount - 1 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("user adds a quoted reply to participant message in thread") { @@ -407,11 +412,16 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 1934) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("participant adds a quoted reply in thread") { - participantRobot.quoteMessageInThread(replyText, toLastMessage: false) + participantRobot.quoteMessageInThread(replyText, last: false) } THEN("user observes the quote reply in thread") { userRobot @@ -434,11 +444,16 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 1935) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("participant sends file as a quoted reply in thread") { - participantRobot.uploadAttachment(type: .file, asReplyToFirstMessage: true, inThread: true) + participantRobot.quoteMessageWithAttachmentInThread(type: .file, last: false) } THEN("user observes the quote reply in thread") { userRobot @@ -456,21 +471,22 @@ final class QuotedReply_Tests: StreamTestCase { .assertScrollToBottomButton(isVisible: true) } } - + + // NOTE: There used to be a problem with tapping on a Send button on iOS > 16 func test_quotedReplyNotInList_whenParticipantAddsQuotedReply_Giphy_InThread() throws { linkToScenario(withId: 1936) - - try XCTSkipIf( - ProcessInfo().operatingSystemVersion.majorVersion > 16, - "The test cannot tap on a `Send` button on iOS 17" - ) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("participant sends giphy as a quoted reply") { - participantRobot.replyWithGiphyInThread(toLastMessage: false) + participantRobot.quoteMessageWithGiphyInThread(last: false) } THEN("user observes the quote reply in thread") { userRobot @@ -496,7 +512,12 @@ final class QuotedReply_Tests: StreamTestCase { let replyToMessageIndex = messageCount - 1 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel() } THEN("user adds a quoted reply in thread") { @@ -523,19 +544,26 @@ final class QuotedReply_Tests: StreamTestCase { func test_threadRepliesCount() { linkToScenario(withId: 1938) + + let repliesCount = 5 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: repliesCount, + messagesText: parentText + ) userRobot.login().openChannel() } THEN("user observes the number of replies in the channel") { - userRobot.assertThreadReplyCountButton(replies: messageCount) + userRobot.assertThreadReplyCountButton(replies: repliesCount) } - WHEN("user opens the tread and scrolls up") { - userRobot.openThread().scrollMessageListUp(times: 3) + WHEN("user opens the tread") { + userRobot.openThread() } AND("user observes the number of replies in the thread") { - userRobot.assertThreadRepliesCountLabel(messageCount) + userRobot.assertThreadRepliesCountLabel(repliesCount) } } @@ -545,7 +573,12 @@ final class QuotedReply_Tests: StreamTestCase { let quotedText = String(messageCount) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("participant adds a quoted reply in thread and also in channel") { @@ -568,7 +601,12 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 1964) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: 1) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: 1, + messagesText: parentText + ) userRobot.login().openChannel() } AND("participant adds a quoted reply") { @@ -586,11 +624,15 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 6645) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + messagesText: parentText + ) userRobot.login().openChannel() } AND("participant sends a message") { - participantRobot.replyToMessageInThread("1") + participantRobot.sendMessageInThread("1") } AND("user adds a quoted reply") { userRobot.openThread().quoteMessage(replyText) @@ -610,7 +652,12 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 1965) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: 1) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: 1, + messagesText: parentText + ) userRobot.login().openChannel() } AND("user adds a quoted reply in thread") { @@ -628,7 +675,7 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 6646) GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messagesCount: 1) + backendRobot.generateChannels(channelsCount: 1, messagesCount: 1) userRobot.login().openChannel() } AND("user adds a quoted reply") { @@ -651,7 +698,12 @@ final class QuotedReply_Tests: StreamTestCase { let replyCount = 30 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: replyCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: replyCount, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("user opens the thread with \(replyCount) replies") { @@ -674,7 +726,12 @@ final class QuotedReply_Tests: StreamTestCase { let pageSize = 25 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: pageSize) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: pageSize, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("user opens the thread with \(pageSize) replies") { @@ -697,7 +754,12 @@ final class QuotedReply_Tests: StreamTestCase { let messageCount = 24 GIVEN("user opens the channel") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel() } WHEN("user opens the thread with \(messageCount) replies") { @@ -712,7 +774,12 @@ final class QuotedReply_Tests: StreamTestCase { linkToScenario(withId: 2000) GIVEN("user opens the thread with \(messageCount) replies") { - backendRobot.generateChannels(count: 1, messageText: parentText, messagesCount: 1, replyCount: messageCount) + backendRobot.generateChannels( + channelsCount: 1, + messagesCount: 1, + repliesCount: messageCount, + messagesText: parentText + ) userRobot.login().openChannel().openThread() } WHEN("user quote replies root message") { diff --git a/StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift b/StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift index b5ab7aa113d..e9cebaa61cf 100644 --- a/StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift @@ -5,12 +5,6 @@ import XCTest final class Reactions_Tests: StreamTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - addTags([.coreFeatures]) - assertMockServer() - } - func test_addsReaction() throws { linkToScenario(withId: 41) diff --git a/StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift b/StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift index 0f8d175a1d9..f516534ab5a 100644 --- a/StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift +++ b/StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift @@ -11,12 +11,6 @@ final class SlowMode_Tests: StreamTestCase { let replyMessage = "reply message" let editedMessage = "edited message" - override func setUpWithError() throws { - try super.setUpWithError() - addTags([.slowMode]) - assertMockServer() - } - func test_slowModeIsActiveAndCooldownIsShown_whenNewMessageIsSent() { linkToScenario(withId: 186) @@ -134,7 +128,7 @@ final class SlowMode_Tests: StreamTestCase { GIVEN("user opens a channel") { backendRobot - .generateChannels(count: 1, messagesCount: 1) + .generateChannels(channelsCount: 1, messagesCount: 1) .setCooldown(enabled: true, duration: cooldownDuration) userRobot .login() diff --git a/StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift b/StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift deleted file mode 100644 index c29a06dd70c..00000000000 --- a/StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -extension StreamTestCase { - enum Tags: String { - case coreFeatures = "Core Features" - case slowMode = "Slow Mode" - case offlineSupport = "Offline Support" - case messageDeliveryStatus = "Message Delivery Status" - } - - func addTags(_ tags: [Tags]) { - addTagsToScenario(tags.map { $0.rawValue }) - } -} diff --git a/StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift b/StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift deleted file mode 100644 index 5fd78f6b30b..00000000000 --- a/StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import XCTest - -final class UserDetails_Tests: StreamTestCase { - override func setUpWithError() throws { - try super.setUpWithError() - assertMockServer() - } - - func test_userDetails() throws { - linkToScenario(withId: 1043) - - WHEN("user logs in") { - userRobot.login() - } - THEN("server receives the correct user details") { - userRobot - .assertConnectionStatus(.connected) - .assertUserDetails(server.userDetails) - } - } -} diff --git a/TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift b/TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift deleted file mode 100644 index 6e1190b4d61..00000000000 --- a/TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -public extension Dictionary { - func jsonToString(prettyPrinted: Bool = false) -> String { - var options: JSONSerialization.WritingOptions = [] - if prettyPrinted { - options = JSONSerialization.WritingOptions.prettyPrinted - } - - do { - let data = try JSONSerialization.data(withJSONObject: self, options: options) - if let string = String(data: data, encoding: String.Encoding.utf8) { - return string - } - } catch { - print(error) - } - - return "" - } -} diff --git a/TestTools/StreamChatTestMockServer/Extensions/String.swift b/TestTools/StreamChatTestMockServer/Extensions/String.swift deleted file mode 100644 index d2b0f92268b..00000000000 --- a/TestTools/StreamChatTestMockServer/Extensions/String.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import XCTest - -public extension String { - var json: [String: Any] { - try! JSONSerialization.jsonObject( - with: Data(self.utf8), - options: .mutableContainers - ) as! [String: Any] - } - - func replace(_ target: String, to: String) -> String { - replacingOccurrences( - of: target, - with: to, - options: NSString.CompareOptions.literal, - range: nil - ) - } - - var html: Self { - self.isEmpty ? "" : "

\(self)

\n" - } -} - -public extension Substring { - func capitalizingFirstLetter() -> Substring { - prefix(1).capitalized + dropFirst() - } - - mutating func capitalizeFirstLetter() { - self = self.capitalizingFirstLetter() - } -} diff --git a/TestTools/StreamChatTestMockServer/Extensions/Swifter.swift b/TestTools/StreamChatTestMockServer/Extensions/Swifter.swift deleted file mode 100644 index 21fd1b7b844..00000000000 --- a/TestTools/StreamChatTestMockServer/Extensions/Swifter.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -public extension HttpServer { - func register(_ path: String, execution: @escaping ((HttpRequest) throws -> HttpResponse?)) { - self[path] = { [weak self] in - self?.delayServerResponseIfNeeded() - - do { - return try execution($0) ?? .badRequest(nil) - } catch { - return .badRequest(nil) - } - } - } - - private func delayServerResponseIfNeeded() { - let delay = StreamMockServer.httpResponseDelay - if delay > 0.0 { - Thread.sleep(forTimeInterval: delay) - } - } -} diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_add_member.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_add_member.json deleted file mode 100644 index 32f7c85cdcf..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_add_member.json +++ /dev/null @@ -1,243 +0,0 @@ -{ - "channel": { - "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "type": "messaging", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "last_message_at": "2025-10-15T11:22:21.804306Z", - "created_at": "2025-10-15T11:22:20.27144Z", - "updated_at": "2025-10-15T11:22:20.27144Z", - "created_by": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "canReceiveMessages": false, - "team": "test", - "type": "team", - "birthland": "Tatooine", - "custom_extra_data_key": true - }, - "frozen": false, - "disabled": false, - "member_count": 4, - "config": { - "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-07-28T15:20:21.098826Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "quotes": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "custom_events": true, - "push_notifications": true, - "reminders": true, - "mark_messages_pending": false, - "polls": true, - "user_message_reminders": false, - "shared_locations": true, - "count_messages": false, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "AI", - "automod_behavior": "block", - "blocklist": "profanity_en_2020_v1", - "blocklist_behavior": "block", - "automod_thresholds": { - "explicit": { - "flag": 0.85, - "block": 0.9 - }, - "spam": { - "flag": 0.85, - "block": 0.9 - }, - "toxic": { - "flag": 0.85, - "block": 0.9 - } - }, - "skip_last_msg_update_for_system_msgs": false, - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "own_capabilities": [ - "ban-channel-members", - "cast-poll-vote", - "connect-events", - "create-attachment", - "delete-any-message", - "delete-channel", - "delete-own-message", - "flag-message", - "join-channel", - "leave-channel", - "mute-channel", - "pin-message", - "query-poll-votes", - "quote-message", - "read-events", - "search-messages", - "send-custom-events", - "send-links", - "send-message", - "send-poll", - "send-reaction", - "send-reply", - "send-restricted-visibility-message", - "send-typing-events", - "set-channel-cooldown", - "share-location", - "skip-slow-mode", - "typing-events", - "update-any-message", - "update-channel", - "update-channel-members", - "update-own-message", - "update-thread", - "upload-file" - ], - "hidden": false, - "name": "Sync Mock Server" - }, - "members": [ - { - "user_id": "count_dooku", - "user": { - "id": "count_dooku", - "name": "Count Dooku", - "image": "https://vignette.wikia.nocookie.net/starwars/images/b/b8/Dooku_Headshot.jpg", - "language": "", - "role": "user", - "teams": [], - "created_at": "2024-04-22T06:42:08.562992Z", - "updated_at": "2025-04-23T13:02:29.974824Z", - "banned": false, - "online": false, - "last_active": "2025-10-01T07:12:24.084195Z", - "blocked_user_ids": [], - "avg_response_time": 531, - "birthland": "Serenno" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "member", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "lando_calrissian", - "user": { - "id": "lando_calrissian", - "name": "Lando Calrissian", - "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T19:06:08.890459Z", - "updated_at": "2025-04-16T17:23:28.189521Z", - "banned": false, - "online": false, - "last_active": "2025-10-12T03:39:43.111316Z", - "blocked_user_ids": [], - "avg_response_time": 1165670, - "birthland": "Socorro" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "birthland": "Tatooine", - "custom_extra_data_key": true, - "type": "team", - "canBeAddedToGroups": true, - "canReceiveMessages": false, - "team": "test", - "pando": "{\"speciality\":\"ios engineer\"}" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "owner", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "leia_organa", - "user": { - "id": "leia_organa", - "name": "Leia Organa", - "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-10-01T16:49:27.725672Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:21:40.020397141Z", - "blocked_user_ids": [], - "avg_response_time": 638602, - "is_moderator": true, - "birthland": "Polis Massa" - }, - "status": "member", - "created_at": "2025-10-15T11:22:22.248971Z", - "updated_at": "2025-10-15T11:22:22.248971Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - } - ], - "duration": "36.22ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_attachment.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_attachment.json deleted file mode 100644 index 21e8fc65c39..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_attachment.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "file": "https://frankfurt.stream-io-cdn.com/102399/images/7a78629b-d178-4bb4-9e6d-ca28484c3130.yoda.jpg?Key-Pair-Id=APKAIHG36VEWPDULE23Q&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9mcmFua2Z1cnQuc3RyZWFtLWlvLWNkbi5jb20vMTAyMzk5L2ltYWdlcy83YTc4NjI5Yi1kMTc4LTRiYjQtOWU2ZC1jYTI4NDg0YzMxMzAueW9kYS5qcGc~Km9oPTAqb3c9MCoiLCJDb25kaXRpb24iOnsiRGF0ZUxlc3NUaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE3NjE3MzY5NDJ9fX1dfQ__&Signature=LI6nidy~xUcwmRDDyFvHiSnpuHfDei7J~FmMdbG1m1uuXAKsyJwue7isX-1tOaKV~hvWaInq-nn4-s0-OkdgwjYw7TsRlJlgowCrGm5NSFmPdkSdOZerNGVfVJEW0zV0FgRkHXrM-K9wS~sJx6x-rDkJs9YEY4CWpwlhhI-jICVpLHaNHaUT63fDtKbKoGU3JCzPjtlI9vjoHAG~nr9EahyizFoxW5~i7K-uAC2tJj15JKeqAw4nWDmPU0qV-NxEH2CGn3Otcp8vrskVq4lqW3B4pGgwVnsopfYBuLrZlN6AuiSwTQos5s6~9iVXlFjGnGOoWbgivHjV-9aiR1nMgg__&oh=0&ow=0", - "duration": "131.42ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_creation.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_creation.json deleted file mode 100644 index c115f5640e6..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_creation.json +++ /dev/null @@ -1,319 +0,0 @@ -{ - "channel": { - "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "type": "messaging", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:20.27144Z", - "updated_at": "2025-10-15T11:22:20.27144Z", - "created_by": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "custom_extra_data_key": true, - "canBeAddedToGroups": true, - "team": "test", - "canReceiveMessages": false, - "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" - }, - "frozen": false, - "disabled": false, - "member_count": 3, - "config": { - "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-07-28T15:20:21.098826Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "quotes": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "custom_events": true, - "push_notifications": true, - "reminders": true, - "mark_messages_pending": false, - "polls": true, - "user_message_reminders": false, - "shared_locations": true, - "count_messages": false, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "AI", - "automod_behavior": "block", - "blocklist": "profanity_en_2020_v1", - "blocklist_behavior": "block", - "automod_thresholds": { - "explicit": { - "flag": 0.85, - "block": 0.9 - }, - "spam": { - "flag": 0.85, - "block": 0.9 - }, - "toxic": { - "flag": 0.85, - "block": 0.9 - } - }, - "skip_last_msg_update_for_system_msgs": false, - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "own_capabilities": [ - "ban-channel-members", - "cast-poll-vote", - "connect-events", - "create-attachment", - "delete-any-message", - "delete-channel", - "delete-own-message", - "flag-message", - "join-channel", - "leave-channel", - "mute-channel", - "pin-message", - "query-poll-votes", - "quote-message", - "read-events", - "search-messages", - "send-custom-events", - "send-links", - "send-message", - "send-poll", - "send-reaction", - "send-reply", - "send-restricted-visibility-message", - "send-typing-events", - "set-channel-cooldown", - "share-location", - "skip-slow-mode", - "typing-events", - "update-any-message", - "update-channel", - "update-channel-members", - "update-own-message", - "update-thread", - "upload-file" - ], - "hidden": false, - "blocked": false, - "name": "Sync Mock Server" - }, - "messages": [], - "pinned_messages": [], - "watcher_count": 1, - "read": [ - { - "user": { - "id": "count_dooku", - "name": "Count Dooku", - "image": "https://vignette.wikia.nocookie.net/starwars/images/b/b8/Dooku_Headshot.jpg", - "language": "", - "role": "user", - "teams": [], - "created_at": "2024-04-22T06:42:08.562992Z", - "updated_at": "2025-04-23T13:02:29.974824Z", - "banned": false, - "online": false, - "last_active": "2025-10-01T07:12:24.084195Z", - "blocked_user_ids": [], - "avg_response_time": 531, - "birthland": "Serenno" - }, - "last_read": "2025-10-15T11:22:20.320400817Z", - "unread_messages": 0 - }, - { - "user": { - "id": "lando_calrissian", - "name": "Lando Calrissian", - "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T19:06:08.890459Z", - "updated_at": "2025-04-16T17:23:28.189521Z", - "banned": false, - "online": false, - "last_active": "2025-10-12T03:39:43.111316Z", - "blocked_user_ids": [], - "avg_response_time": 1165670, - "birthland": "Socorro" - }, - "last_read": "2025-10-15T11:22:20.320400817Z", - "unread_messages": 0 - }, - { - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "canReceiveMessages": false, - "birthland": "Tatooine", - "custom_extra_data_key": true, - "type": "team", - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "team": "test" - }, - "last_read": "2025-10-15T11:22:20.320400817Z", - "unread_messages": 0 - } - ], - "members": [ - { - "user_id": "count_dooku", - "user": { - "id": "count_dooku", - "name": "Count Dooku", - "image": "https://vignette.wikia.nocookie.net/starwars/images/b/b8/Dooku_Headshot.jpg", - "language": "", - "role": "user", - "teams": [], - "created_at": "2024-04-22T06:42:08.562992Z", - "updated_at": "2025-04-23T13:02:29.974824Z", - "banned": false, - "online": false, - "last_active": "2025-10-01T07:12:24.084195Z", - "blocked_user_ids": [], - "avg_response_time": 531, - "birthland": "Serenno" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "member", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "lando_calrissian", - "user": { - "id": "lando_calrissian", - "name": "Lando Calrissian", - "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T19:06:08.890459Z", - "updated_at": "2025-04-16T17:23:28.189521Z", - "banned": false, - "online": false, - "last_active": "2025-10-12T03:39:43.111316Z", - "blocked_user_ids": [], - "avg_response_time": 1165670, - "birthland": "Socorro" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "pando": "{\"speciality\":\"ios engineer\"}", - "team": "test", - "canBeAddedToGroups": true, - "type": "team", - "canReceiveMessages": false, - "birthland": "Tatooine", - "custom_extra_data_key": true - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "owner", - "channel_role": "channel_member", - "notifications_muted": false - } - ], - "membership": { - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "canReceiveMessages": false, - "birthland": "Tatooine", - "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "custom_extra_data_key": true, - "team": "test", - "canBeAddedToGroups": true - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "owner", - "channel_role": "channel_member", - "notifications_muted": false - }, - "threads": [], - "duration": "116.90ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_removal.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_removal.json deleted file mode 100644 index 6002aa23601..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_removal.json +++ /dev/null @@ -1,207 +0,0 @@ -{ - "duration": "22.64ms", - "channel": { - "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "type": "messaging", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:20.27144Z", - "updated_at": "2025-10-15T11:22:24.61465Z", - "deleted_at": "2025-10-15T11:22:24.825752Z", - "created_by": null, - "frozen": false, - "disabled": false, - "members": [ - { - "user_id": "count_dooku", - "user": { - "id": "count_dooku", - "name": "Count Dooku", - "image": "https://vignette.wikia.nocookie.net/starwars/images/b/b8/Dooku_Headshot.jpg", - "language": "", - "role": "user", - "teams": [], - "created_at": "2024-04-22T06:42:08.562992Z", - "updated_at": "2025-04-23T13:02:29.974824Z", - "banned": false, - "online": false, - "last_active": "2025-10-01T07:12:24.084195Z", - "blocked_user_ids": [], - "avg_response_time": 531, - "birthland": "Serenno" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "member", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "lando_calrissian", - "user": { - "id": "lando_calrissian", - "name": "Lando Calrissian", - "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T19:06:08.890459Z", - "updated_at": "2025-04-16T17:23:28.189521Z", - "banned": false, - "online": false, - "last_active": "2025-10-12T03:39:43.111316Z", - "blocked_user_ids": [], - "avg_response_time": 1165670, - "birthland": "Socorro" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "team": "test", - "birthland": "Tatooine", - "type": "team", - "canReceiveMessages": false, - "custom_extra_data_key": true - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "owner", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "leia_organa", - "user": { - "id": "leia_organa", - "name": "Leia Organa", - "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-10-01T16:49:27.725672Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:21:40.020397141Z", - "blocked_user_ids": [], - "avg_response_time": 638602, - "birthland": "Polis Massa", - "is_moderator": true - }, - "status": "member", - "created_at": "2025-10-15T11:22:22.248971Z", - "updated_at": "2025-10-15T11:22:22.248971Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - } - ], - "config": { - "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-07-28T15:20:21.098826Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "quotes": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "custom_events": true, - "push_notifications": true, - "reminders": true, - "mark_messages_pending": false, - "polls": true, - "user_message_reminders": false, - "shared_locations": true, - "count_messages": false, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "AI", - "automod_behavior": "block", - "blocklist": "profanity_en_2020_v1", - "blocklist_behavior": "block", - "automod_thresholds": { - "explicit": { - "flag": 0.85, - "block": 0.9 - }, - "spam": { - "flag": 0.85, - "block": 0.9 - }, - "toxic": { - "flag": 0.85, - "block": 0.9 - } - }, - "skip_last_msg_update_for_system_msgs": false, - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "truncated_at": "2025-10-15T11:22:24.825752Z", - "truncated_by": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "canBeAddedToGroups": true, - "type": "team", - "team": "test", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "custom_extra_data_key": true, - "canReceiveMessages": false - }, - "message_count": 0 - } -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channels.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channels.json deleted file mode 100644 index 9687422dcfb..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channels.json +++ /dev/null @@ -1,322 +0,0 @@ -{ - "channels": [ - { - "channel": { - "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "type": "messaging", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:20.27144Z", - "updated_at": "2025-10-15T11:22:20.27144Z", - "created_by": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "canReceiveMessages": false, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "custom_extra_data_key": true, - "type": "team", - "team": "test", - "canBeAddedToGroups": true - }, - "frozen": false, - "disabled": false, - "member_count": 3, - "config": { - "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-07-28T15:20:21.098826Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "quotes": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "custom_events": true, - "push_notifications": true, - "reminders": true, - "mark_messages_pending": false, - "polls": true, - "user_message_reminders": false, - "shared_locations": true, - "count_messages": false, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "AI", - "automod_behavior": "block", - "blocklist": "profanity_en_2020_v1", - "blocklist_behavior": "block", - "automod_thresholds": { - "explicit": { - "flag": 0.85, - "block": 0.9 - }, - "spam": { - "flag": 0.85, - "block": 0.9 - }, - "toxic": { - "flag": 0.85, - "block": 0.9 - } - }, - "skip_last_msg_update_for_system_msgs": false, - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "own_capabilities": [ - "ban-channel-members", - "cast-poll-vote", - "connect-events", - "create-attachment", - "delete-any-message", - "delete-channel", - "delete-own-message", - "flag-message", - "join-channel", - "leave-channel", - "mute-channel", - "pin-message", - "query-poll-votes", - "quote-message", - "read-events", - "search-messages", - "send-custom-events", - "send-links", - "send-message", - "send-poll", - "send-reaction", - "send-reply", - "send-restricted-visibility-message", - "send-typing-events", - "set-channel-cooldown", - "share-location", - "skip-slow-mode", - "typing-events", - "update-any-message", - "update-channel", - "update-channel-members", - "update-own-message", - "update-thread", - "upload-file" - ], - "hidden": false, - "blocked": false, - "name": "Sync Mock Server" - }, - "messages": [], - "pinned_messages": [], - "watcher_count": 1, - "read": [ - { - "user": { - "id": "count_dooku", - "name": "Count Dooku", - "image": "https://vignette.wikia.nocookie.net/starwars/images/b/b8/Dooku_Headshot.jpg", - "language": "", - "role": "user", - "teams": [], - "created_at": "2024-04-22T06:42:08.562992Z", - "updated_at": "2025-04-23T13:02:29.974824Z", - "banned": false, - "online": false, - "last_active": "2025-10-01T07:12:24.084195Z", - "blocked_user_ids": [], - "avg_response_time": 531, - "birthland": "Serenno" - }, - "unread_messages": 0, - "last_read": "2025-10-15T11:22:20Z" - }, - { - "user": { - "id": "lando_calrissian", - "name": "Lando Calrissian", - "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T19:06:08.890459Z", - "updated_at": "2025-04-16T17:23:28.189521Z", - "banned": false, - "online": false, - "last_active": "2025-10-12T03:39:43.111316Z", - "blocked_user_ids": [], - "avg_response_time": 1165670, - "birthland": "Socorro" - }, - "unread_messages": 0, - "last_read": "2025-10-15T11:22:20Z" - }, - { - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "team": "test", - "canBeAddedToGroups": true, - "custom_extra_data_key": true, - "type": "team", - "canReceiveMessages": false, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" - }, - "unread_messages": 0, - "last_read": "2025-10-15T11:22:20Z" - } - ], - "members": [ - { - "user_id": "count_dooku", - "user": { - "id": "count_dooku", - "name": "Count Dooku", - "image": "https://vignette.wikia.nocookie.net/starwars/images/b/b8/Dooku_Headshot.jpg", - "language": "", - "role": "user", - "teams": [], - "created_at": "2024-04-22T06:42:08.562992Z", - "updated_at": "2025-04-23T13:02:29.974824Z", - "banned": false, - "online": false, - "last_active": "2025-10-01T07:12:24.084195Z", - "blocked_user_ids": [], - "avg_response_time": 531, - "birthland": "Serenno" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "member", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "lando_calrissian", - "user": { - "id": "lando_calrissian", - "name": "Lando Calrissian", - "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T19:06:08.890459Z", - "updated_at": "2025-04-16T17:23:28.189521Z", - "banned": false, - "online": false, - "last_active": "2025-10-12T03:39:43.111316Z", - "blocked_user_ids": [], - "avg_response_time": 1165670, - "birthland": "Socorro" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "team": "test", - "canBeAddedToGroups": true, - "custom_extra_data_key": true, - "type": "team", - "canReceiveMessages": false, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "owner", - "channel_role": "channel_member", - "notifications_muted": false - } - ], - "membership": { - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "pando": "{\"speciality\":\"ios engineer\"}", - "canBeAddedToGroups": true, - "canReceiveMessages": false, - "birthland": "Tatooine", - "custom_extra_data_key": true, - "team": "test", - "type": "team" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "channel_role": "channel_member", - "notifications_muted": false - }, - "threads": [] - } - ], - "duration": "328.53ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_events.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_events.json deleted file mode 100644 index a753dbb4339..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_events.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "event": { - "type": "typing.start", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_type": "messaging", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "birthland": "Tatooine", - "type": "team", - "canBeAddedToGroups": true, - "canReceiveMessages": false, - "team": "test", - "custom_extra_data_key": true, - "pando": "{\"speciality\":\"ios engineer\"}" - }, - "created_at": "2025-10-15T11:22:21.064322094Z" - }, - "duration": "7.74ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_member.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_member.json deleted file mode 100644 index 1242b8f63b9..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_member.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "user_id": "leia_organa", - "user": { - "id": "leia_organa", - "role": "user", - "created_at": "2020-12-07T11:37:33.78879Z", - "updated_at": "2022-04-20T16:07:59.40095Z", - "last_active": "2022-05-02T15:03:25.076408116Z", - "banned": false, - "online": true, - "name": "Leia Organa", - "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "birthland": "Polis Massa", - "AdditionalProperties": {} - }, - "created_at": "2022-05-02T15:16:22.459264Z", - "updated_at": "2022-05-02T15:16:22.459264Z", - "banned": false, - "shadow_banned": false, - "role": "member", - "channel_role": "channel_member" -} diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message.json deleted file mode 100644 index 9e6e84033fe..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "message": { - "id": "dc702391-84b6-44af-aa67-c349d2e72441", - "text": "Test", - "html": "

Test

\n", - "type": "regular", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "custom_extra_data_key": true, - "canReceiveMessages": false, - "team": "test", - "canBeAddedToGroups": true, - "birthland": "Tatooine" - }, - "member": { - "channel_role": "channel_member" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "deleted_reply_count": 0, - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:21.804306Z", - "updated_at": "2025-10-15T11:22:21.804306Z", - "shadowed": false, - "mentioned_users": [], - "silent": false, - "pinned": false, - "pinned_at": null, - "pinned_by": null, - "pin_expires": null, - "restricted_visibility": [] - }, - "duration": "647.31ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message_ephemeral.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message_ephemeral.json deleted file mode 100644 index 2ba07b6ab4e..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message_ephemeral.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "message": { - "id": "31d2f09d-e285-4e0f-a561-6aa6adce37b0", - "text": "/giphy Test", - "command": "giphy", - "html": "

/giphy Test

\n", - "type": "ephemeral", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "birthland": "Tatooine", - "team": "test", - "pando": "{\"speciality\":\"ios engineer\"}", - "canBeAddedToGroups": true, - "custom_extra_data_key": true, - "type": "team", - "canReceiveMessages": false - }, - "member": { - "channel_role": "channel_member" - }, - "attachments": [ - { - "type": "giphy", - "title": "Test", - "title_link": "https://giphy.com/gifs/anthonyo-test-delete-RiiVFKow0eymLkTnMP", - "thumb_url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/giphy.gif", - "actions": [ - { - "name": "image_action", - "text": "Send", - "style": "primary", - "type": "button", - "value": "send" - }, - { - "name": "image_action", - "text": "Shuffle", - "style": "default", - "type": "button", - "value": "shuffle" - }, - { - "name": "image_action", - "text": "Cancel", - "style": "default", - "type": "button", - "value": "cancel" - } - ], - "giphy": { - "original": { - "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/giphy.gif", - "width": "268", - "height": "350", - "size": "1865328", - "frames": "49" - }, - "fixed_height": { - "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200.gif", - "width": "154", - "height": "200", - "size": "612190", - "frames": "" - }, - "fixed_height_still": { - "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200_s.gif", - "width": "154", - "height": "200", - "size": "17451", - "frames": "" - }, - "fixed_height_downsampled": { - "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200_d.gif", - "width": "154", - "height": "200", - "size": "75328", - "frames": "" - }, - "fixed_width": { - "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200w.gif", - "width": "200", - "height": "262", - "size": "849838", - "frames": "" - }, - "fixed_width_still": { - "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200w_s.gif", - "width": "200", - "height": "262", - "size": "16788", - "frames": "" - }, - "fixed_width_downsampled": { - "url": "https://media2.giphy.com/media/v1.Y2lkPWM0YjAzNjc1NGxxbmtmdmU5OHFqMGR6a2cwc3B2b2s0cDZ1ZzZub25nMTdkZDJoaCZlcD12MV9naWZzX3NlYXJjaCZjdD1n/RiiVFKow0eymLkTnMP/200w_d.gif", - "width": "200", - "height": "262", - "size": "102260", - "frames": "" - } - } - } - ], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "deleted_reply_count": 0, - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:22.981577Z", - "updated_at": "2025-10-15T11:22:22.981577Z", - "shadowed": false, - "mentioned_users": [], - "silent": false, - "pinned": false, - "pinned_at": null, - "pinned_by": null, - "pin_expires": null, - "restricted_visibility": [], - "args": "Test", - "command_info": { - "name": "Giphy" - } - }, - "duration": "222.97ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_reaction.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_reaction.json deleted file mode 100644 index 3fac69b9319..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_reaction.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "message": { - "id": "dc702391-84b6-44af-aa67-c349d2e72441", - "text": "Test", - "html": "

Test

\n", - "type": "regular", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "team": "test", - "canReceiveMessages": false, - "pando": "{\"speciality\":\"ios engineer\"}", - "canBeAddedToGroups": true, - "birthland": "Tatooine", - "custom_extra_data_key": true, - "type": "team" - }, - "member": { - "channel_role": "channel_member" - }, - "attachments": [], - "latest_reactions": [ - { - "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "custom_extra_data_key": true, - "canReceiveMessages": false, - "team": "test", - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "type": "team", - "birthland": "Tatooine" - }, - "type": "like", - "score": 1, - "created_at": "2025-10-15T11:22:21.987244Z", - "updated_at": "2025-10-15T11:22:21.987244Z" - } - ], - "own_reactions": [ - { - "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "birthland": "Tatooine", - "canReceiveMessages": false, - "team": "test", - "type": "team", - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "custom_extra_data_key": true - }, - "type": "like", - "score": 1, - "created_at": "2025-10-15T11:22:21.987244Z", - "updated_at": "2025-10-15T11:22:21.987244Z" - } - ], - "reaction_counts": { - "like": 1 - }, - "reaction_scores": { - "like": 1 - }, - "reaction_groups": { - "like": { - "count": 1, - "sum_scores": 1, - "first_reaction_at": "2025-10-15T11:22:21.987244Z", - "last_reaction_at": "2025-10-15T11:22:21.987244Z" - } - }, - "reply_count": 0, - "deleted_reply_count": 0, - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:21.804306Z", - "updated_at": "2025-10-15T11:22:22.001363Z", - "shadowed": false, - "mentioned_users": [], - "silent": false, - "pinned": false, - "pinned_at": null, - "pinned_by": null, - "pin_expires": null, - "restricted_visibility": [] - }, - "reaction": { - "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "canBeAddedToGroups": true, - "custom_extra_data_key": true, - "canReceiveMessages": false, - "team": "test", - "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine" - }, - "type": "like", - "score": 1, - "created_at": "2025-10-15T11:22:21.987244Z", - "updated_at": "2025-10-15T11:22:21.987244Z" - }, - "duration": "46.60ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_truncate.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_truncate.json deleted file mode 100644 index 31786af9143..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_truncate.json +++ /dev/null @@ -1,275 +0,0 @@ -{ - "duration": "73.97ms", - "channel": { - "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "type": "messaging", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "last_message_at": "0001-01-01T00:00:00Z", - "created_at": "2025-10-15T11:22:20.27144Z", - "updated_at": "2025-10-15T11:22:24.61465Z", - "created_by": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "pando": "{\"speciality\":\"ios engineer\"}", - "canReceiveMessages": false, - "type": "team", - "canBeAddedToGroups": true, - "birthland": "Tatooine", - "custom_extra_data_key": true, - "team": "test" - }, - "frozen": false, - "disabled": false, - "members": [ - { - "user_id": "count_dooku", - "user": { - "id": "count_dooku", - "name": "Count Dooku", - "image": "https://vignette.wikia.nocookie.net/starwars/images/b/b8/Dooku_Headshot.jpg", - "language": "", - "role": "user", - "teams": [], - "created_at": "2024-04-22T06:42:08.562992Z", - "updated_at": "2025-04-23T13:02:29.974824Z", - "banned": false, - "online": false, - "last_active": "2025-10-01T07:12:24.084195Z", - "blocked_user_ids": [], - "avg_response_time": 531, - "birthland": "Serenno" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "member", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "lando_calrissian", - "user": { - "id": "lando_calrissian", - "name": "Lando Calrissian", - "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T19:06:08.890459Z", - "updated_at": "2025-04-16T17:23:28.189521Z", - "banned": false, - "online": false, - "last_active": "2025-10-12T03:39:43.111316Z", - "blocked_user_ids": [], - "avg_response_time": 1165670, - "birthland": "Socorro" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "pando": "{\"speciality\":\"ios engineer\"}", - "canReceiveMessages": false, - "birthland": "Tatooine", - "custom_extra_data_key": true, - "team": "test", - "type": "team", - "canBeAddedToGroups": true - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "owner", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "leia_organa", - "user": { - "id": "leia_organa", - "name": "Leia Organa", - "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-10-01T16:49:27.725672Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:21:40.020397141Z", - "blocked_user_ids": [], - "avg_response_time": 638602, - "birthland": "Polis Massa", - "is_moderator": true - }, - "status": "member", - "created_at": "2025-10-15T11:22:22.248971Z", - "updated_at": "2025-10-15T11:22:22.248971Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - } - ], - "member_count": 4, - "config": { - "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-07-28T15:20:21.098826Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "quotes": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "custom_events": true, - "push_notifications": true, - "reminders": true, - "mark_messages_pending": false, - "polls": true, - "user_message_reminders": false, - "shared_locations": true, - "count_messages": false, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "AI", - "automod_behavior": "block", - "blocklist": "profanity_en_2020_v1", - "blocklist_behavior": "block", - "automod_thresholds": { - "explicit": { - "flag": 0.85, - "block": 0.9 - }, - "spam": { - "flag": 0.85, - "block": 0.9 - }, - "toxic": { - "flag": 0.85, - "block": 0.9 - } - }, - "skip_last_msg_update_for_system_msgs": false, - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "truncated_at": "2025-10-15T11:22:24.606151Z", - "truncated_by": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "custom_extra_data_key": true, - "team": "test", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "type": "team", - "canBeAddedToGroups": true, - "canReceiveMessages": false - }, - "name": "Sync Mock Server" - }, - "message": { - "id": "5d6d174f-ca82-4bf7-97c1-b484e82339e4", - "text": "Channel truncated", - "html": "

Channel truncated

\n", - "type": "system", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "canReceiveMessages": false, - "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "custom_extra_data_key": true, - "team": "test", - "canBeAddedToGroups": true - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "deleted_reply_count": 0, - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:24.606152Z", - "updated_at": "2025-10-15T11:22:24.606152Z", - "shadowed": false, - "mentioned_users": [], - "silent": false, - "pinned": false, - "pinned_at": null, - "pinned_by": null, - "pin_expires": null, - "restricted_visibility": [] - } -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_unsplash_link.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_unsplash_link.json deleted file mode 100644 index da72a203357..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_unsplash_link.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "message": { - "id": "b3e2003e-e090-4b17-b52c-584c7ff149a6", - "text": "https://unsplash.com/photos/1_2d3MRbI9c", - "html": "

https://unsplash.com/photos/1_2d3MRbI9c

\n", - "type": "regular", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "type": "team", - "canBeAddedToGroups": true, - "birthland": "Tatooine", - "canReceiveMessages": false, - "pando": "{\"speciality\":\"ios engineer\"}", - "custom_extra_data_key": true, - "team": "test" - }, - "member": { - "channel_role": "channel_member" - }, - "attachments": [ - { - "type": "image", - "author_name": "Photo by Joao Branco on Unsplash", - "title": "Photo by Joao Branco on Unsplash", - "title_link": "https://unsplash.com/photos/green-pine-tree-mountain-slope-scenery-1_2d3MRbI9c", - "text": "Download this photo by Joao Branco on Unsplash", - "image_url": "https://images.unsplash.com/photo-1568574728383-06fca083883d?ixid=M3wxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNzYwNDg3Mzg5fA&ixlib=rb-4.1.0&auto=format&fit=crop&q=60&mark=https%3A%2F%2Fimages.unsplash.com%2Fopengraph%2Flogo.png&mark-w=64&mark-align=top%2Cleft&mark-pad=50&h=630&w=1200&crop=faces%2Cedges&blend-w=1&blend=000000&blend-mode=normal&blend-alpha=10", - "thumb_url": "https://images.unsplash.com/photo-1568574728383-06fca083883d?ixid=M3wxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNzYwNDg3Mzg5fA&ixlib=rb-4.1.0&auto=format&fit=crop&q=60&mark=https%3A%2F%2Fimages.unsplash.com%2Fopengraph%2Flogo.png&mark-w=64&mark-align=top%2Cleft&mark-pad=50&h=630&w=1200&crop=faces%2Cedges&blend-w=1&blend=000000&blend-mode=normal&blend-alpha=10", - "og_scrape_url": "https://unsplash.com/photos/1_2d3MRbI9c" - } - ], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "deleted_reply_count": 0, - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:23.80289Z", - "updated_at": "2025-10-15T11:22:23.80289Z", - "shadowed": false, - "mentioned_users": [], - "silent": false, - "pinned": false, - "pinned_at": null, - "pinned_by": null, - "pin_expires": null, - "restricted_visibility": [] - }, - "duration": "274.32ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_youtube_link.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_youtube_link.json deleted file mode 100644 index afdcad2ecb0..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_youtube_link.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "message": { - "id": "10f63aac-6f52-40f0-ad13-2ac8a6dd2784", - "text": "https://youtube.com/watch?v=xOX7MsrbaPY", - "html": "

https://youtube.com/watch?v=xOX7MsrbaPY

\n", - "type": "regular", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "birthland": "Tatooine", - "custom_extra_data_key": true, - "canReceiveMessages": false, - "canBeAddedToGroups": true, - "team": "test", - "type": "team", - "pando": "{\"speciality\":\"ios engineer\"}" - }, - "member": { - "channel_role": "channel_member" - }, - "attachments": [ - { - "type": "video", - "author_name": "Introducing MotionScape", - "title": "Introducing MotionScape: Prototype SwiftUI Animation Easings", - "title_link": "https://www.youtube.com/watch?v=xOX7MsrbaPY", - "text": "MotionScape is your SwiftUI animation's playground as a developer. You can see all animations and their parameters in effect with beautifully designed and handcrafted animation examples. Best of all: directly preview and export your settings as production-ready SwiftUI code that you can use in your apps as-is. Supercharge your apps with animations and get to know how to use them - with MotionScape! Download MotionScape from the Mac AppStore: https://gstrm.io/motionscape-yt Webpage: https://getstream.github.io/motionscape-app/ SwiftUI chat messaging: https://getstream.io/chat/sdk/swiftui/ Chapters: 00:00 Introducing MotionScape 00:31 Adjusting animation parameters 00:54 Supported interpolations 01:03 Previewing animation examples 01:50 Find and download MotionScape", - "asset_url": "https://www.youtube.com/embed/xOX7MsrbaPY", - "og_scrape_url": "https://youtube.com/watch?v=xOX7MsrbaPY" - } - ], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "deleted_reply_count": 0, - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:23.388057Z", - "updated_at": "2025-10-15T11:22:23.388057Z", - "shadowed": false, - "mentioned_users": [], - "silent": false, - "pinned": false, - "pinned_at": null, - "pinned_by": null, - "pin_expires": null, - "restricted_visibility": [] - }, - "duration": "290.31ms" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/push_notification.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/push_notification.json deleted file mode 100644 index 8ea7419f285..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/push_notification.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "aps": { - "alert": { - "title": "New message from {{ sender.name }}", - "body": "{{ truncate message.text 2000 }}" - }, - "badge": 1, - "mutable-content": 1, - "category": "stream.chat" - }, - "stream": { - "sender": "stream.chat", - "type": "message.new", - "version": "v2", - "id": "{{ message.id }}", - "cid": "{{ channel.cid }}" - } -} diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events.json deleted file mode 100644 index 8a0649b4b93..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events.json +++ /dev/null @@ -1,202 +0,0 @@ -{ - "type": "typing.start", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_type": "messaging", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "shadow_banned": false, - "privacy_settings": { - "typing_indicators": { - "enabled": true - }, - "read_receipts": { - "enabled": true - } - }, - "devices": [ - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80ff3d0ff101a3d4f3319e20996b2e33cbe4ff1673632911ba61471d9c7382cf10db29c41676c6a42cc7e4084f0b00b28b450da1fe52a97edb8c67d5690b0d9b600c863c34a34546a649be08e3a975cf", - "created_at": "2025-10-07T13:31:12.581472Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "466ec8f052e4e43f6f429a55907217c289618204ebaf4f7ca8fe52c303b01d05", - "created_at": "2025-10-06T07:05:44.023463Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80dc67313692ff3d512089cd19df179395b298d106b86cd2aedf0edfe1de40cd7caba3025823acccf9173fce2000728459e6751c4c7c96b80415a441d54c690d3f2d1635d26a8d58c0407edff8fcfe34", - "created_at": "2025-09-23T11:14:39.868685Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8013785658480684ee2a2f523bd4d52d6f2fac5e34283f4765362b4d526e5f374a46a0eeff277aee8d94ca872c4cdce2c021fef9cb1c71a4e5b28bba5481cab79c2ff7f111d4a7488c7a28de76736ba6", - "created_at": "2025-09-22T07:33:07.001628Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8075f5f2d91f892d2a16de528dea1fae54d63f9d1c09ca18501547ec8b3880c54766e80e0eb8e10920a8545cf5a08f0048adf01c029a17b69fda5dbc8019733fdb5b4923e80d525ca96bd833f02434c5", - "created_at": "2025-09-16T07:18:39.830014Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "804df9f905b9fd1f2c53e160a5f3f1db34490bea2ecb0fdb6aa99d9f407fd67b477079cc403e15b7424927276ac32b113c9820bb569b99a14d60fef0f5ae24804bea0e20be5da6d724f4c3abbf5cd0cb", - "created_at": "2025-09-15T18:13:06.007999Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8044edf2252ff9f253dada5dbf39f018f454e5bcdaf188501c6d105a4f5740c1872acb619f113596a1cb6f9071667d20e601f134aa0c83db83ce733f458b60c5c1b8712e2808abd4a840ce0347a8ea1a", - "created_at": "2025-09-15T13:32:20.775357Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80fee24033dcaa2b50911104447c596a7490ff86fa6593eb8f0eaca6c699c951ddad5e39d5ce95d3858d12a0169ab126dec8c4fa4f791a723304bf6c3ee415b0fe67e4dd7422e7af1dc49396442946eb", - "created_at": "2025-09-13T16:07:49.949311Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "a23e236960612858446bc7b0e78152f0d03ff977da16a1beec3d4558b51c3377", - "created_at": "2025-09-11T14:55:25.842429Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5c1967db91dfc57b32d12728b8ac7410edc325daae78215430319239bb1b9378", - "created_at": "2025-09-10T10:32:28.865373Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", - "created_at": "2025-09-03T04:32:38.333126Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802e021f067e90a9be03d142b102c30a871ad7e16b41bf469eab29c1e2f7b480f433802b2a3e6585752c09e44ed9f71d858bb61dad135568caaaefdac4536529d3b43812f2077c95b57ff4483fd1ad26", - "created_at": "2025-09-02T16:12:13.231524Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8006075492db346334382095f7c068600bfa16f8216628c938b1e9bdb18eae4288cd1567707a67b9868ae4166495d92d1eb9c20b2984a0fc3bc578013bcf05654598b343fa8d3f807e5c296e8ce61dc1", - "created_at": "2025-09-02T07:14:39.15913Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802eefd1b58c8a23fefe5d181538f0109535ad3c5baabf31f6d7a5f898b8ef0c8386d882aa07c511cf97d28f8291f6f8c46b78d7c6639f5c70f09a69ca6a92aff4d9b007a4f4a89b3ab5b722ffb9c86c", - "created_at": "2025-09-01T11:36:07.581094Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5c7d0b29710fba33189fb659b1cbc2be3491d4f3c4b399b8afefb8bc920103ec", - "created_at": "2025-08-28T11:02:17.709186Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8079dec1832d067a951130384525f5de98ae20fe23d131e584c0028b69bdb60aec0a461b19e2b8886e94a479781a115c703bad0e3726f644e12a812d37bdd2b79b2021cd103688a2424bde9c08c9f2f0", - "created_at": "2025-08-27T05:23:02.591215Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809eb957c901bdbae1f20ee0bebdf84c12b6d3a141ba32b5898e7353a7575ef53f941dba98b544fd7c1a36e10d3b88abe9b65d12b44b2fd93dc4a7a57fa45a79750d8a3106fe0d54b46cebad6ec7cbda", - "created_at": "2025-08-26T15:54:24.98832Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "806541d07bd58f5d897f3cb28994fd47749351fb77f948edb2b140c7c147c21bab5e5f90c6124a138f809cefce008662220436a7031c09224434282393bf76d4ba0f61866ecd5c72807279da20618b0a", - "created_at": "2025-08-22T10:41:07.519822Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "808f6525eea58e6aa49bd81b12abbff5a5e9d5310c626559cc1c306f1b0b08496e27d06c64872046d39edb5c9c89f84f0f8debe6663b54ce1aff83269d13850464ae06d3f293f3d6748b0ebd9a91a2f8", - "created_at": "2025-08-19T00:56:28.078927Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802e7837a85219bea9972172678578d10f683c085da918b90a56a477f6692ccbb52ff7badf3ece11c1d612afed415347dac1a8acfc1dfa51385e2ea53689d43466c84f729b069f10efff86604bc7c40c", - "created_at": "2025-08-12T23:25:49.648064Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809409e7859ebfe71a461ead24d36a911eb5cdbab7184f848b45c44b153e65ff15c4be82066a2d768dc7184ab9b3b0c1b3cb7ec1f0819f231a6d09ad64c33dbfb8d1b919d5fda0474d9f29e3cc2940b7", - "created_at": "2025-08-12T12:58:05.975459Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", - "created_at": "2025-08-11T20:11:02.29744Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "803588facf5946ecfe386d4906321b9fe31fe4181b03bffbbfd4b733c4acd00f9046a16f7beeec102e95273d15c95c15a44a2076ba83ad001bbd77ce3810b6465a7ef92fe299da7038b636e762d6150b", - "created_at": "2025-08-08T11:31:24.887765Z", - "user_id": "luke_skywalker" - } - ], - "invisible": false, - "canReceiveMessages": false, - "team": "test", - "custom_extra_data_key": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "type": "team", - "canBeAddedToGroups": true - }, - "created_at": "2025-10-15T11:22:21.064322094Z" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_channel.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_channel.json deleted file mode 100644 index 6666d35564d..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_channel.json +++ /dev/null @@ -1,1001 +0,0 @@ -{ - "type": "channel.updated", - "created_at": "2025-10-15T11:22:22.269497124Z", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_member_count": 4, - "channel_type": "messaging", - "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel": { - "id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "type": "messaging", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "last_message_at": "2025-10-15T11:22:21.804306Z", - "created_at": "2025-10-15T11:22:20.27144Z", - "updated_at": "2025-10-15T11:22:20.27144Z", - "created_by": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "shadow_banned": false, - "privacy_settings": { - "typing_indicators": { - "enabled": true - }, - "read_receipts": { - "enabled": true - } - }, - "devices": [ - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80ff3d0ff101a3d4f3319e20996b2e33cbe4ff1673632911ba61471d9c7382cf10db29c41676c6a42cc7e4084f0b00b28b450da1fe52a97edb8c67d5690b0d9b600c863c34a34546a649be08e3a975cf", - "created_at": "2025-10-07T13:31:12.581472Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "466ec8f052e4e43f6f429a55907217c289618204ebaf4f7ca8fe52c303b01d05", - "created_at": "2025-10-06T07:05:44.023463Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80dc67313692ff3d512089cd19df179395b298d106b86cd2aedf0edfe1de40cd7caba3025823acccf9173fce2000728459e6751c4c7c96b80415a441d54c690d3f2d1635d26a8d58c0407edff8fcfe34", - "created_at": "2025-09-23T11:14:39.868685Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8013785658480684ee2a2f523bd4d52d6f2fac5e34283f4765362b4d526e5f374a46a0eeff277aee8d94ca872c4cdce2c021fef9cb1c71a4e5b28bba5481cab79c2ff7f111d4a7488c7a28de76736ba6", - "created_at": "2025-09-22T07:33:07.001628Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8075f5f2d91f892d2a16de528dea1fae54d63f9d1c09ca18501547ec8b3880c54766e80e0eb8e10920a8545cf5a08f0048adf01c029a17b69fda5dbc8019733fdb5b4923e80d525ca96bd833f02434c5", - "created_at": "2025-09-16T07:18:39.830014Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "804df9f905b9fd1f2c53e160a5f3f1db34490bea2ecb0fdb6aa99d9f407fd67b477079cc403e15b7424927276ac32b113c9820bb569b99a14d60fef0f5ae24804bea0e20be5da6d724f4c3abbf5cd0cb", - "created_at": "2025-09-15T18:13:06.007999Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8044edf2252ff9f253dada5dbf39f018f454e5bcdaf188501c6d105a4f5740c1872acb619f113596a1cb6f9071667d20e601f134aa0c83db83ce733f458b60c5c1b8712e2808abd4a840ce0347a8ea1a", - "created_at": "2025-09-15T13:32:20.775357Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80fee24033dcaa2b50911104447c596a7490ff86fa6593eb8f0eaca6c699c951ddad5e39d5ce95d3858d12a0169ab126dec8c4fa4f791a723304bf6c3ee415b0fe67e4dd7422e7af1dc49396442946eb", - "created_at": "2025-09-13T16:07:49.949311Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "a23e236960612858446bc7b0e78152f0d03ff977da16a1beec3d4558b51c3377", - "created_at": "2025-09-11T14:55:25.842429Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5c1967db91dfc57b32d12728b8ac7410edc325daae78215430319239bb1b9378", - "created_at": "2025-09-10T10:32:28.865373Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", - "created_at": "2025-09-03T04:32:38.333126Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802e021f067e90a9be03d142b102c30a871ad7e16b41bf469eab29c1e2f7b480f433802b2a3e6585752c09e44ed9f71d858bb61dad135568caaaefdac4536529d3b43812f2077c95b57ff4483fd1ad26", - "created_at": "2025-09-02T16:12:13.231524Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8006075492db346334382095f7c068600bfa16f8216628c938b1e9bdb18eae4288cd1567707a67b9868ae4166495d92d1eb9c20b2984a0fc3bc578013bcf05654598b343fa8d3f807e5c296e8ce61dc1", - "created_at": "2025-09-02T07:14:39.15913Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802eefd1b58c8a23fefe5d181538f0109535ad3c5baabf31f6d7a5f898b8ef0c8386d882aa07c511cf97d28f8291f6f8c46b78d7c6639f5c70f09a69ca6a92aff4d9b007a4f4a89b3ab5b722ffb9c86c", - "created_at": "2025-09-01T11:36:07.581094Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5c7d0b29710fba33189fb659b1cbc2be3491d4f3c4b399b8afefb8bc920103ec", - "created_at": "2025-08-28T11:02:17.709186Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8079dec1832d067a951130384525f5de98ae20fe23d131e584c0028b69bdb60aec0a461b19e2b8886e94a479781a115c703bad0e3726f644e12a812d37bdd2b79b2021cd103688a2424bde9c08c9f2f0", - "created_at": "2025-08-27T05:23:02.591215Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809eb957c901bdbae1f20ee0bebdf84c12b6d3a141ba32b5898e7353a7575ef53f941dba98b544fd7c1a36e10d3b88abe9b65d12b44b2fd93dc4a7a57fa45a79750d8a3106fe0d54b46cebad6ec7cbda", - "created_at": "2025-08-26T15:54:24.98832Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "806541d07bd58f5d897f3cb28994fd47749351fb77f948edb2b140c7c147c21bab5e5f90c6124a138f809cefce008662220436a7031c09224434282393bf76d4ba0f61866ecd5c72807279da20618b0a", - "created_at": "2025-08-22T10:41:07.519822Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "808f6525eea58e6aa49bd81b12abbff5a5e9d5310c626559cc1c306f1b0b08496e27d06c64872046d39edb5c9c89f84f0f8debe6663b54ce1aff83269d13850464ae06d3f293f3d6748b0ebd9a91a2f8", - "created_at": "2025-08-19T00:56:28.078927Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802e7837a85219bea9972172678578d10f683c085da918b90a56a477f6692ccbb52ff7badf3ece11c1d612afed415347dac1a8acfc1dfa51385e2ea53689d43466c84f729b069f10efff86604bc7c40c", - "created_at": "2025-08-12T23:25:49.648064Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809409e7859ebfe71a461ead24d36a911eb5cdbab7184f848b45c44b153e65ff15c4be82066a2d768dc7184ab9b3b0c1b3cb7ec1f0819f231a6d09ad64c33dbfb8d1b919d5fda0474d9f29e3cc2940b7", - "created_at": "2025-08-12T12:58:05.975459Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", - "created_at": "2025-08-11T20:11:02.29744Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "803588facf5946ecfe386d4906321b9fe31fe4181b03bffbbfd4b733c4acd00f9046a16f7beeec102e95273d15c95c15a44a2076ba83ad001bbd77ce3810b6465a7ef92fe299da7038b636e762d6150b", - "created_at": "2025-08-08T11:31:24.887765Z", - "user_id": "luke_skywalker" - } - ], - "invisible": false, - "custom_extra_data_key": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "canReceiveMessages": false, - "team": "test", - "type": "team", - "canBeAddedToGroups": true - }, - "frozen": false, - "disabled": false, - "members": [ - { - "user_id": "count_dooku", - "user": { - "id": "count_dooku", - "name": "Count Dooku", - "image": "https://vignette.wikia.nocookie.net/starwars/images/b/b8/Dooku_Headshot.jpg", - "language": "", - "role": "user", - "teams": [], - "created_at": "2024-04-22T06:42:08.562992Z", - "updated_at": "2025-04-23T13:02:29.974824Z", - "banned": false, - "online": false, - "last_active": "2025-10-01T07:12:24.084195Z", - "blocked_user_ids": [], - "avg_response_time": 531, - "shadow_banned": false, - "privacy_settings": { - "typing_indicators": { - "enabled": true - }, - "read_receipts": { - "enabled": true - } - }, - "devices": [ - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "805ea3d297c8b6ec3c9d8178bb7c004203cca68ba416b9d3fc7c6266cc92bfee01c1551baff10bf69fc6d357ae249f620eb7ce9d62159d46f27b8557bb1f9a1c258826cf01f06f2752fe87fc332fb8b4", - "created_at": "2025-04-23T12:48:04.255103Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80c5a6b6e6b8470c8631b25d398d68b04c218c5806cdc8a62893c1b96acb3382623f277bbbefe5e0b6f17f6b221627140037dad8c8bed5eca8eaebe00f9353627eaf6253bed1c72352b1f7c30e3af5d1", - "created_at": "2025-04-13T16:47:57.568822Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "e76ad8caaa243d1df1487b27cf0d2ee454f21709b6b69c51e6fbee3702abd721", - "created_at": "2025-03-09T01:21:58.729901Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "806dd79d6620247dec1bcd0c112be289334fc19db6d102bddbb1f9cc6073ea25d01786ba87a507665216f6a8bcaeaf828c2358374d5b98370423856f1832351676ed828bc6a398c4f79b0d3e4e837a4b", - "created_at": "2025-03-03T09:55:46.384028Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5ee2184dcd7c5ebbf17a0b8b820e7f70397adc2529a75a92a0d0817d8c49cbfb", - "created_at": "2025-01-14T21:47:18.084094Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "3b3a27f98aea406a49063e2125ae166b3f63b286a208c2b0dbb2b78947dedb4e", - "created_at": "2024-12-03T08:41:04.105631Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8f5bb77dd2a862b4312af6ba284df39fd50d77896e096fe35813a0a3bae14c14", - "created_at": "2024-11-21T22:22:25.955984Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80d1e3e76b250d7d63364f2d85bc47850655b95e9bb3d082a1eb2b613466c1c4b593f6ebd1957edfb00046bd9db7ae3f56a0fe52e62da55e69e328361a1279fb6bb6b416eb2ebadca40ee7d5a8064931", - "created_at": "2024-11-18T12:05:39.54754Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "a07e4a466e6a4b93367cac7782051521d3235a54f6caab8fb4058d7ca0b5b9f3", - "created_at": "2024-11-07T07:15:14.198046Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "9dc9815891580982a1381fce4bdb7ba26bb2b0744cbddaa0fe3c0fd56e96f28b", - "created_at": "2024-09-04T20:41:14.087342Z", - "user_id": "count_dooku" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "801f28973e47ebdd8afcb814c8af30b95d44bdfe46253281a0bc7ad45737e484b714240ac6ac132f5f64223e92a3a135e3b7f3017ae819c47c03fc082f7d9829333d5c2dc72ca8f9d78925f152abde26", - "created_at": "2024-07-11T05:45:57.533093Z", - "user_id": "count_dooku" - } - ], - "invisible": false, - "birthland": "Serenno" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "member", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "lando_calrissian", - "user": { - "id": "lando_calrissian", - "name": "Lando Calrissian", - "image": "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T19:06:08.890459Z", - "updated_at": "2025-04-16T17:23:28.189521Z", - "banned": false, - "online": false, - "last_active": "2025-10-12T03:39:43.111316Z", - "blocked_user_ids": [], - "avg_response_time": 1165670, - "shadow_banned": false, - "privacy_settings": { - "typing_indicators": { - "enabled": true - }, - "read_receipts": { - "enabled": true - } - }, - "devices": [ - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80fd207c06394ad12bef4ba1c29d8d47d33c15f09551da4da659bbfb3fcfa20b2a19c03c63a0face2a9713e2249c2801f296cf89a675601de1b49437cd354aa6f9e8f6c6b1c0f70fe8b47e13b9966e2d", - "created_at": "2025-07-02T17:28:07.646031Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "807668fb1146ec2887451bfb492ed176318b75bac6f269373032a5279c42f48ee1d39e5a6343069fa72a1edb955848a931f1f156a5a543b878e9be2aefe45260843389eec42f640510ff8ed3b225cf03", - "created_at": "2025-06-20T06:29:12.864132Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "82093d2a6d4b1a5ce7516398b350e46816da77c05f22d88c27c0d1e0cf3dd22d", - "created_at": "2025-06-08T07:57:49.274354Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8660694406405a9e8c9544a9693061feebe9c8dbe43897d135a8540417224aba", - "created_at": "2025-06-03T17:01:00.396494Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8511fb7ed1b4b274cfb28bd37fdfadd50759e268f5eed5d2a62d0dacf662d04a", - "created_at": "2025-05-28T13:10:55.29705Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "93c24dc48e340302116e3887c5c5581b9023bdb0eb742a5a465d0fe4903c1876", - "created_at": "2025-05-26T10:15:28.710502Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "1d83f0fa4dd79bea75fd51324924027e81d366eaac47c55a36df8de8a870e1fb", - "created_at": "2025-05-16T20:34:31.031296Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "c26374589b9d018f36c61e2b9e6f18ca3dce5f8cb7c24392d61dd66fc7a5fd32", - "created_at": "2025-05-07T01:14:21.875477Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8006d309839f95442e1e5a75eb6ca08c663a1b7b9162046961fe84fd902929f59ec854b39a430949ecc95225525599fe9162eac6eeddbe91fe96dfab75a66614b0b4d722f1ca941b5c918a44eface3bc", - "created_at": "2025-05-04T22:06:32.060655Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "36d3efac60c179fff596cc47e81c22beedaa4ebe367b34f59bd0c583b9046c74", - "created_at": "2025-04-29T22:02:15.838796Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "6519624008d60453a633556a08c0af4f6744a47d655d05eb2c8c51e475c6bd46", - "created_at": "2025-04-26T20:21:18.692769Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "e3ce7c58999ac779aed5460b367a140423498e4b28ee8b5c56a4ad8523f3e00c", - "created_at": "2025-04-15T09:30:42.778492Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "89b56798cdf220d4a5de4990dd8a34b5c312950927d0409097a2073a07de25e5", - "created_at": "2025-04-11T22:24:27.745853Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "c57e77977466c71fff0a972536a436f6ba4425fcd426b75cee5b6be6c0326712", - "created_at": "2025-04-02T18:55:46.532785Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "2522a047f4d128f29ea3c25e4ec6795bcaa248efc879a09d33d015be1ca2afa8", - "created_at": "2025-04-01T09:45:35.569944Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "97de66a3fc9266046b0ee3c5ee6ef26fd5f5d3c818a1ddff9aab504da2e6d6fc", - "created_at": "2025-03-17T23:03:11.287394Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "026710ef5c80ecff242b6908cf638d810ea7f682622d7acad7a06776411e601b", - "created_at": "2025-03-15T07:07:21.347976Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8039cd4c893aac2f2af7c668002c75b0cbe6155b25a5a2cbbd94068f907d758de31d93d25b5347d9f75f168baac9b27b842143bbbcfc4c49bb8b5413fcd5d08bb0334536f7f94e438b0a1591d4c33b06", - "created_at": "2025-03-12T12:47:27.304069Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "4ac20de2cda85c8c31a50b416b5049b3b7b31e5184e0f0371a811529d20b3ad0", - "created_at": "2025-02-10T19:25:02.942617Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "6de10cbebd4518ae2a917f075be6754784684c3b95dda0c5579315c101c22dcd", - "created_at": "2025-02-07T00:15:22.100131Z", - "user_id": "lando_calrissian" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "7d1c4b24c6143de096973964dd34016b60a96acc74944dfa2997170c5368f65d", - "created_at": "2025-01-31T17:35:26.948409Z", - "user_id": "lando_calrissian" - } - ], - "invisible": false, - "birthland": "Socorro" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "shadow_banned": false, - "privacy_settings": { - "typing_indicators": { - "enabled": true - }, - "read_receipts": { - "enabled": true - } - }, - "devices": [ - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80ff3d0ff101a3d4f3319e20996b2e33cbe4ff1673632911ba61471d9c7382cf10db29c41676c6a42cc7e4084f0b00b28b450da1fe52a97edb8c67d5690b0d9b600c863c34a34546a649be08e3a975cf", - "created_at": "2025-10-07T13:31:12.581472Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "466ec8f052e4e43f6f429a55907217c289618204ebaf4f7ca8fe52c303b01d05", - "created_at": "2025-10-06T07:05:44.023463Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80dc67313692ff3d512089cd19df179395b298d106b86cd2aedf0edfe1de40cd7caba3025823acccf9173fce2000728459e6751c4c7c96b80415a441d54c690d3f2d1635d26a8d58c0407edff8fcfe34", - "created_at": "2025-09-23T11:14:39.868685Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8013785658480684ee2a2f523bd4d52d6f2fac5e34283f4765362b4d526e5f374a46a0eeff277aee8d94ca872c4cdce2c021fef9cb1c71a4e5b28bba5481cab79c2ff7f111d4a7488c7a28de76736ba6", - "created_at": "2025-09-22T07:33:07.001628Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8075f5f2d91f892d2a16de528dea1fae54d63f9d1c09ca18501547ec8b3880c54766e80e0eb8e10920a8545cf5a08f0048adf01c029a17b69fda5dbc8019733fdb5b4923e80d525ca96bd833f02434c5", - "created_at": "2025-09-16T07:18:39.830014Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "804df9f905b9fd1f2c53e160a5f3f1db34490bea2ecb0fdb6aa99d9f407fd67b477079cc403e15b7424927276ac32b113c9820bb569b99a14d60fef0f5ae24804bea0e20be5da6d724f4c3abbf5cd0cb", - "created_at": "2025-09-15T18:13:06.007999Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8044edf2252ff9f253dada5dbf39f018f454e5bcdaf188501c6d105a4f5740c1872acb619f113596a1cb6f9071667d20e601f134aa0c83db83ce733f458b60c5c1b8712e2808abd4a840ce0347a8ea1a", - "created_at": "2025-09-15T13:32:20.775357Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80fee24033dcaa2b50911104447c596a7490ff86fa6593eb8f0eaca6c699c951ddad5e39d5ce95d3858d12a0169ab126dec8c4fa4f791a723304bf6c3ee415b0fe67e4dd7422e7af1dc49396442946eb", - "created_at": "2025-09-13T16:07:49.949311Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "a23e236960612858446bc7b0e78152f0d03ff977da16a1beec3d4558b51c3377", - "created_at": "2025-09-11T14:55:25.842429Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5c1967db91dfc57b32d12728b8ac7410edc325daae78215430319239bb1b9378", - "created_at": "2025-09-10T10:32:28.865373Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", - "created_at": "2025-09-03T04:32:38.333126Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802e021f067e90a9be03d142b102c30a871ad7e16b41bf469eab29c1e2f7b480f433802b2a3e6585752c09e44ed9f71d858bb61dad135568caaaefdac4536529d3b43812f2077c95b57ff4483fd1ad26", - "created_at": "2025-09-02T16:12:13.231524Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8006075492db346334382095f7c068600bfa16f8216628c938b1e9bdb18eae4288cd1567707a67b9868ae4166495d92d1eb9c20b2984a0fc3bc578013bcf05654598b343fa8d3f807e5c296e8ce61dc1", - "created_at": "2025-09-02T07:14:39.15913Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802eefd1b58c8a23fefe5d181538f0109535ad3c5baabf31f6d7a5f898b8ef0c8386d882aa07c511cf97d28f8291f6f8c46b78d7c6639f5c70f09a69ca6a92aff4d9b007a4f4a89b3ab5b722ffb9c86c", - "created_at": "2025-09-01T11:36:07.581094Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5c7d0b29710fba33189fb659b1cbc2be3491d4f3c4b399b8afefb8bc920103ec", - "created_at": "2025-08-28T11:02:17.709186Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8079dec1832d067a951130384525f5de98ae20fe23d131e584c0028b69bdb60aec0a461b19e2b8886e94a479781a115c703bad0e3726f644e12a812d37bdd2b79b2021cd103688a2424bde9c08c9f2f0", - "created_at": "2025-08-27T05:23:02.591215Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809eb957c901bdbae1f20ee0bebdf84c12b6d3a141ba32b5898e7353a7575ef53f941dba98b544fd7c1a36e10d3b88abe9b65d12b44b2fd93dc4a7a57fa45a79750d8a3106fe0d54b46cebad6ec7cbda", - "created_at": "2025-08-26T15:54:24.98832Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "806541d07bd58f5d897f3cb28994fd47749351fb77f948edb2b140c7c147c21bab5e5f90c6124a138f809cefce008662220436a7031c09224434282393bf76d4ba0f61866ecd5c72807279da20618b0a", - "created_at": "2025-08-22T10:41:07.519822Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "808f6525eea58e6aa49bd81b12abbff5a5e9d5310c626559cc1c306f1b0b08496e27d06c64872046d39edb5c9c89f84f0f8debe6663b54ce1aff83269d13850464ae06d3f293f3d6748b0ebd9a91a2f8", - "created_at": "2025-08-19T00:56:28.078927Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802e7837a85219bea9972172678578d10f683c085da918b90a56a477f6692ccbb52ff7badf3ece11c1d612afed415347dac1a8acfc1dfa51385e2ea53689d43466c84f729b069f10efff86604bc7c40c", - "created_at": "2025-08-12T23:25:49.648064Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809409e7859ebfe71a461ead24d36a911eb5cdbab7184f848b45c44b153e65ff15c4be82066a2d768dc7184ab9b3b0c1b3cb7ec1f0819f231a6d09ad64c33dbfb8d1b919d5fda0474d9f29e3cc2940b7", - "created_at": "2025-08-12T12:58:05.975459Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", - "created_at": "2025-08-11T20:11:02.29744Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "803588facf5946ecfe386d4906321b9fe31fe4181b03bffbbfd4b733c4acd00f9046a16f7beeec102e95273d15c95c15a44a2076ba83ad001bbd77ce3810b6465a7ef92fe299da7038b636e762d6150b", - "created_at": "2025-08-08T11:31:24.887765Z", - "user_id": "luke_skywalker" - } - ], - "invisible": false, - "team": "test", - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "type": "team", - "canReceiveMessages": false, - "custom_extra_data_key": true - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "role": "owner", - "channel_role": "channel_member", - "notifications_muted": false - }, - { - "user_id": "leia_organa", - "user": { - "id": "leia_organa", - "name": "Leia Organa", - "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "language": "", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-10-01T16:49:27.725672Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:21:40.020397141Z", - "blocked_user_ids": [], - "avg_response_time": 638602, - "shadow_banned": false, - "privacy_settings": { - "typing_indicators": { - "enabled": true - }, - "read_receipts": { - "enabled": true - } - }, - "devices": [ - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "801ffb7f463b104d50a2cbdad23120a4b2ea77d3c98954b7929bc19f4fd0f38ca988fbd91577879dedfc503001f515fbf11ccf204b755c0e062df31750ebb8c463da8a3afb6a4e6dc028e96623f74a35", - "created_at": "2025-10-11T12:30:03.355048Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "14216b28af8556468ae8dbf08a5ea602f9deb9c2eb115d9b6aacf7f7bde74ade", - "created_at": "2025-10-10T16:00:49.838202Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "ff2dc6e49c2663ff99b3a5e2a1dc8bf779a263c851f2dad3dc06270145fd1bbd", - "created_at": "2025-09-29T15:50:48.713725Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80fc30257c03c5a273df381a64d9e168a94ca4cc5c44d82f6e9b27f94f6f5de710a08d7baa9a83fe41a72366c446eaba3c148273ef7b66f15c6e6ed21e20e068c45df62d18ab7680e91a076bb325fce7", - "created_at": "2025-09-08T17:54:15.751885Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8038e9ddae90ad612073cccda59c8e715193e375185a4c96cc374b12bf3b74d8bdcc4016ad13c5e48544a4606c82e874809a13322acdd03b4a4e9fd4ccfc5704aa183fd1cc12481b3a9d83e630e8adfe", - "created_at": "2025-07-29T15:46:00.608151Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "806c802cfe521f2980a80135de1e2c18dff2d634ab770bf6b645014a5da1f01047ff66c9933c83a8c5a464f5eb00e4d34a8bc02addc27f93aa24dd7f56c92e272e15d9f6d9beca620d08c93f80e01fe3", - "created_at": "2025-07-27T10:22:24.684004Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8043c6d0aeff8f1223f3c516e40e520c378e2ede462f06d215b630367d81f7b103181bb31f74ab23e37fe1f8686480f54eb31b2c560cac5ee18daa5558981c7f25465733563afc8050ecb7035be93ea7", - "created_at": "2025-07-04T15:29:30.008558Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80b164164bc6eedba4a8c4d606d1e92b965facaa019aa89d81eaf7ed2579c4128e3ad7dff7a26dbba507ef75f79260d8acd86a26ce53a48efb83216045e8d9ed0eccd716fcd1807e381169a3f68f5f2e", - "created_at": "2025-07-04T13:17:05.176311Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80c3ed5bdc76358518c9ef3707c00a4046848575a3ecc6f5f0fc9e67cf5823dd17e3784464aa6fc2a9640ec7e4f6eb6b7be3a4a9858857cda94fce3f6b3af907c1a68c19052302a4d07c5eeacbad1d0d", - "created_at": "2025-06-23T12:39:52.733646Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "7b7bf6129032c5c3c333f2f80d8939d914587a11aaa30953af5e63d89d9defa1", - "created_at": "2025-06-18T03:58:29.477347Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "807146c48063da5e10a127367fa5b54103c9b61e0f95576a485d6289681490a92bef0a3d59c4ca33fcd7eef7ce339463603221f39d7ef5109aebd80cd647942efc31e8a5d2e4c03f7e2af6d0e3b0db4b", - "created_at": "2025-05-02T18:24:07.641188Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5dd2129704b206673f40fcacc51498af12466d202fb7b86fbcd1def5e021743b", - "created_at": "2025-04-29T18:11:54.744459Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80416524327191703aa5704a6788f5b85b6b38cb8de437675da7883c1a61b4b479fc20426200dd366c45b972948046a258d95d3331d4fc694e17c491ba671b209792b4df54e58907cdca208e58ffbd66", - "created_at": "2025-04-25T04:57:05.059816Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "de52baf21c547d06ce71f54f5ef8a47693c9b9f194dd6e82e1c855803eaacc4d", - "created_at": "2025-04-24T16:27:51.309408Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8babc49a1d5e8943a25291fd4c2606f6d8f38470693daf7628a9a9c8617ea313", - "created_at": "2025-04-15T19:38:32.316517Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80390cd4b5322ce878abb0ea8c6f2f554d21f2233767b61b4eb3a916e0be7325bc225528ffd97e3503ac6a56c5a62b747150b0d44abbeb6fb8c8e8b2dbf7fbe4291f3dbcac257ab869a6046876db1289", - "created_at": "2025-04-07T13:06:09.56915Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "f71e38ca56a6625256d487331369e2b950219fd0d55e8d7cb82fbd88966fee65", - "created_at": "2025-04-06T04:27:36.245492Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809855cbb448059e832d627031ae978b9bb37f323b1ad0b3b0b879e4437cdc20", - "created_at": "2025-04-04T23:27:03.99699Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "92045b8b37f75e4c36ed74665f68ebf4d932c19acd2a02d0c3604230a44a9537", - "created_at": "2025-03-27T01:06:37.823489Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80f13fec122f492c20a9dc11bb7003a1e870fb75de24890568c0fb5e3b7365331618227d89162b7322e9b68b20a0b02a42d6b8bf9923091d34492a1375dc781026cd9845b6b561a0d284cbc77b5bdc39", - "created_at": "2025-02-20T11:33:28.513905Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80c9f99c1d02347ba3599ab752e55308681244fe97fcbc95eb0e630bcd97cb3ee927e8b15d5085938b4b18246f2e9ba2de3f357a6ca9127b1971df14742725841f0fd8537233b5e43a07a24422411816", - "created_at": "2025-02-06T05:12:00.670052Z", - "user_id": "leia_organa" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8099cf56e010eb7a7ec2202eacffbc22c0d84b2c3007b9b45403f4f99fd42b670d6552521aea4c14263fd0ed0d034f2e3f25aa44145a4054ed7d067e25e2f221582d30de66f750e62e45e2c0fa73ed74", - "created_at": "2025-02-05T14:25:05.737649Z", - "user_id": "leia_organa" - } - ], - "invisible": false, - "is_moderator": true, - "birthland": "Polis Massa" - }, - "status": "member", - "created_at": "2025-10-15T11:22:22.248971Z", - "updated_at": "2025-10-15T11:22:22.248971Z", - "banned": false, - "shadow_banned": false, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - } - ], - "member_count": 4, - "config": { - "created_at": "2021-03-01T19:26:18.406502Z", - "updated_at": "2025-07-28T15:20:21.098826Z", - "name": "messaging", - "typing_events": true, - "read_events": true, - "connect_events": true, - "search": true, - "reactions": true, - "replies": true, - "quotes": true, - "mutes": true, - "uploads": true, - "url_enrichment": true, - "custom_events": true, - "push_notifications": true, - "reminders": true, - "mark_messages_pending": false, - "polls": true, - "user_message_reminders": false, - "shared_locations": true, - "count_messages": false, - "message_retention": "infinite", - "max_message_length": 5000, - "automod": "AI", - "automod_behavior": "block", - "blocklist": "profanity_en_2020_v1", - "blocklist_behavior": "block", - "automod_thresholds": { - "explicit": { - "flag": 0.85, - "block": 0.9 - }, - "spam": { - "flag": 0.85, - "block": 0.9 - }, - "toxic": { - "flag": 0.85, - "block": 0.9 - } - }, - "skip_last_msg_update_for_system_msgs": false, - "commands": [ - { - "name": "giphy", - "description": "Post a random gif to the channel", - "args": "[text]", - "set": "fun_set" - } - ] - }, - "name": "Sync Mock Server" - }, - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "canBeAddedToGroups": true, - "canReceiveMessages": false, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "custom_extra_data_key": true, - "team": "test", - "type": "team", - "privacy_settings": { - "read_receipts": { - "enabled": true - }, - "typing_indicators": { - "enabled": true - } - } - } -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_member.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_member.json deleted file mode 100644 index ffb7cd3c4d6..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_member.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "type": "member.added", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_type": "messaging", - "member": { - "user_id": "leia_organa", - "user": { - "id": "leia_organa", - "role": "admin", - "teams_role": null, - "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-10-01T16:49:27.725672Z", - "last_active": "2025-10-15T11:19:09.931581Z", - "last_engaged_at": "2025-10-15T00:02:20.390107Z", - "banned": false, - "online": true, - "avg_response_time": 638602, - "name": "Leia Organa", - "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "birthland": "Polis Massa", - "is_moderator": true - }, - "status": "member", - "created_at": "2025-10-15T11:22:22.248971Z", - "updated_at": "2025-10-15T11:22:22.248971Z", - "banned": false, - "shadow_banned": false, - "is_global_banned": false, - "archived_at": null, - "pinned_at": null, - "role": "admin", - "channel_role": "channel_member", - "notifications_muted": false - }, - "user": { - "id": "leia_organa", - "role": "admin", - "teams_role": null, - "created_at": "2024-04-04T09:42:00.68335Z", - "updated_at": "2025-10-01T16:49:27.725672Z", - "last_active": "2025-10-15T11:19:09.931581Z", - "last_engaged_at": "2025-10-15T00:02:20.390107Z", - "banned": false, - "online": true, - "avg_response_time": 638602, - "name": "Leia Organa", - "image": "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png", - "birthland": "Polis Massa", - "is_moderator": true - }, - "channel_last_message_at": "2025-10-15T11:22:21.804306Z", - "created_at": "2025-10-15T11:22:22.257574084Z" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_health_check.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_health_check.json deleted file mode 100644 index 86e1e1209f9..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_health_check.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "connection_id": "68e67b7d-0a15-3975-0200-0000000082cb", - "me": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "avg_response_time": 203648, - "privacy_settings": { - "typing_indicators": { - "enabled": true - }, - "read_receipts": { - "enabled": true - } - }, - "devices": [], - "invisible": false, - "mutes": [], - "channel_mutes": [], - "unread_count": 9, - "total_unread_count": 9, - "unread_channels": 1, - "unread_threads": 0, - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "custom_extra_data_key": true, - "canReceiveMessages": false, - "birthland": "Tatooine", - "team": "test", - "type": "team" - }, - "cid": "*", - "type": "health.check", - "created_at": "2025-10-15T11:22:20.040248985Z" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_message.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_message.json deleted file mode 100644 index c34b88ed7d9..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_message.json +++ /dev/null @@ -1,260 +0,0 @@ -{ - "type": "message.new", - "created_at": "2025-10-15T11:22:21.838194472Z", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_member_count": 3, - "channel_custom": { - "name": "Sync Mock Server" - }, - "channel_type": "messaging", - "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", - "message": { - "id": "dc702391-84b6-44af-aa67-c349d2e72441", - "text": "Test", - "html": "

Test

\n", - "type": "regular", - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "shadow_banned": false, - "devices": [ - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80ff3d0ff101a3d4f3319e20996b2e33cbe4ff1673632911ba61471d9c7382cf10db29c41676c6a42cc7e4084f0b00b28b450da1fe52a97edb8c67d5690b0d9b600c863c34a34546a649be08e3a975cf", - "created_at": "2025-10-07T13:31:12.581472Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "466ec8f052e4e43f6f429a55907217c289618204ebaf4f7ca8fe52c303b01d05", - "created_at": "2025-10-06T07:05:44.023463Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80dc67313692ff3d512089cd19df179395b298d106b86cd2aedf0edfe1de40cd7caba3025823acccf9173fce2000728459e6751c4c7c96b80415a441d54c690d3f2d1635d26a8d58c0407edff8fcfe34", - "created_at": "2025-09-23T11:14:39.868685Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8013785658480684ee2a2f523bd4d52d6f2fac5e34283f4765362b4d526e5f374a46a0eeff277aee8d94ca872c4cdce2c021fef9cb1c71a4e5b28bba5481cab79c2ff7f111d4a7488c7a28de76736ba6", - "created_at": "2025-09-22T07:33:07.001628Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8075f5f2d91f892d2a16de528dea1fae54d63f9d1c09ca18501547ec8b3880c54766e80e0eb8e10920a8545cf5a08f0048adf01c029a17b69fda5dbc8019733fdb5b4923e80d525ca96bd833f02434c5", - "created_at": "2025-09-16T07:18:39.830014Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "804df9f905b9fd1f2c53e160a5f3f1db34490bea2ecb0fdb6aa99d9f407fd67b477079cc403e15b7424927276ac32b113c9820bb569b99a14d60fef0f5ae24804bea0e20be5da6d724f4c3abbf5cd0cb", - "created_at": "2025-09-15T18:13:06.007999Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8044edf2252ff9f253dada5dbf39f018f454e5bcdaf188501c6d105a4f5740c1872acb619f113596a1cb6f9071667d20e601f134aa0c83db83ce733f458b60c5c1b8712e2808abd4a840ce0347a8ea1a", - "created_at": "2025-09-15T13:32:20.775357Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "80fee24033dcaa2b50911104447c596a7490ff86fa6593eb8f0eaca6c699c951ddad5e39d5ce95d3858d12a0169ab126dec8c4fa4f791a723304bf6c3ee415b0fe67e4dd7422e7af1dc49396442946eb", - "created_at": "2025-09-13T16:07:49.949311Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "a23e236960612858446bc7b0e78152f0d03ff977da16a1beec3d4558b51c3377", - "created_at": "2025-09-11T14:55:25.842429Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5c1967db91dfc57b32d12728b8ac7410edc325daae78215430319239bb1b9378", - "created_at": "2025-09-10T10:32:28.865373Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8082e4f123b0f83543d1f7c04920c76ec7d4b4db9d8f2e8ff0cc4daeef338209be72df2b35257cfdb86623c14d241cf1d1f42b78fef01e16052d13b03f75ed135360886f1900d3a0e80e97b268514c77", - "created_at": "2025-09-03T04:32:38.333126Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802e021f067e90a9be03d142b102c30a871ad7e16b41bf469eab29c1e2f7b480f433802b2a3e6585752c09e44ed9f71d858bb61dad135568caaaefdac4536529d3b43812f2077c95b57ff4483fd1ad26", - "created_at": "2025-09-02T16:12:13.231524Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8006075492db346334382095f7c068600bfa16f8216628c938b1e9bdb18eae4288cd1567707a67b9868ae4166495d92d1eb9c20b2984a0fc3bc578013bcf05654598b343fa8d3f807e5c296e8ce61dc1", - "created_at": "2025-09-02T07:14:39.15913Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802eefd1b58c8a23fefe5d181538f0109535ad3c5baabf31f6d7a5f898b8ef0c8386d882aa07c511cf97d28f8291f6f8c46b78d7c6639f5c70f09a69ca6a92aff4d9b007a4f4a89b3ab5b722ffb9c86c", - "created_at": "2025-09-01T11:36:07.581094Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "5c7d0b29710fba33189fb659b1cbc2be3491d4f3c4b399b8afefb8bc920103ec", - "created_at": "2025-08-28T11:02:17.709186Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "8079dec1832d067a951130384525f5de98ae20fe23d131e584c0028b69bdb60aec0a461b19e2b8886e94a479781a115c703bad0e3726f644e12a812d37bdd2b79b2021cd103688a2424bde9c08c9f2f0", - "created_at": "2025-08-27T05:23:02.591215Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809eb957c901bdbae1f20ee0bebdf84c12b6d3a141ba32b5898e7353a7575ef53f941dba98b544fd7c1a36e10d3b88abe9b65d12b44b2fd93dc4a7a57fa45a79750d8a3106fe0d54b46cebad6ec7cbda", - "created_at": "2025-08-26T15:54:24.98832Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "806541d07bd58f5d897f3cb28994fd47749351fb77f948edb2b140c7c147c21bab5e5f90c6124a138f809cefce008662220436a7031c09224434282393bf76d4ba0f61866ecd5c72807279da20618b0a", - "created_at": "2025-08-22T10:41:07.519822Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "808f6525eea58e6aa49bd81b12abbff5a5e9d5310c626559cc1c306f1b0b08496e27d06c64872046d39edb5c9c89f84f0f8debe6663b54ce1aff83269d13850464ae06d3f293f3d6748b0ebd9a91a2f8", - "created_at": "2025-08-19T00:56:28.078927Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "802e7837a85219bea9972172678578d10f683c085da918b90a56a477f6692ccbb52ff7badf3ece11c1d612afed415347dac1a8acfc1dfa51385e2ea53689d43466c84f729b069f10efff86604bc7c40c", - "created_at": "2025-08-12T23:25:49.648064Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809409e7859ebfe71a461ead24d36a911eb5cdbab7184f848b45c44b153e65ff15c4be82066a2d768dc7184ab9b3b0c1b3cb7ec1f0819f231a6d09ad64c33dbfb8d1b919d5fda0474d9f29e3cc2940b7", - "created_at": "2025-08-12T12:58:05.975459Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "809065933d06e42d27179dfe7f24ef7c34e74c0898c184cc1de0abd791f0f9d2a1342ad140129c3a26ba7a4f86b3534358475d9964024ff373bc2c705bc2d999cbbe0402af60077fb7af6c1f73295bd7", - "created_at": "2025-08-11T20:11:02.29744Z", - "user_id": "luke_skywalker" - }, - { - "push_provider": "apn", - "push_provider_name": "APN-Configuration", - "id": "803588facf5946ecfe386d4906321b9fe31fe4181b03bffbbfd4b733c4acd00f9046a16f7beeec102e95273d15c95c15a44a2076ba83ad001bbd77ce3810b6465a7ef92fe299da7038b636e762d6150b", - "created_at": "2025-08-08T11:31:24.887765Z", - "user_id": "luke_skywalker" - } - ], - "invisible": false, - "pando": "{\"speciality\":\"ios engineer\"}", - "team": "test", - "canBeAddedToGroups": true, - "type": "team", - "canReceiveMessages": false, - "birthland": "Tatooine", - "custom_extra_data_key": true - }, - "member": { - "channel_role": "channel_member" - }, - "attachments": [], - "latest_reactions": [], - "own_reactions": [], - "reaction_counts": {}, - "reaction_scores": {}, - "reply_count": 0, - "deleted_reply_count": 0, - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:21.804306Z", - "updated_at": "2025-10-15T11:22:21.804306Z", - "shadowed": false, - "mentioned_users": [], - "silent": false, - "pinned": false, - "pinned_at": null, - "pinned_by": null, - "pin_expires": null, - "restricted_visibility": [] - }, - "user": { - "id": "luke_skywalker", - "name": "Luke Skywalker", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "language": "en", - "role": "admin", - "teams": [], - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "banned": false, - "online": true, - "last_active": "2025-10-15T11:22:19.961028904Z", - "blocked_user_ids": [], - "avg_response_time": 203648, - "custom_extra_data_key": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "team": "test", - "type": "team", - "canReceiveMessages": false, - "privacy_settings": { - "read_receipts": { - "enabled": false - }, - "typing_indicators": { - "enabled": false - } - }, - "canBeAddedToGroups": true, - "birthland": "Tatooine" - }, - "watcher_count": 1, - "unread_count": 9, - "total_unread_count": 9, - "unread_channels": 1 -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_reaction.json b/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_reaction.json deleted file mode 100644 index f50cd514d28..00000000000 --- a/TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_reaction.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "type": "reaction.new", - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_id": "ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "channel_type": "messaging", - "message": { - "id": "dc702391-84b6-44af-aa67-c349d2e72441", - "text": "Test", - "html": "

Test

\n", - "type": "regular", - "user": { - "id": "luke_skywalker", - "role": "admin", - "teams_role": null, - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "last_active": "2025-10-15T11:22:19.961028904Z", - "last_engaged_at": "2025-10-15T00:03:45.658694Z", - "banned": false, - "online": true, - "language": "en", - "avg_response_time": 203648, - "canBeAddedToGroups": true, - "canReceiveMessages": false, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "custom_extra_data_key": true, - "type": "team", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "name": "Luke Skywalker", - "team": "test" - }, - "restricted_visibility": [], - "attachments": [], - "latest_reactions": [ - { - "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "role": "admin", - "teams_role": null, - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "last_active": "2025-10-15T11:22:19.961028904Z", - "last_engaged_at": "2025-10-15T00:03:45.658694Z", - "banned": false, - "online": true, - "language": "en", - "avg_response_time": 203648, - "canReceiveMessages": false, - "name": "Luke Skywalker", - "team": "test", - "type": "team", - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "custom_extra_data_key": true, - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg" - }, - "type": "like", - "score": 1, - "created_at": "2025-10-15T11:22:21.987244Z", - "updated_at": "2025-10-15T11:22:21.987244Z" - } - ], - "own_reactions": [], - "reaction_counts": { - "like": 1 - }, - "reaction_scores": { - "like": 1 - }, - "reaction_groups": { - "like": { - "count": 1, - "sum_scores": 1, - "first_reaction_at": "2025-10-15T11:22:21.987244Z", - "last_reaction_at": "2025-10-15T11:22:21.987244Z" - } - }, - "reply_count": 0, - "deleted_reply_count": 0, - "cid": "messaging:ec9f758c-3f7e-46f3-9618-bc5760428e5e", - "created_at": "2025-10-15T11:22:21.804306Z", - "updated_at": "2025-10-15T11:22:22.001363Z", - "shadowed": false, - "mentioned_users": [], - "silent": false, - "pinned": false, - "pinned_at": null, - "pinned_by": null, - "pin_expires": null, - "member": { - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "role": "admin", - "teams_role": null, - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "last_active": "2025-10-15T11:22:19.961028904Z", - "last_engaged_at": "2025-10-15T00:03:45.658694Z", - "banned": false, - "online": true, - "language": "en", - "avg_response_time": 203648, - "custom_extra_data_key": true, - "team": "test", - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "name": "Luke Skywalker", - "birthland": "Tatooine", - "type": "team", - "canBeAddedToGroups": true, - "canReceiveMessages": false, - "pando": "{\"speciality\":\"ios engineer\"}" - }, - "status": "member", - "created_at": "2025-10-15T11:22:20.279452Z", - "updated_at": "2025-10-15T11:22:20.279452Z", - "banned": false, - "shadow_banned": false, - "is_global_banned": false, - "archived_at": null, - "pinned_at": null, - "channel_role": "channel_member", - "notifications_muted": false - } - }, - "reaction": { - "message_id": "dc702391-84b6-44af-aa67-c349d2e72441", - "user_id": "luke_skywalker", - "user": { - "id": "luke_skywalker", - "role": "admin", - "teams_role": null, - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "last_active": "2025-10-15T11:22:19.961028904Z", - "last_engaged_at": "2025-10-15T00:03:45.658694Z", - "banned": false, - "online": true, - "language": "en", - "avg_response_time": 203648, - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "custom_extra_data_key": true, - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "canReceiveMessages": false, - "name": "Luke Skywalker", - "team": "test", - "type": "team" - }, - "type": "like", - "score": 1, - "created_at": "2025-10-15T11:22:21.987244Z", - "updated_at": "2025-10-15T11:22:21.987244Z" - }, - "user": { - "id": "luke_skywalker", - "role": "admin", - "teams_role": null, - "created_at": "2024-04-04T09:26:11.805899Z", - "updated_at": "2025-09-15T05:51:41.748915Z", - "last_active": "2025-10-15T11:22:19.961028904Z", - "last_engaged_at": "2025-10-15T00:03:45.658694Z", - "banned": false, - "online": true, - "language": "en", - "avg_response_time": 203648, - "image": "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg", - "canReceiveMessages": false, - "name": "Luke Skywalker", - "team": "test", - "type": "team", - "canBeAddedToGroups": true, - "pando": "{\"speciality\":\"ios engineer\"}", - "birthland": "Tatooine", - "custom_extra_data_key": true - }, - "channel_last_message_at": "2025-10-15T11:22:21.804306Z", - "created_at": "2025-10-15T11:22:22.01374502Z" -} \ No newline at end of file diff --git a/TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift deleted file mode 100644 index 7c69e6d1ac5..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -@testable import StreamChat -import XCTest - -public extension StreamMockServer { - func configureAttachmentEndpoints() { - server.register(MockEndpoint.image) { [weak self] _ in - self?.attachmentCreation(fileUrl: Attachments.image) - } - server.register(MockEndpoint.file) { [weak self] _ in - self?.attachmentCreation(fileUrl: Attachments.file) - } - } - - private func attachmentCreation(fileUrl: String) -> HttpResponse { - var json = TestData.toJson(.httpAttachment) - json[JSONKey.file] = fileUrl - return .ok(.json(json)) - } -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift b/TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift deleted file mode 100644 index a8d4d0e73a8..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -@testable import StreamChat -import XCTest - -// MARK: - Config - -public struct ChannelConfigs { - struct Cooldown { - var isEnabled = false - var duration: Int = 3 - } - - private var configs: [String: ChannelConfig_Mock] = [:] - - var coolDown = Cooldown() - - mutating func setCooldown(enabled value: Bool, duration: Int) { - coolDown = Cooldown(isEnabled: value, duration: duration) - } - - func updateChannel(channel: inout [String: Any], withId id: String) { - guard - let config = configs[id], - var innerChannel = channel[JSONKey.channel] as? [String: Any], - var configJson = innerChannel[JSONKey.config] as? [String: Any] - else { - return - } - config.update(json: &configJson) - innerChannel[JSONKey.config] = configJson - channel[JSONKey.channel] = innerChannel - } - - mutating func updateConfig( - config: ChannelConfig_Mock, - forChannelWithId id: String, - server: StreamMockServer - ) { - var json = server.channelList - guard - var channels = json[JSONKey.channels] as? [[String: Any]], - let channelIndex = server.channelIndex(withId: id), - var channel = server.channel(withId: id) - else { - return - } - - configs[id] = config - - updateChannel(channel: &channel, withId: id) - channels[channelIndex] = channel - json[JSONKey.channels] = channels - server.channelList = json - } - - mutating func config( - forChannelId id: String, - server: StreamMockServer - ) -> ChannelConfig_Mock? { - if let config = configs[id] { return config } - - let config = loadConfig(forChannelId: id, server: server) - configs[id] = config - return config - } - - private func loadConfig( - forChannelId id: String, - server: StreamMockServer - ) -> ChannelConfig_Mock? { - guard - let channel = server.channel(withId: id), - let innerChannel = channel[JSONKey.channel] as? [String: Any], - let configJson = (innerChannel[JSONKey.config] as? [String: Any])?.jsonToString() - else { - return nil - } - - return try? ChannelConfig_Mock(configJson) - } -} - -public struct ChannelConfig_Mock: Codable { - public var typingEvents: Bool - public var readEvents: Bool - public var connectEvents: Bool - public var search: Bool - public var reactions: Bool - public var replies: Bool - public var quotes: Bool - public var mutes: Bool - public var uploads: Bool - public var urlEnrichment: Bool - public var customEvents: Bool - public var pushNotifications: Bool - public var reminders: Bool - - public enum CodingKeys: String, CodingKey, CaseIterable { - case typingEvents = "typing_events" - case readEvents = "read_events" - case connectEvents = "connect_events" - case search - case reactions - case replies - case quotes - case mutes - case uploads - case urlEnrichment = "url_enrichment" - case customEvents = "custom_events" - case pushNotifications = "push_notifications" - case reminders - } - - public func update(json: inout [String: Any]) { - json[CodingKeys.typingEvents.rawValue] = typingEvents - json[CodingKeys.readEvents.rawValue] = readEvents - json[CodingKeys.connectEvents.rawValue] = connectEvents - json[CodingKeys.search.rawValue] = search - json[CodingKeys.reactions.rawValue] = reactions - json[CodingKeys.replies.rawValue] = replies - json[CodingKeys.quotes.rawValue] = quotes - json[CodingKeys.mutes.rawValue] = mutes - json[CodingKeys.uploads.rawValue] = uploads - json[CodingKeys.urlEnrichment.rawValue] = urlEnrichment - json[CodingKeys.customEvents.rawValue] = customEvents - json[CodingKeys.pushNotifications.rawValue] = pushNotifications - json[CodingKeys.reminders.rawValue] = reminders - } - - public init(data: Data) throws { - self = try JSONDecoder().decode(ChannelConfig_Mock.self, from: data) - } - - public init(_ json: String, using encoding: String.Encoding = .utf8) throws { - guard let data = json.data(using: encoding) else { - throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil) - } - try self.init(data: data) - } -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift deleted file mode 100644 index c86800ca8f7..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift +++ /dev/null @@ -1,549 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -@testable import StreamChat -import XCTest - -public let channelKey = ChannelCodingKeys.self -public let channelPayloadKey = ChannelPayload.CodingKeys.self -var autogeneratedMessagesCounter = 0 - -public extension StreamMockServer { - private enum ChannelRequestType { - case addMembers([String]) - case removeMembers([String]) - - static func type(from body: [UInt8]) -> ChannelRequestType? { - let json = TestData.toJson(body) - - // Add members - if let ids = json[JSONKey.Channel.addMembers] as? [String] { - return .addMembers(ids) - } - - // Remove members - if let ids = json[JSONKey.Channel.removeMembers] as? [String] { - return .removeMembers(ids) - } - - return nil - } - - var eventType: EventType { - switch self { - case .addMembers: - return .memberAdded - case .removeMembers: - return .memberRemoved - } - } - - var ids: [String] { - switch self { - case .addMembers(let ids), - .removeMembers(let ids): - return ids - } - } - } - - func configureChannelEndpoints() { - server.register(MockEndpoint.query) { [weak self] request in - let channelId = try XCTUnwrap(request.params[EndpointQuery.channelId]) - self?.channelQueryEndpointWasCalled = true - self?.updateChannel(withId: channelId) - return self?.mockChannelQuery(request) - } - server.register(MockEndpoint.channels) { [weak self] request in - self?.channelsEndpointWasCalled = true - self?.updateChannels() - return self?.limitChannels(request) - } - server.register(MockEndpoint.channel) { [weak self] request in - self?.handleChannelRequest(request) - } - server.register(MockEndpoint.truncate) { [weak self] request in - self?.channelTruncation(request) - } - server.register(MockEndpoint.sync) { [weak self] _ in - self?.handleSyncRequest() - } - } - - func channelIndex(withId id: String) -> Int? { - guard - let channels = channelList[JSONKey.channels] as? [[String: Any]], - let index = channels.firstIndex( - where: { - let channel = $0[channelPayloadKey.channel.rawValue] as? [String: Any] - return (channel?[channelKey.id.rawValue] as? String) == id - }) - else { - return nil - } - - return index - } - - func channel(withId id: String) -> [String: Any]? { - guard - let channels = channelList[JSONKey.channels] as? [[String: Any]], - let index = channelIndex(withId: id) - else { - return nil - } - return channels[index] - } - - func waitForChannelQueryUpdate(timeout: Double = StreamMockServer.waitTimeout) { - let endTime = Date().timeIntervalSince1970 * 1000 + timeout * 1000 - while !channelQueryEndpointWasCalled - && endTime > Date().timeIntervalSince1970 * 1000 {} - } - - func waitForChannelsUpdate(timeout: Double = StreamMockServer.waitTimeout) { - let endTime = Date().timeIntervalSince1970 * 1000 + timeout * 1000 - while !channelsEndpointWasCalled - && endTime > Date().timeIntervalSince1970 * 1000 {} - } - - private func updateChannel(withId id: String) { - var json = channelList - var channels = json[JSONKey.channels] as? [[String: Any]] - if let index = channels?.firstIndex(where: { - let channel = $0[channelPayloadKey.channel.rawValue] as? [String: Any] - return (channel?[channelKey.id.rawValue] as? String) == id - }) { - let messageList = findMessagesByChannelId(id) - if - var channel = channels?[index], - var innerChannel = channel[JSONKey.channel] as? [String: Any] { - setCooldown(in: &innerChannel) - channel[JSONKey.channel] = innerChannel - - channel[channelPayloadKey.messages.rawValue] = messageList - - channels?[index] = channel - json[JSONKey.channels] = channels - } - currentChannelId = id - channelList = json - } - } - - private func updateChannels() { - var json = channelList - guard var channels = json[JSONKey.channels] as? [[String: Any]] else { return } - - for (index, channel) in channels.enumerated() { - let channelDetails = channel[channelPayloadKey.channel.rawValue] as? [String: Any] - if let channelId = channelDetails?[channelKey.id.rawValue] as? String { - let messageList = findMessagesByChannelId(channelId) - var mockedChannel = channel - mockedChannel[channelPayloadKey.messages.rawValue] = messageList - channels[index] = mockedChannel - } - } - json[JSONKey.channels] = channels - channelList = json - } - - private func limitChannels(_ request: HttpRequest) -> HttpResponse { - guard - let payloadQuery = request.queryParams.first(where: { $0.0 == JSONKey.payload }), - let payload = payloadQuery.1.removingPercentEncoding?.json, - let limit = payload[Pagination.CodingKeys.pageSize.rawValue] as? Int - else { - return .ok(.json(channelList)) - } - - let offset = payload[Pagination.CodingKeys.offset.rawValue] as? Int ?? 0 - - var limitedChannelList = channelList - let channels = limitedChannelList[JSONKey.channels] as? [[String: Any]] ?? [] - let channelCount = channels.count - 1 - - if !allChannelsWereLoaded && channelCount > limit { - allChannelsWereLoaded = (channelCount - limit - offset < 0) - let startWith = offset > channelCount ? channelCount : offset - let endWith = offset + limit < channelCount ? offset + limit - 1 : channelCount - limitedChannelList[JSONKey.channels] = Array(channels[startWith...endWith]) - } - - return .ok(.json(limitedChannelList)) - } - - private func mockChannelQuery(_ request: HttpRequest) -> HttpResponse { - let json = TestData.toJson(request.body) - let messages = json[JSONKey.messages] as? [String: Any] - - guard let id = request.params[EndpointQuery.channelId] else { return .badRequest(nil) } - guard var channel = findChannelById(id) else { return .badRequest(nil) } - guard let limit = messages?[MessagesPagination.CodingKeys.pageSize.rawValue] as? Int else { - return .ok(.json(channel)) - } - - channel[channelPayloadKey.messages.rawValue] = mockMessagePagination( - messageList: findMessagesByChannelId(id), - limit: limit, - idLt: messages?[paginationKey.lessThan.rawValue] as? String, - idGt: messages?[paginationKey.greaterThan.rawValue] as? String, - idLte: messages?[paginationKey.lessThanOrEqual.rawValue] as? String, - idGte: messages?[paginationKey.greaterThanOrEqual.rawValue] as? String, - around: messages?[paginationKey.around.rawValue] as? String - ) - return .ok(.json(channel)) - } - - // MARK: Channel Members - - private func handleChannelRequest(_ request: HttpRequest) -> HttpResponse? { - guard let type = ChannelRequestType.type(from: request.body) else { - print("Unhandled request: \(request)") - return .badRequest(nil) - } - - return updateChannelMembers(request, ids: type.ids, eventType: type.eventType) - } - - // TODO: CIS-2230 - private func handleSyncRequest() -> HttpResponse? { - .ok(.json([JSONKey.events: []])) - } - - private func updateChannelMembers( - _ request: HttpRequest, - ids: [String], - eventType: EventType - ) -> HttpResponse { - guard - let id = request.params[EndpointQuery.channelId] - else { - return .ok(.json(channelList)) - } - - var json = channelList - guard - var channels = json[JSONKey.channels] as? [[String: Any]], - let channelIndex = channelIndex(withId: id), - var channel = channel(withId: id), - var innerChannel = channel[JSONKey.channel] as? [String: Any], - var members = channel[channelKey.members.rawValue] as? [[String: Any]] - else { - return .badRequest(nil) - } - - let membersWithIds = memberJSONs(for: ids) - switch eventType { - case .memberAdded: - members.append(contentsOf: membersWithIds) - case .memberRemoved: - members.removeAll(where: { - let memberId = $0[JSONKey.userId] as? String - return ids.contains(memberId ?? "") - }) - default: - return .badRequest(nil) - } - innerChannel[channelKey.members.rawValue] = members - innerChannel[channelKey.memberCount.rawValue] = members.count - setCooldown(in: &innerChannel) - channel[JSONKey.channel] = innerChannel - - channels[channelIndex] = channel - json[JSONKey.channels] = channels - - if let channelId = (channel[JSONKey.channel] as? [String: Any])?[JSONKey.id] as? String { - // Send web socket event with given event type - membersWithIds.forEach { - websocketMember(with: $0, channelId: channelId, eventType: eventType) - } - - // Send channel update web socket event - websocketChannelUpdated(with: members, channelId: channelId) - } - - channelList = json - - return .ok(.json(json)) - } - - func mockMembers( - userSources: [String: Any]?, - sampleChannel: [String: Any], - memberDetails: [[String: String]] - ) -> [[String: Any]] { - var members: [[String: Any]] = [] - let channelMembers = sampleChannel[channelPayloadKey.members.rawValue] as? [[String: Any]] - guard var sampleMember = channelMembers?.first else { return members } - - for member in memberDetails { - sampleMember[JSONKey.user] = setUpUser(source: userSources, details: member) - sampleMember[JSONKey.userId] = member[userKey.id.rawValue] - members.append(sampleMember) - } - return members - } - - func mockChannels( - count: Int, - messageText: String? = nil, - messagesCount: Int = 0, - replyCount: Int = 0, - author: [String: Any]?, - members: [[String: Any]], - sampleChannel: [String: Any], - withAttachments: Bool = false - ) -> [[String: Any]] { - var channels: [[String: Any]] = [] - guard count > 0 else { return channels } - - var membership = sampleChannel[channelPayloadKey.membership.rawValue] as? [String: Any] - membership?[JSONKey.user] = author - - for channelIndex in 1...count { - autogeneratedMessagesCounter = 0 - var newChannel = sampleChannel - var messages: [[String: Any]?] = [] - newChannel[channelPayloadKey.members.rawValue] = members - newChannel[channelPayloadKey.membership.rawValue] = membership - let channelDetails = mockChannelDetails( - channel: newChannel, - author: author, - memberCount: members.count, - channelIndex: channelIndex - ) - - if messagesCount > 0 { - for messageIndex in 1...messagesCount { - let channelId = channelDetails?[channelKey.id.rawValue] as? String - let messageId = TestData.uniqueId - let newMessage = generateMessage( - withText: messageText, - withIndex: messageIndex, - withChannelIndex: channelIndex, - withId: messageId, - channelId: channelId, - author: author, - replyCount: replyCount, - withAttachments: withAttachments, - overallMessagesCount: messagesCount - ) - messages.append(newMessage) - - if replyCount > 0 { - for replyIndex in 1...replyCount { - generateMessage( - withIndex: replyIndex, - withChannelIndex: channelIndex, - withId: TestData.uniqueId, - parentId: messageId, - channelId: channelId, - author: author - ) - } - } - } - } - - newChannel[channelPayloadKey.messages.rawValue] = messages - newChannel[channelPayloadKey.channel.rawValue] = channelDetails - channels.append(newChannel) - } - - return channels - } - - @discardableResult - private func generateMessage( - withText text: String? = nil, - withIndex index: Int, - withChannelIndex channelIndex: Int, - withId id: String?, - parentId: String? = nil, - channelId: String?, - author: [String: Any]?, - replyCount: Int? = 0, - withAttachments: Bool = false, - overallMessagesCount: Int = 1 - ) -> [String: Any]? { - let timeInterval = TimeInterval(index + channelIndex * 1000 - 123_456_789) - let timestamp = TestData.stringTimestamp(Date(timeIntervalSinceNow: timeInterval)) - let messageText = text == nil ? String(index) : text - var message = mockMessage( - TestData.toJson(.message)[JSONKey.message] as? [String: Any], - channelId: channelId, - messageId: id, - text: messageText, - user: author, - createdAt: timestamp, - updatedAt: timestamp, - parentId: parentId, - replyCount: replyCount - ) - - if withAttachments { - var attachments: [[String: Any]] = [] - var file: [String: Any] = [:] - var type: AttachmentType? - - switch autogeneratedMessagesCounter { - case overallMessagesCount - 1, overallMessagesCount - 9: - type = .image - file[AttachmentCodingKeys.imageURL.rawValue] = Attachments.image - case overallMessagesCount - 3, overallMessagesCount - 11: - type = .giphy - let json = TestData.getMockResponse(fromFile: MockFile.ephemeralMessage).json - let messageObj = json["message"] as? [String: Any] - attachments = messageObj?[messageKey.attachments.rawValue] as? [[String: Any]] ?? [] - attachments[0][GiphyAttachmentSpecificCodingKeys.actions.rawValue] = nil - case overallMessagesCount - 5, overallMessagesCount - 13: - type = .file - file[AttachmentCodingKeys.assetURL.rawValue] = Attachments.file - file[AttachmentFile.CodingKeys.mimeType.rawValue] = "application/pdf" - file[AttachmentFile.CodingKeys.size.rawValue] = 123_456 - case overallMessagesCount - 7, overallMessagesCount - 15: - type = .video - file[AttachmentCodingKeys.assetURL.rawValue] = Attachments.video - file[AttachmentFile.CodingKeys.mimeType.rawValue] = "video/mp4" - file[AttachmentFile.CodingKeys.size.rawValue] = 123_456 - default: - break - } - if type != nil && type != .giphy { - for _ in 0...2 { - file[AttachmentCodingKeys.type.rawValue] = type?.rawValue - file[AttachmentCodingKeys.title.rawValue] = UUID().uuidString - attachments.append(file) - } - } - message?[messageKey.attachments.rawValue] = attachments - autogeneratedMessagesCounter += 1 - } - - parentId == nil ? saveMessage(message) : saveReply(message) - return message - } - - private func mockChannelDetails( - channel: [String: Any], - author: [String: Any]?, - memberCount: Int, - channelIndex: Int - ) -> [String: Any]? { - var channelDetails = channel[channelPayloadKey.channel.rawValue] as? [String: Any] - let uniqueId = TestData.uniqueId - let timeInterval = TimeInterval(123_456_789 - channelIndex * 1000) - let timestamp = TestData.stringTimestamp(Date(timeIntervalSinceNow: timeInterval)) - channelDetails?[channelKey.name.rawValue] = "\(channelIndex)" - channelDetails?[channelKey.id.rawValue] = uniqueId - channelDetails?[channelKey.cid.rawValue] = "\(ChannelType.messaging.rawValue):\(uniqueId)" - channelDetails?[channelKey.createdBy.rawValue] = author - channelDetails?[channelKey.memberCount.rawValue] = memberCount - channelDetails?[channelKey.createdAt.rawValue] = timestamp - channelDetails?[channelKey.updatedAt.rawValue] = timestamp - return channelDetails - } - - func getFirstChannelId() -> String { - let endTime = TestData.waitingEndTime - while channelList.isEmpty && endTime > TestData.currentTimeInterval {} - guard - let channels = channelList[JSONKey.channels] as? [[String: Any]], - let firstChannel = channels.first?[JSONKey.channel] as? [String: Any], - let id = firstChannel[channelKey.id.rawValue] as? String - else { - return "" - } - return id - } - - func removeChannel(_ id: String) { - let deletedChannel = try? XCTUnwrap(findChannelById(id)) - guard var channels = channelList[JSONKey.channels] as? [[String: Any]] else { return } - - if let deletedIndex = channels.firstIndex(where: { (channel) -> Bool in - (channel[channelKey.id.rawValue] as? String) == (deletedChannel?[channelKey.id.rawValue] as? String) - }) { - channels.remove(at: deletedIndex) - } - - channelList[JSONKey.channels] = channels - } - - private func truncateChannel(_ id: String, truncatedAt: String, truncatedBy: [String: Any]?) { - let channelMessages = findMessagesByChannelId(id) - for message in channelMessages { - removeMessage(message) - } - - var channel = findChannelById(id) - var channelDetails = channel?[JSONKey.channel] as? [String: Any] - channelDetails?[JSONKey.Channel.truncatedBy] = truncatedBy - channelDetails?[channelKey.truncatedAt.rawValue] = truncatedAt - - channel?[JSONKey.channel] = channelDetails - channel?[JSONKey.messages] = [] - - if var channels = channelList[JSONKey.channels] as? [[String: Any]?] { - removeChannel(id) - channels.append(channel) - channelList[JSONKey.channels] = channels - } - } - - private func channelTruncation(_ request: HttpRequest) -> HttpResponse? { - waitForChannelQueryUpdate() - guard let channelId = request.params[EndpointQuery.channelId] else { return .badRequest(nil) } - var json = TestData.toJson(.httpTruncate) - var truncatedMessage: [String: Any]? - var channel = json[JSONKey.channel] as? [String: Any] - let channelDetails = findChannelById(channelId)?[JSONKey.channel] as? [String: Any] - let truncatedby = channelDetails?[channelKey.createdBy.rawValue] as? [String: Any] - let truncatedAt = TestData.currentDate - - truncateChannel( - channelId, - truncatedAt: truncatedAt, - truncatedBy: truncatedby - ) - - channel?[channelKey.id.rawValue] = channelId - channel?[channelKey.cid.rawValue] = "\(ChannelType.messaging.rawValue):\(channelId)" - channel?[JSONKey.Channel.truncatedBy] = truncatedby - channel?[channelKey.truncatedAt.rawValue] = truncatedAt - channel?[channelKey.name.rawValue] = channelDetails?[channelKey.name.rawValue] - - websocketEvent( - .channelTruncated, - user: truncatedby, - channelId: channelId, - channel: channel - ) - - if let message = TestData.toJson(request.body)[JSONKey.message] as? [String: Any] { - truncatedMessage = json[JSONKey.message] as? [String: Any] - truncatedMessage?[messageKey.id.rawValue] = message[messageKey.id.rawValue] - if let text = message[messageKey.text.rawValue] as? String { - truncatedMessage?[messageKey.text.rawValue] = text - truncatedMessage?[messageKey.html.rawValue] = text.html - } - websocketMessage( - truncatedMessage?[messageKey.text.rawValue] as? String, - channelId: channelId, - messageId: truncatedMessage?[messageKey.id.rawValue] as? String, - messageType: .system, - eventType: .messageNew, - user: truncatedby, - channel: channel - ) - } else { - truncatedMessage = nil - } - - json[JSONKey.message] = truncatedMessage - json[JSONKey.channel] = channel - return .ok(.json(json)) - } -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift b/TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift new file mode 100644 index 00000000000..f7ea8b53c08 --- /dev/null +++ b/TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift @@ -0,0 +1,38 @@ +// +// Copyright © 2026 Stream.io Inc. All rights reserved. +// + +import Foundation + +public enum AttachmentType: String { + case image + case video + case file + + var attachment: String { + return rawValue + } +} + +public enum ReactionType: String { + case love + case lol = "haha" + case wow + case sad + case like + + var reaction: String { + return rawValue + } +} + +public enum MessageDeliveryStatus: String { + case read + case pending + case sent + case failed + + var status: String { + return rawValue + } +} diff --git a/TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift b/TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift deleted file mode 100644 index b589a71cec4..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -public extension StreamMockServer { - func pushNotification( - senderName: String, - text: String, - messageId: String, - cid: String, - targetBundleId: String - ) { - var json: [String: Any] - if pushNotificationPayload.isEmpty { - json = TestData.toJson(.pushNotification) - var aps = json[APNSKey.aps] as? [String: Any] - var alert = aps?[APNSKey.alert] as? [String: Any] - alert?[APNSKey.title] = "New message from \(senderName)" - alert?[APNSKey.body] = text - aps?[APNSKey.alert] = alert - json[APNSKey.aps] = aps - - var stream = json[APNSKey.stream] as? [String: Any] - stream?[APNSKey.messageId] = messageId - stream?[APNSKey.cid] = cid - json[APNSKey.stream] = stream - } else { - json = pushNotificationPayload - } - - let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"] ?? "" - let urlString = "\(MockServerConfiguration.httpHost):4567/push/\(udid)/\(targetBundleId)" - guard let url = URL(string: urlString) else { return } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.httpBody = json.jsonToString().data(using: .utf8) - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - - URLSession.shared.dataTask(with: request).resume() - } - - func recordVideo(name: String, delete: Bool = false, stop: Bool = false) { - let json: [String: Any] = ["delete": delete, "stop": stop] - let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"] ?? "" - let urlString = "\(MockServerConfiguration.httpHost):4567/record_video/\(udid)/\(name)" - guard let url = URL(string: urlString) else { return } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.httpBody = json.jsonToString().data(using: .utf8) - URLSession.shared.dataTask(with: request).resume() - } - - func revokeJwt(duration: UInt32 = jwtTimeout) { - let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"] ?? "" - let urlString = "\(MockServerConfiguration.httpHost):4567/jwt/revoke/\(udid)?duration=\(duration)" - guard let url = URL(string: urlString) else { return } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - URLSession.shared.dataTask(with: request).resume() - } - - func breakJwt(duration: UInt32 = jwtTimeout) { - let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"] ?? "" - let urlString = "\(MockServerConfiguration.httpHost):4567/jwt/break/\(udid)?duration=\(duration)" - guard let url = URL(string: urlString) else { return } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - URLSession.shared.dataTask(with: request).resume() - } -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift deleted file mode 100644 index 68e5350367c..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -@testable import StreamChat -import XCTest - -public let eventKey = EventPayload.CodingKeys.self - -public extension StreamMockServer { - func configureEventEndpoints() { - server.register(MockEndpoint.event) { [weak self] request in - let channelId = try XCTUnwrap(request.params[EndpointQuery.channelId]) - let json = TestData.toJson(request.body) - let event = json[JSONKey.event] as? [String: Any] - let eventType = event?[eventKey.eventType.rawValue] as? String - self?.websocketEvent( - EventType(rawValue: String(describing: eventType)), - user: UserDetails.lukeSkywalker, - channelId: channelId - ) - return self?.sendEvent(eventType, channelId: channelId) - } - server.register(MockEndpoint.messageRead) { [weak self] request in - let channelId = try XCTUnwrap(request.params[EndpointQuery.channelId]) - self?.websocketEvent(.messageRead, user: UserDetails.lukeSkywalker, channelId: channelId) - return self?.sendEvent(.messageRead, channelId: channelId) - } - } - - private func sendEvent(_ eventType: String?, channelId: String) -> HttpResponse { - var json = TestData.toJson(.httpChatEvent) - var event = json[JSONKey.event] as? [String: Any] - let user = setUpUser(source: event, details: UserDetails.lukeSkywalker) - event?[eventKey.user.rawValue] = user - event?[eventKey.createdAt.rawValue] = TestData.currentDate - event?[eventKey.eventType.rawValue] = eventType - event?[eventKey.cid.rawValue] = "\(ChannelType.messaging.rawValue):\(channelId)" - event?[eventKey.channelId.rawValue] = channelId - json[JSONKey.event] = event - return .ok(.json(json)) - } - - private func sendEvent(_ eventType: EventType, channelId: String) -> HttpResponse { - sendEvent(eventType.rawValue, channelId: channelId) - } -} diff --git a/TestTools/StreamChatTestMockServer/Utilities/LaunchArgument.swift b/TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift similarity index 81% rename from TestTools/StreamChatTestMockServer/Utilities/LaunchArgument.swift rename to TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift index 11fdfa77401..ff868e65caf 100644 --- a/TestTools/StreamChatTestMockServer/Utilities/LaunchArgument.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift @@ -5,12 +5,6 @@ import Foundation import XCTest -public enum MockServerConfiguration { - public static var port: UInt16 = UInt16(Int.random(in: 61000..<62000)) - public static var websocketHost = "ws://localhost" - public static var httpHost = "http://localhost" -} - public enum EnvironmentVariable: String { // This changes the base url to localhost with assigned port. // Two conditions need to be met in order to leverage the web socket server in LLC. @@ -40,7 +34,7 @@ public extension ProcessInfo { public extension XCUIApplication { func setLaunchArguments(_ args: LaunchArgument...) { - launchArguments.append(contentsOf: args.map { $0.rawValue }) + launchArguments.append(contentsOf: args.map(\.rawValue)) } func setEnvironmentVariables(_ envVars: [EnvironmentVariable: String]) { diff --git a/TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift b/TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift deleted file mode 100644 index 28d129ce535..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -public extension StreamMockServer { - func configureMembersEndpoints() { - server.register(MockEndpoint.members) { [weak self] request in - return self?.mockMembersQuery(request) - } - } - - private func mockMembersQuery(_ request: HttpRequest) -> HttpResponse { - guard - let payloadQuery = request.queryParams.first(where: { $0.0 == JSONKey.payload }), - let payload = payloadQuery.1.removingPercentEncoding?.json, - let channelId = payload[JSONKey.id] as? String - else { - return .badRequest(nil) - } - - guard let channel = findChannelById(channelId) else { return .badRequest(nil) } - guard let members = channel[JSONKey.members] else { return .badRequest(nil) } - return .ok(.json([JSONKey.members: members])) - } -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/MessageList.swift b/TestTools/StreamChatTestMockServer/MockServer/MessageList.swift deleted file mode 100644 index ef54d369e5b..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/MessageList.swift +++ /dev/null @@ -1,235 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -@testable import StreamChat -import XCTest - -public extension StreamMockServer { - func saveMessage(_ message: [String: Any]?) { - guard let newMessage = message else { return } - - let newMessageId = newMessage[messageKey.id.rawValue] as? String - if let messageIndex = messageList.firstIndex(where: { (message) -> Bool in - let existedMessageId = message[messageKey.id.rawValue] as? String - return newMessageId == existedMessageId - }) { - messageList[messageIndex] = newMessage - } else { - messageList.append(newMessage) - } - } - - func saveReply(_ message: [String: Any]?) { - guard let newMessage = message else { return } - - let newMessageId = newMessage[messageKey.id.rawValue] as? String - if let messageIndex = threadList.firstIndex(where: { (message) -> Bool in - let existedMessageId = message[messageKey.id.rawValue] as? String - return newMessageId == existedMessageId - }) { - threadList[messageIndex] = newMessage - } else { - threadList.append(newMessage) - } - } - - var firstMessage: [String: Any]? { - try? XCTUnwrap(waitForMessageList().first) - } - - var lastMessage: [String: Any]? { - try? XCTUnwrap(waitForMessageList().last) - } - - var firstMessageInThread: [String: Any]? { - try? XCTUnwrap(threadList.first) - } - - var lastMessageInThread: [String: Any]? { - try? XCTUnwrap(threadList.last) - } - - func findMessageByIndex(_ index: Int) -> [String: Any]? { - try? XCTUnwrap(waitForMessageList()[index]) - } - - func findMessageById(_ id: String) -> [String: Any]? { - waitForMessageWithId(id) ?? [:] // this avoids unexpected crashes after test - } - - func findMessageByUserId(_ userId: String) -> [String: Any]? { - try? XCTUnwrap(waitForMessageWithUserId(userId)) - } - - func findMessagesByParentId(_ parentId: String) -> [[String: Any]] { - _ = waitForMessageWithId(parentId) - return (threadList + messageList).filter { - ($0[messageKey.parentId.rawValue] as? String) == parentId - } - } - - func findMessagesByChannelId(_ channelId: String) -> [[String: Any]] { - return messageList.filter { - String(describing: $0[messageKey.cid.rawValue]).contains(":\(channelId)") - } - } - - func getMessageId(_ message: [String: Any]?) -> String? { - message?[messageKey.id.rawValue] as? String - } - - func removeMessage(_ deletedMessage: [String: Any]?) { - removeMessage(id: getMessageId(deletedMessage)) - } - - func removeMessage(id: String?) { - removeMessageFromMessageList(id) - removeMessageFromThreadList(id) - } - - private func removeMessageFromMessageList(_ id: String?) { - if let deletedIndex = messageList.firstIndex(where: { (message) -> Bool in - (message[messageKey.id.rawValue] as? String) == id - }) { - messageList.remove(at: deletedIndex) - } - } - - private func removeMessageFromThreadList(_ id: String?) { - if let deletedIndex = threadList.firstIndex(where: { (message) -> Bool in - (message[messageKey.id.rawValue] as? String) == id - }) { - threadList.remove(at: deletedIndex) - } - } - - func isMessageInList(_ list: [[String: Any]], message: [String: Any]?) -> Bool { - let filteredList = list.filter { - ($0[messageKey.id.rawValue] as? String) == (message?[messageKey.id.rawValue] as? String) - } - return !filteredList.isEmpty - } - - @discardableResult - private func waitForMessageList() -> [[String: Any]] { - let endTime = TestData.waitingEndTime - while messageList.isEmpty && endTime > TestData.currentTimeInterval {} - return messageList - } - - private func waitForMessageWithId(_ id: String) -> [String: Any]? { - let endTime = TestData.waitingEndTime - var newMessageList: [[String: Any]] = [] - while newMessageList.isEmpty && endTime > TestData.currentTimeInterval { - newMessageList = (messageList + threadList).filter { - ($0[messageKey.id.rawValue] as? String) == id - } - } - return newMessageList.first - } - - private func waitForMessageWithUserId(_ userId: String) -> [String: Any]? { - let endTime = TestData.waitingEndTime - var newMessageList: [[String: Any]] = [] - while newMessageList.isEmpty && endTime > TestData.currentTimeInterval { - newMessageList = (messageList + threadList).filter { - let user = $0[messageKey.user.rawValue] as? [String: Any] - return (user?[userKey.id.rawValue] as? String) == userId - } - } - return newMessageList.first - } - - func waitForWebsocketMessage( - withText text: String, - timeout: Double = StreamMockServer.waitTimeout - ) { - let endTime = Date().timeIntervalSince1970 * 1000 + timeout * 1000 - while latestWebsocketMessage != text - && endTime > Date().timeIntervalSince1970 * 1000 { - print("Waiting for websocket message with text: '\(text)'") - } - } - - func waitForHttpMessage( - withText text: String, - timeout: Double = StreamMockServer.waitTimeout - ) { - let endTime = Date().timeIntervalSince1970 * 1000 + timeout * 1000 - while latestHttpMessage != text - && endTime > Date().timeIntervalSince1970 * 1000 { - print("Waiting for http message with text: '\(text)'") - } - } - - func mockMessagePagination( - messageList: [[String: Any]], - limit: Int, - idLt: String?, - idGt: String?, - idLte: String?, - idGte: String?, - around: String? - ) -> [[String: Any]] { - var newMessageList: [[String: Any]] = [] - var startWith: Int? - var endWith: Int? - - if let idLt = idLt { - let messageIndex = messageList.firstIndex { - idLt == $0[messageKey.id.rawValue] as? String - } - if let messageIndex = messageIndex { - startWith = messageIndex - limit > 0 ? messageIndex - limit : 0 - endWith = messageIndex - 1 > 0 ? messageIndex - 1 : 0 - } - } else if let idGt = idGt { - let messageIndex = messageList.firstIndex { - idGt == $0[messageKey.id.rawValue] as? String - } - if let messageIndex = messageIndex { - let messageCount = messageList.count - 1 - let plusLimit = messageIndex + limit - startWith = messageIndex - endWith = plusLimit < messageCount ? plusLimit : messageCount - } - } else if let idLte = idLte { - let messageIndex = messageList.firstIndex { - idLte == $0[messageKey.id.rawValue] as? String - } - if let messageIndex = messageIndex { - let minusLimit = messageIndex - limit - startWith = minusLimit > 0 ? minusLimit : 0 - endWith = messageIndex - } - } else if let idGte = idGte { - let messageIndex = messageList.firstIndex { - idGte == $0[messageKey.id.rawValue] as? String - } - if let messageIndex = messageIndex { - let messageCount = messageList.count - 1 - let plusLimit = messageIndex + limit - startWith = messageIndex - endWith = plusLimit < messageCount ? plusLimit - 1 : messageCount - } - } else if let around = around { - let messageIndex = messageList.firstIndex { - around == $0[messageKey.id.rawValue] as? String - } - if let messageIndex = messageIndex { - startWith = messageIndex - endWith = min(messageIndex + limit, messageList.count - 1) - } - } - - if let startWith = startWith, let endWith = endWith { - let endWith = min(endWith, messageList.count - 1) - newMessageList = Array(messageList[startWith...endWith]) - } else { - newMessageList = Array(messageList.suffix(limit)) - } - - return newMessageList - } -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift deleted file mode 100644 index cc040c0a34e..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift +++ /dev/null @@ -1,620 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -@testable import StreamChat -import XCTest - -public let messageKey = MessagePayloadsCodingKeys.self -public let paginationKey = PaginationParameter.CodingKeys.self - -public extension StreamMockServer { - func configureMessagingEndpoints() { - server.register(MockEndpoint.message) { [weak self] request in - let channelId = try XCTUnwrap(request.params[EndpointQuery.channelId]) - return self?.messageCreation(request, channelId: channelId) - } - server.register(MockEndpoint.messageUpdate) { [weak self] request in - try self?.messageUpdate(request) - } - server.register(MockEndpoint.replies) { [weak self] request in - try self?.mockMessageReplies(request) - } - server.register(MockEndpoint.action) { [weak self] request in - let json = TestData.toJson(request.body) - let messageId = json[AttachmentActionRequestBody.CodingKeys.messageId.rawValue] as? String - let channelId = json[AttachmentActionRequestBody.CodingKeys.channelId.rawValue] as? String - let formData = json[AttachmentActionRequestBody.CodingKeys.data.rawValue] as? [String: Any] - return self?.ephemeralMessageCreation( - messageId: try XCTUnwrap(messageId), - channelId: try XCTUnwrap(channelId), - formData: try XCTUnwrap(formData) - ) - } - } - - private func trackMessage( - _ text: String, - messageType: MessageType, - eventType: EventType - ) { - if eventType == .messageNew && messageType != .ephemeral { - latestHttpMessage = text - } - } - - func mockDeletedMessage(_ message: [String: Any]?, user: [String: Any]?) -> [String: Any]? { - var mockedMessage = message - mockedMessage?[messageKey.deletedAt.rawValue] = TestData.currentDate - mockedMessage?[messageKey.type.rawValue] = MessageType.deleted.rawValue - mockedMessage?[messageKey.user.rawValue] = user - return mockedMessage - } - - func mockMessage( - _ message: [String: Any]?, - messageType: MessageType = .regular, - channelId: String? = nil, - messageId: String?, - text: String?, - command: String? = nil, - user: [String: Any]?, - createdAt: String?, - updatedAt: String?, - parentId: String? = nil, - showReplyInChannel: Bool? = nil, - quotedMessageId: String? = nil, - quotedMessage: [String: Any]? = nil, - attachments: Any? = nil, - replyCount: Int? = 0 - ) -> [String: Any]? { - var mockedMessage = message - mockedMessage?[messageKey.type.rawValue] = messageType.rawValue - if let createdAt = createdAt, let updatedAt = updatedAt { - mockedMessage?[messageKey.createdAt.rawValue] = createdAt - mockedMessage?[messageKey.updatedAt.rawValue] = updatedAt - } - if let messageId = messageId { - mockedMessage?[messageKey.id.rawValue] = messageId - } - if let attachments = attachments { - mockedMessage?[messageKey.attachments.rawValue] = attachments as? [[String: Any]] - } - if let text = text { - mockedMessage?[messageKey.text.rawValue] = text - mockedMessage?[messageKey.html.rawValue] = text.html - - if [Links.youtube, Links.unsplash].contains(where: { text.contains($0) }) { - let jsonWithLink = text.contains(Links.youtube) ? MockFile.youtube : MockFile.unsplash - let json = TestData.toJson(jsonWithLink)[JSONKey.message] as? [String: Any] - let linkAttachments = json?[messageKey.attachments.rawValue] - var updatedAttachments = attachments as? [[String: Any]] ?? [] - updatedAttachments += linkAttachments as? [[String: Any]] ?? [] - mockedMessage?[messageKey.attachments.rawValue] = updatedAttachments - } - } - if let command = command { - mockedMessage?[messageKey.command.rawValue] = command - } - if let channelId = channelId { - let channelType = ChannelType.messaging.rawValue - mockedMessage?[messageKey.cid.rawValue] = "\(channelType):\(channelId)" - mockedMessage?[eventKey.channelId.rawValue] = channelId - } - if let parentId = parentId { - mockedMessage?[messageKey.parentId.rawValue] = parentId - } - if let showReplyInChannel = showReplyInChannel { - mockedMessage?[messageKey.showReplyInChannel.rawValue] = showReplyInChannel - } - if let quotedMessageId = quotedMessageId { - mockedMessage?[messageKey.quotedMessageId.rawValue] = quotedMessageId - } - if let quotedMessage = quotedMessage { - mockedMessage?[messageKey.quotedMessage.rawValue] = quotedMessage - } - if let user = user { - mockedMessage?[messageKey.user.rawValue] = user - } - if let replyCount = replyCount { - mockedMessage?[messageKey.replyCount.rawValue] = replyCount - } - return mockedMessage - } - - private func messageUpdate(_ request: HttpRequest) throws -> HttpResponse { - let messageId = try XCTUnwrap(request.params[EndpointQuery.messageId]) - let message = findMessageById(messageId) - let cid = message?[messageKey.cid.rawValue] as? String - let channelId = cid?.split(separator: ":").last.map { String($0) } - switch request.method { - case EndpointMethod.delete.rawValue: - let hardParam = request.queryParams.filter { $0.0 == JSONKey.hard }.first?.1 - let hardDelete = (hardParam == "1") - return messageDeletion( - messageId: messageId, - channelId: channelId, - hardDelete: hardDelete - ) - case EndpointMethod.get.rawValue: - return messageInfo(messageId: messageId) - default: - return messageCreation( - request, - channelId: channelId, - eventType: .messageUpdated - ) - } - } - - private func ephemeralMessageCreation( - messageId: String, - channelId: String, - formData: [String: Any] - ) -> HttpResponse { - var json = TestData.toJson(.ephemeralMessage) - var message = findMessageById(messageId) - let attachmentAction = formData[JSONKey.attachmentAction] as? String - let timestamp = TestData.currentDate - - switch attachmentAction { - case JSONKey.AttachmentAction.send: - var attachments = message?[messageKey.attachments.rawValue] as? [[String: Any]] - attachments?[0][GiphyAttachmentSpecificCodingKeys.actions.rawValue] = nil - message?[messageKey.attachments.rawValue] = attachments - - sendWebsocketMessages( - httpMessage: message, - messageText: "", - messageTimestamp: timestamp, - messageType: .ephemeral, - eventType: .messageNew - ) - case JSONKey.AttachmentAction.shuffle: - break - default: - return .badRequest(nil) - } - - message?[messageKey.updatedAt.rawValue] = timestamp - json[JSONKey.message] = message - return .ok(.json(json)) - } - - private func messageCreation( - _ request: HttpRequest, - channelId: String?, - eventType: EventType = .messageNew - ) -> HttpResponse { - let json = TestData.toJson(request.body) - let message = json[JSONKey.message] as? [String: Any] - let parentId = message?[messageKey.parentId.rawValue] as? String - let quotedMessageId = message?[messageKey.quotedMessageId.rawValue] as? String - let channelReply = message?[messageKey.showReplyInChannel.rawValue] as? Bool ?? false - - let messageText = message?[messageKey.text.rawValue] as? String ?? "" - let messageTextComponents = Set(messageText.components(separatedBy: " ")) - - let messageType: MessageType - if messageText.starts(with: "/giphy") { - messageType = .ephemeral - } else if channelReply || parentId == nil { - messageType = .regular - } else { - messageType = .reply - } - if messageText.starts(with: "/") && messageType != .ephemeral { - return messageInvalidCommand( - message, - command: String(messageText.dropFirst(1)), - channelId: channelId, - parentId: parentId - ) - } else if messageType != .ephemeral && !forbiddenWords.isDisjoint(with: messageTextComponents) { - return errorMessageHttpResponse( - from: message, - errorText: Message.blockedByModerationPolicies, - channelId: channelId - ) - } - - if let parentId = parentId, let quotedMessageId = quotedMessageId { - return quotedMessageCreationInThread( - message, - messageType: messageType, - parentId: parentId, - quotedMessageId: quotedMessageId, - eventType: eventType - ) - } else if let parentId = parentId { - return messageCreationInThread( - message, - messageType: messageType, - parentId: parentId, - eventType: eventType - ) - } else if let quotedMessageId = quotedMessageId { - return quotedMessageCreationInChannel( - message, - messageType: messageType, - channelId: channelId, - quotedMessageId: quotedMessageId, - eventType: eventType - ) - } else { - return messageCreationInChannel( - message, - messageType: messageType, - channelId: channelId, - eventType: eventType - ) - } - } - - private func messageCreationInChannel( - _ message: [String: Any]?, - messageType: MessageType, - channelId: String?, - eventType: EventType = .messageNew - ) -> HttpResponse { - let text = message?[messageKey.text.rawValue] as? String ?? "" - let messageId = message?[messageKey.id.rawValue] as? String - let mockFile = messageType == .ephemeral ? MockFile.ephemeralMessage : MockFile.message - var responseJson = TestData.toJson(mockFile) - let responseMessage = responseJson[JSONKey.message] as? [String: Any] - let timestamp: String = TestData.currentDate - let user = setUpUser(source: responseMessage, details: UserDetails.lukeSkywalker) - let attachments = message?[messageKey.attachments.rawValue] - ?? responseMessage?[messageKey.attachments.rawValue] - - let mockedMessage = mockMessage( - responseMessage, - messageType: messageType, - channelId: channelId, - messageId: messageId, - text: text, - user: user, - createdAt: timestamp, - updatedAt: timestamp, - attachments: attachments - ) - - if messageType == .ephemeral { - saveMessage(mockedMessage) - } else { - sendWebsocketMessages( - httpMessage: mockedMessage, - messageText: text, - messageTimestamp: timestamp, - messageType: messageType, - eventType: eventType - ) - } - - responseJson[JSONKey.message] = mockedMessage - trackMessage(text, messageType: messageType, eventType: eventType) - return .ok(.json(responseJson)) - } - - private func quotedMessageCreationInChannel( - _ message: [String: Any]?, - messageType: MessageType, - channelId: String?, - quotedMessageId: String, - eventType: EventType = .messageNew - ) -> HttpResponse { - let text = message?[messageKey.text.rawValue] as? String ?? "" - let messageId = message?[messageKey.id.rawValue] as? String - let mockFile = messageType == .ephemeral ? MockFile.ephemeralMessage : MockFile.message - var responseJson = TestData.toJson(mockFile) - let responseMessage = responseJson[JSONKey.message] as? [String: Any] - let timestamp: String = TestData.currentDate - let user = setUpUser(source: responseMessage, details: UserDetails.lukeSkywalker) - let quotedMessage = findMessageById(quotedMessageId) - let attachments = message?[messageKey.attachments.rawValue] - ?? responseMessage?[messageKey.attachments.rawValue] - - let mockedMessage = mockMessage( - responseMessage, - messageType: messageType, - channelId: channelId, - messageId: messageId, - text: text, - user: user, - createdAt: timestamp, - updatedAt: timestamp, - quotedMessageId: quotedMessageId, - quotedMessage: quotedMessage, - attachments: attachments - ) - - if messageType == .ephemeral { - saveMessage(mockedMessage) - } else { - sendWebsocketMessages( - httpMessage: mockedMessage, - messageText: text, - messageTimestamp: timestamp, - messageType: messageType, - eventType: eventType - ) - } - - responseJson[JSONKey.message] = mockedMessage - trackMessage(text, messageType: messageType, eventType: eventType) - return .ok(.json(responseJson)) - } - - private func messageCreationInThread( - _ message: [String: Any]?, - messageType: MessageType, - parentId: String, - eventType: EventType = .messageNew - ) -> HttpResponse { - let showReplyInChannel = message?[messageKey.showReplyInChannel.rawValue] as? Bool - let text = message?[messageKey.text.rawValue] as? String ?? "" - let messageId = message?[messageKey.id.rawValue] as? String - let mockFile = messageType == .ephemeral ? MockFile.ephemeralMessage : MockFile.message - var responseJson = TestData.toJson(mockFile) - let responseMessage = responseJson[JSONKey.message] as? [String: Any] - let timestamp: String = TestData.currentDate - let user = setUpUser(source: responseMessage, details: UserDetails.lukeSkywalker) - let attachments = message?[messageKey.attachments.rawValue] - ?? responseMessage?[messageKey.attachments.rawValue] - - let mockedMessage = mockMessage( - responseMessage, - messageType: messageType, - messageId: messageId, - text: text, - user: user, - createdAt: timestamp, - updatedAt: timestamp, - parentId: parentId, - showReplyInChannel: showReplyInChannel, - attachments: attachments - ) - - if messageType == .ephemeral { - saveMessage(mockedMessage) - } else { - sendWebsocketMessages( - httpMessage: mockedMessage, - messageText: text, - messageTimestamp: timestamp, - messageType: messageType, - eventType: eventType, - channelReply: showReplyInChannel - ) - } - - responseJson[JSONKey.message] = mockedMessage - trackMessage(text, messageType: messageType, eventType: eventType) - return .ok(.json(responseJson)) - } - - private func quotedMessageCreationInThread( - _ message: [String: Any]?, - messageType: MessageType, - parentId: String, - quotedMessageId: String, - eventType: EventType = .messageNew - ) -> HttpResponse { - let showReplyInChannel = message?[messageKey.showReplyInChannel.rawValue] as? Bool - let text = message?[messageKey.text.rawValue] as? String ?? "" - let messageId = message?[messageKey.id.rawValue] as? String - let mockFile = messageType == .ephemeral ? MockFile.ephemeralMessage : MockFile.message - var responseJson = TestData.toJson(mockFile) - let responseMessage = responseJson[JSONKey.message] as? [String: Any] - let timestamp: String = TestData.currentDate - let user = setUpUser(source: responseMessage, details: UserDetails.lukeSkywalker) - let quotedMessage = findMessageById(quotedMessageId) - let attachments = message?[messageKey.attachments.rawValue] - ?? responseMessage?[messageKey.attachments.rawValue] - - let mockedMessage = mockMessage( - responseMessage, - messageType: messageType, - messageId: messageId, - text: text, - user: user, - createdAt: timestamp, - updatedAt: timestamp, - parentId: parentId, - showReplyInChannel: showReplyInChannel, - quotedMessageId: quotedMessageId, - quotedMessage: quotedMessage, - attachments: attachments - ) - - if messageType == .ephemeral { - saveMessage(mockedMessage) - } else { - sendWebsocketMessages( - httpMessage: mockedMessage, - messageText: text, - messageTimestamp: timestamp, - messageType: messageType, - eventType: eventType, - channelReply: showReplyInChannel - ) - } - - responseJson[JSONKey.message] = mockedMessage - trackMessage(text, messageType: messageType, eventType: eventType) - return .ok(.json(responseJson)) - } - - private func sendWebsocketMessages( - httpMessage: [String: Any]?, - messageText: String, - messageTimestamp: String, - messageType: MessageType, - eventType: EventType, - channelReply: Bool? = false - ) { - if let parentId = httpMessage?[messageKey.parentId.rawValue] as? String { - let parentMessage = findMessageById(parentId) - websocketMessage( - parentMessage?[messageKey.text.rawValue] as? String, - channelId: httpMessage?[eventKey.channelId.rawValue] as? String, - messageId: parentId, - timestamp: parentMessage?[messageKey.createdAt.rawValue] as? String, - eventType: .messageUpdated, - user: parentMessage?[JSONKey.user] as? [String: Any] - ) { message in - message?[messageKey.threadParticipants.rawValue] = [httpMessage?[messageKey.user.rawValue]] - return message - } - } - - websocketMessage( - messageText, - channelId: httpMessage?[eventKey.channelId.rawValue] as? String, - messageId: httpMessage?[messageKey.id.rawValue] as? String, - parentId: httpMessage?[messageKey.parentId.rawValue] as? String, - timestamp: messageTimestamp, - messageType: messageType, - eventType: eventType, - user: httpMessage?[messageKey.user.rawValue] as? [String: Any], - channelReply: channelReply ?? false - ) { message in - if let parentId = httpMessage?[messageKey.parentId.rawValue] as? String { - message?[messageKey.parentId.rawValue] = parentId - } - if let showReplyInChannel = httpMessage?[messageKey.showReplyInChannel.rawValue] { - message?[messageKey.showReplyInChannel.rawValue] = showReplyInChannel - } - if let attachments = httpMessage?[messageKey.attachments.rawValue] as? [[String: Any]] { - message?[messageKey.attachments.rawValue] = attachments - } - if let quotedMessageId = httpMessage?[messageKey.quotedMessageId.rawValue] as? String { - let quotedMessage = self.findMessageById(quotedMessageId) - message?[messageKey.quotedMessageId.rawValue] = quotedMessageId - message?[messageKey.quotedMessage.rawValue] = quotedMessage - } - return message - } - } - - private func messageDeletion(messageId: String, channelId: String?, hardDelete: Bool) -> HttpResponse { - var json = TestData.toJson(.message) - let message = findMessageById(messageId) - let timestamp: String = TestData.currentDate - let user = message?[JSONKey.user] as? [String: Any] - let mockedMessage = mockDeletedMessage(message, user: user) - - websocketMessage( - channelId: channelId, - messageId: messageId, - timestamp: timestamp, - eventType: .messageDeleted, - user: user, - hardDelete: hardDelete - ) - - json[JSONKey.message] = mockedMessage - return .ok(.json(json)) - } - - private func messageInfo(messageId: String) -> HttpResponse { - var json = TestData.toJson(.message) - json[JSONKey.message] = findMessageById(messageId) - return .ok(.json(json)) - } - - private func mockMessageReplies(_ request: HttpRequest) throws -> HttpResponse { - let messageId = try XCTUnwrap(request.params[EndpointQuery.messageId]) - var json = "{\"\(JSONKey.messages)\":[]}".json - var threadList = findMessagesByParentId(messageId) - - guard - let parentMessage = findMessageById(messageId), - let limitQueryParam = request.queryParams.first(where: { $0.0 == MessagesPagination.CodingKeys.pageSize.rawValue }) - else { - json[JSONKey.messages] = threadList - return .ok(.json(json)) - } - - let limit = (limitQueryParam.1 as NSString).integerValue - - threadList.insert(parentMessage, at: 0) - json[JSONKey.messages] = mockMessagePagination( - messageList: threadList, - limit: limit, - idLt: request.queryParams.first(where: { $0.0 == paginationKey.lessThan.rawValue })?.1, - idGt: request.queryParams.first(where: { $0.0 == paginationKey.greaterThan.rawValue })?.1, - idLte: request.queryParams.first(where: { $0.0 == paginationKey.lessThanOrEqual.rawValue })?.1, - idGte: request.queryParams.first(where: { $0.0 == paginationKey.greaterThanOrEqual.rawValue })?.1, - around: request.queryParams.first(where: { $0.0 == paginationKey.around.rawValue })?.1 - ) - return .ok(.json(json)) - } - - private func messageInvalidCommand( - _ message: [String: Any]?, - command: String, - channelId: String?, - parentId: String?, - eventType: EventType = .messageRead - ) -> HttpResponse { - let text = Message.message(withInvalidCommand: command) - let messageId = message?[messageKey.id.rawValue] as? String - var responseJson = TestData.toJson(.message) - let responseMessage = responseJson[JSONKey.message] as? [String: Any] - let timestamp: String = TestData.currentDate - let user = setUpUser(source: responseMessage, details: UserDetails.lukeSkywalker) - - websocketMessage( - text, - channelId: channelId, - messageId: messageId, - parentId: parentId, - timestamp: timestamp, - eventType: eventType, - user: user - ) - - let mockedMessage = mockMessage( - responseMessage, - messageType: .error, - channelId: channelId, - messageId: messageId, - text: text, - command: command, - user: user, - createdAt: timestamp, - updatedAt: timestamp, - parentId: parentId - ) - - responseJson[JSONKey.message] = mockedMessage - return .ok(.json(responseJson)) - } - - private func errorMessageHttpResponse( - from message: [String: Any]?, - errorText: String, - channelId: String? - ) -> HttpResponse { - let messageId = message?[messageKey.id.rawValue] as? String - var responseJson = TestData.toJson(.message) - let responseMessage = responseJson[JSONKey.message] as? [String: Any] - let timestamp: String = TestData.currentDate - let user = setUpUser(source: responseMessage, details: UserDetails.lukeSkywalker) - - let mockedMessage = mockMessage( - responseMessage, - messageType: .error, - channelId: channelId, - messageId: messageId, - text: errorText, - user: user, - createdAt: timestamp, - updatedAt: timestamp - ) - - responseJson[JSONKey.message] = mockedMessage - return .ok(.json(responseJson)) - } -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift b/TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift deleted file mode 100644 index 89c1230ea3f..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift +++ /dev/null @@ -1,237 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation -@testable import StreamChat - -public enum Message { - public static func message(withInvalidCommand command: String) -> String { - "Sorry, command \(command) doesn't exist. Try posting your message without the starting /" - } - - public static var blockedByModerationPolicies: String { - "Message was blocked by moderation policies" - } -} - -public enum MockFile: String { - case message = "http_message" - case ephemeralMessage = "http_message_ephemeral" - case httpChatEvent = "http_events" - case httpReaction = "http_reaction" - case httpReplies = "http_replies" - case httpMember = "http_member" - case httpChannels = "http_channels" - case httpAttachment = "http_attachment" - case httpTruncate = "http_truncate" - - case wsMessage = "ws_message" - case wsChatEvent = "ws_events" - case wsChannelEvent = "ws_events_channel" - case wsMemberEvent = "ws_events_member" - case wsReaction = "ws_reaction" - case wsHealthCheck = "ws_health_check" - - case youtube = "http_youtube_link" - case unsplash = "http_unsplash_link" - - case pushNotification = "push_notification" - - var filePath: String { - "\(Bundle.testTools.pathToJSONsFolder)\(rawValue)" - } -} - -public enum MockEndpoint { - public static let connect = "/connect" - public static let messageUpdate = "/messages/\(EndpointQuery.messageId)" - public static let replies = "/messages/\(EndpointQuery.messageId)/replies" - public static let reaction = "/messages/\(EndpointQuery.messageId)/reaction" - public static let reactionUpdate = "/messages/\(EndpointQuery.messageId)/reaction/\(EndpointQuery.reactionType)" - public static let action = "/messages/\(EndpointQuery.messageId)/action" - public static let channels = "/channels" - public static let channel = "/channels/messaging/\(EndpointQuery.channelId)" - public static let event = "/channels/messaging/\(EndpointQuery.channelId)/event" - public static let query = "/channels/\(EndpointQuery.channelType)/\(EndpointQuery.channelId)/query" - public static let messageRead = "/channels/messaging/\(EndpointQuery.channelId)/read" - public static let message = "/channels/messaging/\(EndpointQuery.channelId)/message" - public static let image = "/channels/messaging/\(EndpointQuery.channelId)/image" - public static let file = "/channels/messaging/\(EndpointQuery.channelId)/file" - public static let truncate = "/channels/messaging/\(EndpointQuery.channelId)/truncate" - public static let sync = "/sync" - public static let members = "/members" -} - -public enum EndpointQuery { - public static let messageId = ":message_id" - public static let channelId = ":channel_id" - public static let channelType = ":channel_type" - public static let reactionType = ":reaction_type" -} - -public enum JSONKey { - public static let messages = "messages" - public static let message = "message" - public static let reaction = "reaction" - public static let event = "event" - public static let events = "events" - public static let channels = "channels" - public static let user = "user" - public static let userId = "user_id" - public static let cid = "cid" - public static let channel = "channel" - public static let channelId = "channel_id" - public static let channelType = "channel_type" - public static let config = "config" - public static let createdAt = "created_at" - public static let eventType = "type" - public static let members = "members" - public static let member = "member" - public static let id = "id" - public static let cooldown = "cooldown" - public static let attachmentAction = "image_action" - public static let file = "file" - public static let payload = "payload" - public static let hard = "hard" - - public enum Channel { - public static let addMembers = "add_members" - public static let removeMembers = "remove_members" - public static let truncatedBy = "truncated_by" - } - - public enum AttachmentAction { - public static let send = "send" - public static let shuffle = "shuffle" - } -} - -public enum APNSKey { - public static let aps = "aps" - public static let alert = "alert" - public static let title = "title" - public static let body = "body" - public static let badge = "badge" - public static let stream = "stream" - public static let messageId = "id" - public static let cid = "cid" - public static let mutableContent = "mutable-content" - public static let category = "category" - public static let sender = "sender" - public static let type = "type" - public static let version = "version" -} - -public enum UserDetails { - public static var users: [[String: String]] { - [ - hanSolo, - lukeSkywalker, - countDooku, - leiaOrgana, - landoCalrissian, - chewbacca, - r2d2 - ] - } - - public static let hanSoloId = "han_solo" - public static let hanSoloName = "Han Solo" - public static let hanSolo = [ - userKey.id.rawValue: hanSoloId, - userKey.name.rawValue: hanSoloName, - userKey.imageURL.rawValue: "https://vignette.wikia.nocookie.net/starwars/images/e/e2/TFAHanSolo.png" - ] - - public static let lukeSkywalkerName = "Luke Skywalker" - public static let lukeSkywalkerId = "luke_skywalker" - public static let lukeSkywalkerImageURL = "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg" - public static let lukeSkywalker = [ - userKey.id.rawValue: lukeSkywalkerId, - userKey.name.rawValue: lukeSkywalkerName, - userKey.imageURL.rawValue: lukeSkywalkerImageURL - ] - - public static let countDookuId = "count_dooku" - public static let countDookuName = "Count Dooku" - public static let countDooku = [ - userKey.id.rawValue: "count_dooku", - userKey.name.rawValue: "Count Dooku", - userKey.imageURL.rawValue: "https://vignette.wikia.nocookie.net/starwars/images/b/b8/Dooku_Headshot.jpg" - ] - - public static let leiaOrganaId = "leia_organa" - public static let leiaOrganaName = "Leia Organa" - public static let leiaOrgana = [ - userKey.id.rawValue: leiaOrganaId, - userKey.name.rawValue: "Leia Organa", - userKey.imageURL.rawValue: "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png" - ] - - public static let landoCalrissianId = "lando_calrissian" - public static let landoCalrissianName = "Lando Calrissian" - public static let landoCalrissian = [ - userKey.id.rawValue: "lando_calrissian", - userKey.name.rawValue: "Lando Calrissian", - userKey.imageURL.rawValue: "https://vignette.wikia.nocookie.net/starwars/images/8/8f/Lando_ROTJ.png" - ] - - public static let chewbacca = [ - userKey.id.rawValue: "chewbacca", - userKey.name.rawValue: "Chewbacca", - userKey.imageURL.rawValue: "https://vignette.wikia.nocookie.net/starwars/images/4/48/Chewbacca_TLJ.png" - ] - - public static let r2d2 = [ - userKey.id.rawValue: "r2-d2", - userKey.name.rawValue: "R2-D2", - userKey.imageURL.rawValue: "https://vignette.wikia.nocookie.net/starwars/images/e/eb/ArtooTFA2-Fathead.png" - ] - - public static func unknownUser(withUserId userId: String) -> [String: String] { - [ - userKey.id.rawValue: userId, - userKey.name.rawValue: userName(for: userId), - userKey.imageURL.rawValue: "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png" - ] - } - - public static func userId(for user: [String: String]) -> String { - user[userKey.id.rawValue] ?? leiaOrganaId - } - - public static func userName(for id: String) -> String { - id - .split(separator: "_") - .map { $0.capitalizingFirstLetter() } - .joined(separator: " ") - } - - public static func userTuple(withUserId userId: String) -> (id: String, name: String, url: String) { - let user = user(withUserId: userId) - return ( - user[userKey.id.rawValue] ?? leiaOrganaId, - user[userKey.name.rawValue] ?? "Leia Organa", - user[userKey.imageURL.rawValue] ?? "https://vignette.wikia.nocookie.net/starwars/images/f/fc/Leia_Organa_TLJ.png" - ) - } - - public static func user(withUserId userId: String) -> [String: String] { - guard let user = UserDetails.users.first(where: { $0[userKey.id.rawValue] == userId }) else { - return UserDetails.unknownUser(withUserId: userId) - } - return user - } -} - -public enum Attachments { - public static let image = "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg" - public static let video = "https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_1mb.mp4" - public static let file = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" -} - -public enum Links { - public static let youtube = "youtube.com/" - public static let unsplash = "unsplash.com/" -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift deleted file mode 100644 index fa122eb5d48..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift +++ /dev/null @@ -1,147 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -@testable import StreamChat -import XCTest - -public let reactionKey = MessageReactionPayload.CodingKeys.self - -public extension StreamMockServer { - func configureReactionEndpoints() { - server.register(MockEndpoint.reaction) { [weak self] request in - let messageId = try XCTUnwrap(request.params[EndpointQuery.messageId]) - let requestJson = TestData.toJson(request.body) - let requestReaction = requestJson[JSONKey.reaction] as? [String: Any] - let reactionType = requestReaction?[reactionKey.type.rawValue] as? String - return self?.reactionResponse( - messageId: messageId, - reactionType: reactionType, - eventType: .reactionNew - ) - } - server.register(MockEndpoint.reactionUpdate) { [weak self] request in - let messageId = try XCTUnwrap(request.params[EndpointQuery.messageId]) - let reactionType = try XCTUnwrap(request.params[EndpointQuery.reactionType]) - return self?.reactionResponse( - messageId: messageId, - reactionType: reactionType, - eventType: .reactionDeleted - ) - } - } - - func mockReaction( - _ reaction: [String: Any]?, - fromUser user: [String: Any]?, - messageId: Any?, - reactionType: Any?, - timestamp: Any? - ) -> [String: Any]? { - var mockedReaction = reaction - mockedReaction?[reactionKey.messageId.rawValue] = messageId - mockedReaction?[reactionKey.type.rawValue] = reactionType - mockedReaction?[reactionKey.createdAt.rawValue] = timestamp - mockedReaction?[reactionKey.updatedAt.rawValue] = timestamp - mockedReaction?[reactionKey.user.rawValue] = user - mockedReaction?[reactionKey.userId.rawValue] = user?[userKey.id.rawValue] - return mockedReaction - } - - func mockMessageWithReaction( - _ message: [String: Any]?, - fromUser user: [String: Any]?, - reactionType: String?, - timestamp: String, - deleted: Bool = false - ) -> [String: Any]? { - var mockedMessage = message - let messageId = mockedMessage?[messageKey.id.rawValue] - - if deleted { - mockedMessage?[messageKey.latestReactions.rawValue] = [] - mockedMessage?[messageKey.ownReactions.rawValue] = [] - mockedMessage?[messageKey.reactionCounts.rawValue] = [:] - mockedMessage?[messageKey.reactionScores.rawValue] = [:] - } else { - var latest_reactions = mockedMessage?[messageKey.latestReactions.rawValue] as? [[String: Any]] - var reaction_counts = mockedMessage?[messageKey.reactionCounts.rawValue] as? [String: Any] ?? [:] - var reaction_scores = mockedMessage?[messageKey.reactionScores.rawValue] as? [String: Any] ?? [:] - var reaction_groups = mockedMessage?[messageKey.reactionGroups.rawValue] as? [String: Any] ?? [:] - - var isCurrentUser = false - var newReaction: [String: Any] = [:] - newReaction[reactionKey.messageId.rawValue] = messageId - newReaction[reactionKey.score.rawValue] = 1 - newReaction[reactionKey.createdAt.rawValue] = timestamp - newReaction[reactionKey.updatedAt.rawValue] = timestamp - newReaction[MessageReactionRequestPayload.CodingKeys.enforceUnique.rawValue] = false - - if let reactionType = reactionType { - newReaction[reactionKey.type.rawValue] = reactionType - reaction_counts[reactionType] = 1 - reaction_scores[reactionType] = 1 - reaction_groups[reactionType] = [ - "sum_scores": 1, - "count": 1, - "first_reaction_at": timestamp, - "last_reaction_at": timestamp - ] - } - - if let userId = user?[userKey.id.rawValue] as? String { - newReaction[reactionKey.user.rawValue] = user - newReaction[reactionKey.userId.rawValue] = userId - isCurrentUser = (userId == UserDetails.lukeSkywalker[userKey.id.rawValue]) - } - - latest_reactions?.append(newReaction) - - mockedMessage?[messageKey.ownReactions.rawValue] = isCurrentUser ? latest_reactions : [] - mockedMessage?[messageKey.latestReactions.rawValue] = latest_reactions - mockedMessage?[messageKey.reactionCounts.rawValue] = reaction_counts - mockedMessage?[messageKey.reactionScores.rawValue] = reaction_scores - mockedMessage?[messageKey.reactionGroups.rawValue] = reaction_groups - } - - return mockedMessage - } - - private func reactionResponse( - messageId: String, - reactionType: String?, - eventType: EventType - ) -> HttpResponse { - var json = TestData.toJson(.httpReaction) - let reaction = json[JSONKey.reaction] as? [String: Any] - let message = findMessageById(messageId) - let timestamp = TestData.currentDate - let user = setUpUser(source: message, details: UserDetails.lukeSkywalker) - - let mockedMessage = mockMessageWithReaction( - message, - fromUser: user, - reactionType: reactionType, - timestamp: timestamp, - deleted: eventType == .reactionDeleted - ) - json[JSONKey.message] = mockedMessage - saveMessage(mockedMessage) - - json[JSONKey.reaction] = mockReaction( - reaction, - fromUser: user, - messageId: messageId, - reactionType: reactionType, - timestamp: timestamp - ) - - websocketReaction( - type: TestData.Reactions(rawValue: String(describing: reactionType)), - eventType: eventType, - user: user - ) - - return .ok(.json(json)) - } -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift b/TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift index d0fc55264bf..986df6fa097 100644 --- a/TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift +++ b/TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift @@ -3,151 +3,90 @@ // import Foundation -@testable import StreamChat import XCTest public final class StreamMockServer { - // Delays all HTTP responses by given time interval, 0 by default - public static var httpResponseDelay: TimeInterval = 0.0 - // Waits for all HTTP and Websocket responses during given time interval, 10 by default - public static var waitTimeout = 10.0 - // Expires JWT after given timeout if `MOCK_JWT environment variable is provided + public nonisolated(unsafe) static var url: String? + public nonisolated(unsafe) static var port: String? + private let urlSession: URLSession = URLSession.shared public static let jwtTimeout: UInt32 = 5 - - public private(set) var server: HttpServer = HttpServer() - private weak var globalSession: WebSocketSession? - private var channelConfigs = ChannelConfigs() - public var threadList: [[String: Any]] = [] - public var messageList: [[String: Any]] = [] - public var channelList = TestData.toJson(.httpChannels) - public var currentChannelId = "" - public var channelsEndpointWasCalled = false - public var channelQueryEndpointWasCalled = false - public var allChannelsWereLoaded = false - public var latestWebsocketMessage = "" - public var latestHttpMessage = "" - public let forbiddenWords: Set = ["wth"] - public var pushNotificationPayload: [String: Any] = [:] - public var userDetails: [String: Any]? = [:] - - public init() {} - - public func start(port: UInt16) -> Bool { - do { - try server.start(port) - print("Server status: \(server.state). Port: \(port)") - return true - } catch { - print("Server start error: \(error)") - return false - } + public let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"] ?? "" + public let forbiddenWord: String = "wth" + public let jwtTimeout: UInt32 = 5 + + public init(driverPort: String, testName: String) { + let driverUrl = "http://localhost:\(driverPort)" + let response = getRequest(baseUrl: driverUrl, endpoint: "start/\(testName)") + XCTAssertEqual(200, response.statusCode, "Failed connecting to mock server.") + + let mockServerPort = response.body + StreamMockServer.port = mockServerPort + StreamMockServer.url = driverUrl.replacingOccurrences( + of: driverPort, + with: mockServerPort + ) } public func stop() { - server.stop() - } - - public func configure() { - StreamMockServer.httpResponseDelay = 0.0 - configureWebsockets() - configureEventEndpoints() - configureChannelEndpoints() - configureReactionEndpoints() - configureMessagingEndpoints() - configureAttachmentEndpoints() - configureMembersEndpoints() - } - - public func writeText(_ text: String) { - globalSession?.writeText(text) + getRequest(endpoint: "stop") } - private func configureWebsockets() { - let websocket = websocket(connected: { [weak self] session in - self?.globalSession = session - self?.healthCheck() - }, disconnected: { [weak self] _ in - self?.globalSession = nil - }) - - server.register(MockEndpoint.connect) { [weak self] request in - self?.userDetails = request.queryParams.first { $0.0 == "json" }?.1.removingPercentEncoding?.json - return websocket(request) - } - } - - private func healthCheck() { - writeText(TestData.getMockResponse(fromFile: .wsHealthCheck)) - } -} - -// MARK: Shared - -extension StreamMockServer { - func findChannelById(_ id: String) -> [String: Any]? { - try? XCTUnwrap(waitForChannelWithId(id)) - } - - func waitForChannelWithId(_ id: String) -> [String: Any]? { - let endTime = TestData.waitingEndTime - var newChannelList: [[String: Any]] = [] - while newChannelList.isEmpty && endTime > TestData.currentTimeInterval { - guard let channels = channelList[JSONKey.channels] as? [[String: Any]] else { return nil } - newChannelList = channels.filter { - let channel = $0[JSONKey.channel] as? [String: Any] - return id == channel?[channelKey.id.rawValue] as? String + @discardableResult + public func postRequest( + baseUrl: String = url!, + endpoint: String, + body: Data = Data(), + async: Bool = false + ) -> (body: String, statusCode: Int) { + let url = URL(string: "\(baseUrl)/\(endpoint)")! + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.httpBody = body + request.setValue("text/plain", forHTTPHeaderField: "Content-Type") + nonisolated(unsafe) var output = "" + nonisolated(unsafe) var statusCode = 0 + + let semaphore: DispatchSemaphore? = async ? nil : DispatchSemaphore(value: 0) + let task = URLSession.shared.dataTask(with: request) { data, response, _ in + if let httpResponse = response as? HTTPURLResponse { + statusCode = httpResponse.statusCode } + if let data = data, let string = String(data: data, encoding: .utf8) { + output = string + } + semaphore?.signal() } - return newChannelList.first - } -} - -// MARK: Config + task.resume() + semaphore?.wait() -public extension StreamMockServer { - func config(forChannelId id: String) -> ChannelConfig_Mock? { - channelConfigs.config(forChannelId: id, server: self) + return (output, statusCode) } - func updateConfig(config: ChannelConfig_Mock, forChannelWithId id: String) { - channelConfigs.updateConfig(config: config, forChannelWithId: id, server: self) - } - - func updateConfig(in channel: inout [String: Any], withId id: String) { - channelConfigs.updateChannel(channel: &channel, withId: id) - } -} - -public extension StreamMockServer { - func setCooldown(enabled: Bool, duration: Int, inChannelWithId id: String) { - channelConfigs.setCooldown(enabled: enabled, duration: duration) - - var json = channelList - guard - var channels = json[JSONKey.channels] as? [[String: Any]], - let channelIndex = channelIndex(withId: id), - var channel = channel(withId: id), - var innerChannel = channel[JSONKey.channel] as? [String: Any] - else { - return + @discardableResult + public func getRequest( + baseUrl: String = url!, + endpoint: String, + async: Bool = false + ) -> (body: String, statusCode: Int) { + let url = URL(string: "\(baseUrl)/\(endpoint)")! + var request = URLRequest(url: url) + request.httpMethod = "GET" + nonisolated(unsafe) var output = "" + nonisolated(unsafe) var statusCode = 0 + + let semaphore: DispatchSemaphore? = async ? nil : DispatchSemaphore(value: 0) + let task = URLSession.shared.dataTask(with: request) { data, response, _ in + if let httpResponse = response as? HTTPURLResponse { + statusCode = httpResponse.statusCode + } + if let data = data, let string = String(data: data, encoding: .utf8) { + output = string + } + semaphore?.signal() } + task.resume() + semaphore?.wait() - setCooldown(in: &innerChannel) - channel[JSONKey.channel] = innerChannel - channels[channelIndex] = channel - json[JSONKey.channels] = channels - channelList = json - } - - func setCooldown(in channel: inout [String: Any]) { - let cooldown = channelConfigs.coolDown - if cooldown.isEnabled { - channel[channelKey.cooldownDuration.rawValue] = cooldown.duration - var ownCapabilities = channel[channelKey.ownCapabilities.rawValue] as? [String] - ownCapabilities?.removeAll { $0 == ChannelCapability.skipSlowMode.rawValue } - channel[channelKey.ownCapabilities.rawValue] = ownCapabilities - } else { - channel[channelKey.cooldownDuration.rawValue] = nil - } + return (output, statusCode) } } diff --git a/TestTools/StreamChatTestMockServer/MockServer/User.swift b/TestTools/StreamChatTestMockServer/MockServer/User.swift deleted file mode 100644 index 38ea9fc77bd..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/User.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -@testable import StreamChat - -public let userKey = UserPayloadsCodingKeys.self - -public extension StreamMockServer { - func setUpUser( - source: [String: Any]?, - details: [String: String] = [:] - ) -> [String: Any]? { - guard let user = source?[JSONKey.user] as? [String: Any] else { return nil } - - return user.merging(details) { $1 } - } - - func memberJSONs(for ids: [String]) -> [[String: Any]] { - ids.map { - let user = UserDetails.userTuple(withUserId: $0) - var member = TestData.toJson(.httpMember) - member[JSONKey.userId] = user.id - member[userKey.id.rawValue] = user.name - member[userKey.name.rawValue] = user.name - - if var userJSON = member[JSONKey.user] as? [String: Any] { - userJSON[userKey.id.rawValue] = user.id - userJSON[userKey.name.rawValue] = user.name - userJSON[userKey.imageURL.rawValue] = user.url - member[JSONKey.user] = userJSON - } - return member - } - } -} diff --git a/TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift b/TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift deleted file mode 100644 index 654ca8b63b9..00000000000 --- a/TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift +++ /dev/null @@ -1,267 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation -@testable import StreamChat - -public extension StreamMockServer { - /// Sends an event over a websocket connection - /// - /// - Parameters: - /// - EventType - /// - [String : Any]: user who triggered an event - /// - Returns: Self - @discardableResult - func websocketEvent( - _ eventType: EventType, - user: [String: Any]?, - channelId: String, - channel: [String: Any]? = nil, - parentMessageId: String? = nil - ) -> Self { - var json = TestData.getMockResponse(fromFile: .wsChatEvent).json - json[eventKey.user.rawValue] = user - json[eventKey.createdAt.rawValue] = TestData.currentDate - json[eventKey.eventType.rawValue] = eventType.rawValue - json[eventKey.channelId.rawValue] = channelId - json[eventKey.channelType.rawValue] = ChannelType.messaging.rawValue - json[eventKey.parentId.rawValue] = parentMessageId - json[eventKey.cid.rawValue] = "\(ChannelType.messaging.rawValue):\(channelId)" - - if let channel = channel { json[JSONKey.channel] = channel } - - writeText(json.jsonToString()) - return self - } - - /// Manages the lifecycle of the messages over a websocket connection - /// - /// - Parameters: - /// - String: the text that will be used in the message (empty by default for deleted messages) - /// - String: messageId that was assigned to the message - /// - String: timestamp when the message was created and/or updated - /// - EventType: what needs to be done with the message - /// - [String : Any]: user who sent the message - /// - Returns: Self - @discardableResult - func websocketMessage( - _ text: String? = "", - channelId: String? = nil, - messageId: String?, - parentId: String? = nil, - timestamp: String? = TestData.currentDate, - messageType: MessageType = .regular, - eventType: EventType, - user: [String: Any]?, - channel: [String: Any]? = nil, - channelReply: Bool = false, - hardDelete: Bool = false, - intercept: ((inout [String: Any]?) -> [String: Any]?)? = nil - ) -> Self { - guard let messageId = messageId else { return self } - - let mockFile = messageType == .ephemeral ? MockFile.ephemeralMessage : MockFile.wsMessage - var json = TestData.getMockResponse(fromFile: mockFile).json - var mockedMessage: [String: Any]? - - switch eventType { - case .messageNew: - mockedMessage = mockMessage( - json[JSONKey.message] as? [String: Any], - messageType: messageType, - channelId: channelId, - messageId: messageId, - text: text, - user: user, - createdAt: timestamp, - updatedAt: timestamp - ) - mockedMessage = intercept?(&mockedMessage) ?? mockedMessage - if messageType == .ephemeral { - var attachments = mockedMessage?[messageKey.attachments.rawValue] as? [[String: Any]] - attachments?[0][GiphyAttachmentSpecificCodingKeys.actions.rawValue] = nil - mockedMessage?[messageKey.attachments.rawValue] = attachments - mockedMessage?[messageKey.type.rawValue] = parentId == nil || channelReply ? MessageType.regular.rawValue : MessageType.reply.rawValue - } - parentId == nil ? saveMessage(mockedMessage) : saveReply(mockedMessage) - case .messageDeleted: - let message = findMessageById(messageId) - mockedMessage = mockDeletedMessage(message, user: user) - mockedMessage = intercept?(&mockedMessage) ?? mockedMessage - if hardDelete { - removeMessage(id: messageId) - } else if isMessageInList(messageList, message: mockedMessage) { - saveMessage(mockedMessage) - } else { - saveReply(mockedMessage) - } - case .messageUpdated: - let message = findMessageById(messageId) - mockedMessage = mockMessage( - message, - channelId: channelId, - messageId: message?[messageKey.id.rawValue] as? String, - text: text, - user: user, - createdAt: message?[messageKey.createdAt.rawValue] as? String, - updatedAt: timestamp - ) - mockedMessage = intercept?(&mockedMessage) ?? mockedMessage - parentId == nil ? saveMessage(mockedMessage) : saveReply(mockedMessage) - default: - mockedMessage = [:] - } - - if let parentMessageId = parentId { - var parentMessage = messageList.first { message in - (message[messageKey.id.rawValue] as? String) ?? "" == parentMessageId - } - let previousReplyCount = (parentMessage?[messageKey.replyCount.rawValue] as? Int) ?? 0 - parentMessage?[messageKey.replyCount.rawValue] = previousReplyCount + 1 - saveMessage(parentMessage) - } - - if let channelId = channelId { - json[JSONKey.channelId] = channelId - json[JSONKey.channelType] = ChannelType.messaging.rawValue - json[JSONKey.cid] = "\(ChannelType.messaging.rawValue):\(channelId)" - } - - if let channel = channel { json[JSONKey.channel] = channel } - - json[JSONKey.user] = user - json[JSONKey.message] = mockedMessage - json[messageKey.createdAt.rawValue] = TestData.currentDate - json[messageKey.type.rawValue] = eventType.rawValue - if hardDelete { json[eventKey.hardDelete.rawValue] = true } - - writeText(json.jsonToString()) - if eventType == .messageNew { latestWebsocketMessage = text ?? "" } - - return self - } - - /// Manages the lifecycle of the reactions over a websocket connection - /// - /// - Parameters: - /// - TestData.Reactions: the reaction that will be used - /// - EventType: what needs to be done with reaction - /// - [String : Any]: user who sent the reaction - /// - Returns: Self - @discardableResult - func websocketReaction( - type: TestData.Reactions?, - eventType: EventType, - user: [String: Any]? - ) -> Self { - let messageDetails = lastMessage - var json = TestData.getMockResponse(fromFile: .wsReaction).json - var reaction = json[JSONKey.reaction] as? [String: Any] - var message = json[JSONKey.message] as? [String: Any] - let messageId = messageDetails?[messageKey.id.rawValue] as? String - let cid = messageDetails?[messageKey.cid.rawValue] as? String - let channelId = cid?.split(separator: ":").last - let timestamp = TestData.currentDate - - message = mockMessageWithReaction( - messageDetails, - fromUser: user, - reactionType: type?.rawValue, - timestamp: timestamp, - deleted: eventType == .reactionDeleted - ) - - reaction = mockReaction( - reaction, - fromUser: user, - messageId: messageId, - reactionType: type?.rawValue, - timestamp: timestamp - ) - - json[JSONKey.channelId] = channelId - json[JSONKey.cid] = cid - json[JSONKey.message] = message - json[JSONKey.reaction] = reaction - json[reactionKey.user.rawValue] = user - json[reactionKey.createdAt.rawValue] = TestData.currentDate - json[reactionKey.type.rawValue] = eventType.rawValue - - saveMessage(message) - writeText(json.jsonToString()) - return self - } -} - -// MARK: Channel Members - -public extension StreamMockServer { - /// Adds new members to channel - /// - /// - Parameters: - /// - members: json representation of members - /// - channelId: channel id - /// - timestamp: event timestamp - /// - EventType: what needs to be done with the message - /// - user: user who created the channel - /// - Returns: Self - @discardableResult - func websocketChannelUpdated( - with members: [[String: Any]], - channelId: String, - timestamp: String? = TestData.currentDate - ) -> Self { - var json = TestData.getMockResponse(fromFile: .wsChannelEvent).json - - // updated config with current values - updateConfig(in: &json, withId: channelId) - - json[JSONKey.channelId] = channelId - json[JSONKey.cid] = "\(ChannelType.messaging.rawValue):\(channelId)" - json[JSONKey.channelType] = ChannelType.messaging.rawValue - json[JSONKey.createdAt] = TestData.currentDate - json[JSONKey.eventType] = EventType.channelUpdated.rawValue - json[JSONKey.user] = setUpUser(source: json, details: UserDetails.lukeSkywalker) - - if var channel = json[JSONKey.channel] as? [String: Any] { - channel[JSONKey.members] = members - channel[channelKey.memberCount.rawValue] = members.count - - json[channelKey.members.rawValue] = members - json[channelKey.memberCount.rawValue] = members.count - json[JSONKey.channel] = channel - } - - writeText(json.jsonToString()) - return self - } - - /// Events: member.added, member.updatem member.removed - /// - /// - Parameters: - /// - member: json representation of member - /// - channelId: channel id - /// - timestamp: event timestamp - /// - EventType: what needs to be done with the message - /// - Returns: Self - @discardableResult - func websocketMember( - with member: [String: Any], - channelId: String, - timestamp: String? = TestData.currentDate, - eventType: EventType - ) -> Self { - var json = TestData.getMockResponse(fromFile: .wsMemberEvent).json - json[JSONKey.channelId] = channelId - json[JSONKey.cid] = "\(ChannelType.messaging.rawValue):\(channelId)" - json[JSONKey.channelType] = ChannelType.messaging.rawValue - json[JSONKey.createdAt] = TestData.currentDate - json[JSONKey.eventType] = eventType.rawValue - json[JSONKey.member] = member - json[JSONKey.user] = member[JSONKey.user] - - writeText(json.jsonToString()) - return self - } -} diff --git a/TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift b/TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift index 08c6d8844e5..ae5a3f41384 100644 --- a/TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift +++ b/TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift @@ -6,75 +6,116 @@ import Foundation import XCTest public class BackendRobot { - private var server: StreamMockServer + private let mockServer: StreamMockServer - public init(_ server: StreamMockServer) { - self.server = server + public init(_ mockServer: StreamMockServer) { + self.mockServer = mockServer } @discardableResult - public func delayServerResponse(byTimeInterval timeInterval: TimeInterval) -> Self { - StreamMockServer.httpResponseDelay = timeInterval + public func generateChannels( + channelsCount: Int, + messagesCount: Int = 0, + repliesCount: Int = 0, + attachments: Bool = false, + messagesText: String? = nil, + repliesText: String? = nil + ) -> BackendRobot { + waitForMockServerToStart() + var messagesTextQueryParam = "" + if let messagesText { + messagesTextQueryParam = "messages_text=\(messagesText)&" + } + var repliesTextQueryParam = "" + if let repliesText { + repliesTextQueryParam = "replies_text=\(repliesText)&" + } + var attachmentsQueryParam = "" + if attachments { + attachmentsQueryParam = "attachments=true&" + } + let endpoint = "mock?" + + messagesTextQueryParam + + repliesTextQueryParam + + attachmentsQueryParam + + "channels=\(channelsCount)&" + + "messages=\(messagesCount)&" + + "replies=\(repliesCount)" + _ = mockServer.postRequest(endpoint: endpoint) return self } @discardableResult - public func setReadEvents(to value: Bool) -> Self { - let id = server.currentChannelId.isEmpty ? server.getFirstChannelId() : server.currentChannelId - guard var config = server.config(forChannelId: id) else { - return self - } - config.readEvents = value - server.updateConfig(config: config, forChannelWithId: id) + public func failNewMessages() -> BackendRobot { + _ = mockServer.postRequest(endpoint: "fail_messages") return self } - + @discardableResult - public func setCooldown(enabled value: Bool, duration: Int) -> Self { - let id = server.currentChannelId.isEmpty ? server.getFirstChannelId() : server.currentChannelId - server.setCooldown(enabled: value, duration: duration, inChannelWithId: id) + public func delayNewMessages(by seconds: Int) -> BackendRobot { + _ = mockServer.postRequest(endpoint: "delay_messages?delay=\(seconds)") return self } - @discardableResult - public func generateChannels( - count: Int, - messageText: String? = nil, - messagesCount: Int = 0, - replyCount: Int = 0, - authorDetails: [String: String] = UserDetails.lukeSkywalker, - memberDetails: [[String: String]] = [ - UserDetails.lukeSkywalker, - UserDetails.hanSolo, - UserDetails.countDooku - ], - withAttachments: Bool = false - ) -> Self { - var json = server.channelList - guard let sampleChannel = (json[JSONKey.channels] as? [[String: Any]])?.first else { return self } + public func revokeToken(duration: Int = 5) { + waitForMockServerToStart() + _ = mockServer.postRequest(endpoint: "jwt/revoke_token?duration=\(duration)") + } - let userSources = TestData.toJson(.httpChatEvent)[JSONKey.event] as? [String: Any] + public func invalidateToken(duration: Int = 5) { + waitForMockServerToStart() + _ = mockServer.postRequest(endpoint: "jwt/invalidate_token?duration=\(duration)") + } - let members = server.mockMembers( - userSources: userSources, - sampleChannel: sampleChannel, - memberDetails: memberDetails - ) + public func invalidateTokenDate(duration: Int = 5) { + waitForMockServerToStart() + _ = mockServer.postRequest(endpoint: "jwt/invalidate_token_date?duration=\(duration)") + } - let author = server.setUpUser(source: userSources, details: authorDetails) - let channels = server.mockChannels( - count: count, - messageText: messageText, - messagesCount: messagesCount, - replyCount: replyCount, - author: author, - members: members, - sampleChannel: sampleChannel, - withAttachments: withAttachments - ) + public func invalidateTokenSignature(duration: Int = 5) { + waitForMockServerToStart() + _ = mockServer.postRequest(endpoint: "jwt/invalidate_token_signature?duration=\(duration)") + } - json[JSONKey.channels] = channels - server.channelList = json - return self + public func breakTokenGeneration(duration: Int = 5) { + waitForMockServerToStart() + _ = mockServer.postRequest(endpoint: "jwt/break_token_generation?duration=\(duration)") + } + + public func setReadEvents(to value: Bool) { + waitForMockServerToStart() + _ = mockServer.postRequest(endpoint: "config/read_events?value=\(value)") + } + + public func setCooldown(enabled: Bool, duration: Int) { + waitForMockServerToStart() + _ = mockServer.postRequest(endpoint: "config/cooldown?enabled=\(enabled)&duration=\(duration)") + } + + private func waitForMockServerToStart() { + let startTime = Date().timeIntervalSince1970 + while Date().timeIntervalSince1970 - startTime < 5.0 { + var request = URLRequest(url: URL(string: "\(StreamMockServer.url!)/ping")!) + request.httpMethod = "GET" + request.timeoutInterval = 1.0 + + let semaphore = DispatchSemaphore(value: 0) + var responseCode: Int? + let task = URLSession.shared.dataTask(with: request) { _, response, _ in + if let httpResponse = response as? HTTPURLResponse { + responseCode = httpResponse.statusCode + } + semaphore.signal() + } + task.resume() + semaphore.wait() + + if responseCode == 200 { + return + } + + Thread.sleep(forTimeInterval: 0.5) + } + XCTFail("MockServer did not start within 5 seconds") } } diff --git a/TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift b/TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift index e48beca56f1..e2e7b14cfd7 100644 --- a/TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift +++ b/TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift @@ -2,472 +2,247 @@ // Copyright © 2026 Stream.io Inc. All rights reserved. // -@testable import StreamChat -import XCTest +import Foundation public class ParticipantRobot { - private var server: StreamMockServer - private var threadParentId: String? - private var user: [String: String] = UserDetails.countDooku + private let mockServer: StreamMockServer - public init(_ server: StreamMockServer) { - self.server = server - } + public let name: String = "Count Dooku" + public let id: String = "count_dooku" - public var currentUserId: String { - UserDetails.userId(for: user) + public init(_ mockServer: StreamMockServer) { + self.mockServer = mockServer } @discardableResult - public func startTyping() -> Self { - server.websocketEvent( - .userStartTyping, - user: participant(), - channelId: server.currentChannelId - ) + public func startTyping() -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/typing/start") return self } @discardableResult - public func startTypingInThread() -> Self { - let parentId = threadParentId ?? (server.lastMessage?[messageKey.id.rawValue] as? String) - server.websocketEvent( - .userStartTyping, - user: participant(), - channelId: server.currentChannelId, - parentMessageId: parentId - ) + public func startTypingInThread() -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/typing/start?thread=true") return self } @discardableResult - public func stopTyping() -> Self { - server.websocketEvent( - .userStopTyping, - user: participant(), - channelId: server.currentChannelId - ) + public func stopTyping() -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/typing/stop") return self } @discardableResult - public func stopTypingInThread() -> Self { - let parentId = threadParentId ?? (server.lastMessage?[messageKey.id.rawValue] as? String) - server.websocketEvent( - .userStopTyping, - user: participant(), - channelId: server.currentChannelId, - parentMessageId: parentId - ) + public func stopTypingInThread() -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/typing/stop?thread=true") return self } - // Sleep in seconds @discardableResult - public func wait(_ duration: TimeInterval) -> Self { - let sleepTime = UInt32(duration * 1_000_000) - usleep(sleepTime) + public func sleep(_ timeOutSeconds: TimeInterval) -> ParticipantRobot { + Thread.sleep(forTimeInterval: timeOutSeconds) return self } @discardableResult - public func readMessage(parentId: String? = nil) -> Self { - server.waitForChannelsUpdate() - - server.websocketEvent( - .messageRead, - user: participant(), - channelId: server.currentChannelId, - parentMessageId: parentId - ) + public func readMessage() -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/read") return self } @discardableResult - public func readMessageAfterDelay(_ delay: Double = 1, parentId: String? = nil) -> Self { - wait(delay) - return readMessage(parentId: parentId) - } - - @discardableResult - public func readMessageInThreadAfterDelay(_ delay: Double = 1) -> Self { - let parentId = threadParentId ?? (server.lastMessage?[messageKey.id.rawValue] as? String) - return readMessageAfterDelay(parentId: parentId) + public func sendMessage(_ text: String, delay: Int = 0) -> ParticipantRobot { + var endpoint = "participant/message" + if delay > 0 { + endpoint += "?delay=\(delay)" + } + let body = text.data(using: .utf8) ?? Data() + _ = mockServer.postRequest(endpoint: endpoint, body: body) + return self } @discardableResult - public func sendMessage( - _ text: String, - withPushNotification: Bool = false, - bundleIdForPushNotification: String = "", - waitForAppearance: Bool = true, - waitForChannelQuery: Bool = true, - waitBeforeSending: TimeInterval = 0, - file: StaticString = #filePath, - line: UInt = #line - ) -> Self { - if waitBeforeSending > 0 { - wait(waitBeforeSending) + public func sendPushNotification( + title: String?, + body: String, + bundleId: String, + rest: String? = nil + ) -> ParticipantRobot { + let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"]! + var endpoint = "participant/push?" + + "bundle_id=\(bundleId)&" + + "udid=\(udid)&" + + "body=\(body)" + if let title { + endpoint += "&title=\(title)" } - - if waitForChannelQuery { - server.waitForChannelQueryUpdate() - } - - startTyping() - stopTyping() - - let messageId = TestData.uniqueId - - server.websocketMessage( - text, - channelId: server.currentChannelId, - messageId: messageId, - eventType: .messageNew, - user: participant() - ) - - if waitForAppearance { - server.waitForWebsocketMessage(withText: text) - } - - if withPushNotification { - if let senderName = participant()?["name"] as? String { - server.pushNotification( - senderName: senderName, - text: text, - messageId: messageId, - cid: "\(ChannelType.messaging.rawValue):\(server.currentChannelId)", - targetBundleId: bundleIdForPushNotification - ) - } + if let rest { + endpoint += "&rest=\(rest)" } + _ = mockServer.postRequest(endpoint: endpoint) return self } - /// The given text will be decorated with the index, eg "message-10" @discardableResult - public func sendMultipleMessages(repeatingText text: String, count: Int) -> Self { + public func sendMultipleMessages(_ text: String, count: Int) -> ParticipantRobot { var texts = [String]() for index in 1...count { texts.append("\(text)-\(index)") } texts.forEach { - sendMessage($0, waitForAppearance: false) - wait(0.3) + sendMessage($0) + sleep(0.3) } return self } @discardableResult - public func editMessage(_ text: String) -> Self { - let messageId = server.lastMessage?[messageKey.id.rawValue] as? String - server.websocketMessage( - text, - channelId: server.currentChannelId, - messageId: messageId, - eventType: .messageUpdated, - user: participant() + public func sendMessageInThread(_ text: String, alsoSendInChannel: Bool = false) -> ParticipantRobot { + let body = text.data(using: .utf8) ?? Data() + _ = mockServer.postRequest( + endpoint: "participant/message?thread=true&thread_and_channel=\(alsoSendInChannel)", + body: body ) return self } @discardableResult - public func deleteMessage(hard: Bool = false) -> Self { - let user = participant() - guard let userId = user?[userKey.id.rawValue] as? String else { return self } - let message = server.findMessageByUserId(userId) - let messageId = message?[messageKey.id.rawValue] as? String - server.websocketMessage( - channelId: server.currentChannelId, - messageId: messageId, - eventType: .messageDeleted, - user: user, - hardDelete: hard + public func editMessage(_ text: String) -> ParticipantRobot { + let body = text.data(using: .utf8) ?? Data() + _ = mockServer.postRequest( + endpoint: "participant/message?action=edit", + body: body ) return self } @discardableResult - public func addReaction(type: TestData.Reactions) -> Self { - server.websocketReaction( - type: type, - eventType: .reactionNew, - user: participant() - ) + public func deleteMessage(hard: Bool = false) -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/message?action=delete&hard_delete=\(hard)") return self } @discardableResult - public func deleteReaction(type: TestData.Reactions) -> Self { - server.websocketReaction( - type: type, - eventType: .reactionDeleted, - user: participant() - ) + public func quoteMessage(_ text: String, last: Bool = true) -> ParticipantRobot { + let quote = last ? "quote_last=true" : "quote_first=true" + let body = text.data(using: .utf8) ?? Data() + _ = mockServer.postRequest(endpoint: "participant/message?\(quote)", body: body) return self } - - @discardableResult - public func replyToMessage(_ text: String, withAttachment: Bool, toLastMessage: Bool = true) -> Self { - startTyping() - stopTyping() - let quotedMessage = toLastMessage ? server.lastMessage : server.firstMessage - let quotedMessageId = quotedMessage?[messageKey.id.rawValue] as? String - server.websocketMessage( - text, - channelId: server.currentChannelId, - messageId: TestData.uniqueId, - eventType: .messageNew, - user: participant() - ) { message in - message?[messageKey.quotedMessageId.rawValue] = quotedMessageId - message?[messageKey.quotedMessage.rawValue] = quotedMessage - return message - } + @discardableResult + public func quoteMessageInThread( + _ text: String, + alsoSendInChannel: Bool = false, + last: Bool = true + ) -> ParticipantRobot { + let quote = last ? "quote_last=true" : "quote_first=true" + let body = text.data(using: .utf8) ?? Data() + _ = mockServer.postRequest( + endpoint: "participant/message?\(quote)&thread=true&thread_and_channel=\(alsoSendInChannel)", + body: body + ) return self } @discardableResult - public func quoteMessage(_ text: String, toLastMessage: Bool = true) -> Self { - startTyping() - stopTyping() - - let quotedMessage = toLastMessage ? server.lastMessage : server.firstMessage - let quotedMessageId = quotedMessage?[messageKey.id.rawValue] as? String - server.websocketMessage( - text, - channelId: server.currentChannelId, - messageId: TestData.uniqueId, - eventType: .messageNew, - user: participant() - ) { message in - message?[messageKey.quotedMessageId.rawValue] = quotedMessageId - message?[messageKey.quotedMessage.rawValue] = quotedMessage - return message - } + public func uploadGiphy() -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/message?giphy=true") return self } @discardableResult - public func replyToMessageInThread(_ text: String, alsoSendInChannel: Bool = false) -> Self { - startTypingInThread() - stopTypingInThread() - - let parentId = threadParentId ?? (server.lastMessage?[messageKey.id.rawValue] as? String) - server.websocketMessage( - text, - channelId: server.currentChannelId, - messageId: TestData.uniqueId, - parentId: parentId, - messageType: alsoSendInChannel ? .regular : .reply, - eventType: .messageNew, - user: participant(), - channelReply: alsoSendInChannel - ) { message in - message?[messageKey.parentId.rawValue] = parentId - message?[messageKey.showReplyInChannel.rawValue] = alsoSendInChannel - return message - } + public func uploadGiphyInThread() -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/message?giphy=true&thread=true") return self } - - @discardableResult - public func quoteMessageInThread(_ text: String, toLastMessage: Bool = true, alsoSendInChannel: Bool = false) -> Self { - startTypingInThread() - stopTypingInThread() - let parentId = threadParentId ?? (server.lastMessage?[messageKey.id.rawValue] as? String) - let quotedMessage = toLastMessage ? server.lastMessageInThread : server.firstMessageInThread - let quotedMessageId = quotedMessage?[messageKey.id.rawValue] as? String - server.websocketMessage( - text, - channelId: server.currentChannelId, - messageId: TestData.uniqueId, - parentId: parentId, - messageType: alsoSendInChannel ? .regular : .reply, - eventType: .messageNew, - user: participant(), - channelReply: alsoSendInChannel - ) { message in - message?[messageKey.parentId.rawValue] = parentId - message?[messageKey.showReplyInChannel.rawValue] = alsoSendInChannel - message?[messageKey.quotedMessageId.rawValue] = quotedMessageId - message?[messageKey.quotedMessage.rawValue] = quotedMessage - return message - } + @discardableResult + public func quoteMessageWithGiphy(last: Bool = true) -> ParticipantRobot { + let quote = last ? "quote_last=true" : "quote_first=true" + _ = mockServer.postRequest(endpoint: "participant/message?giphy=true&\(quote)") return self } @discardableResult - public func sendGiphy(waitForChannelQuery: Bool = true, waitBeforeSending: TimeInterval = 0) -> Self { - if waitBeforeSending > 0 { - wait(waitBeforeSending) - } - - if waitForChannelQuery { - server.waitForChannelQueryUpdate() - } - - startTyping() - stopTyping() - - server.websocketMessage( - channelId: server.currentChannelId, - messageId: TestData.uniqueId, - messageType: .ephemeral, - eventType: .messageNew, - user: participant() - ) + public func quoteMessageWithGiphyInThread( + alsoSendInChannel: Bool = false, + last: Bool = true + ) -> ParticipantRobot { + let quote = last ? "quote_last=true" : "quote_first=true" + let endpoint = "participant/message?giphy=true&\(quote)&thread=true&thread_and_channel=\(alsoSendInChannel)" + _ = mockServer.postRequest(endpoint: endpoint) return self } @discardableResult - public func replyWithGiphy(toLastMessage: Bool = true) -> Self { - startTyping() - stopTyping() - - let quotedMessage = toLastMessage ? server.lastMessage : server.firstMessage - let quotedMessageId = quotedMessage?[messageKey.id.rawValue] as? String - server.websocketMessage( - channelId: server.currentChannelId, - messageId: TestData.uniqueId, - messageType: .ephemeral, - eventType: .messageNew, - user: participant() - ) { message in - message?[messageKey.quotedMessageId.rawValue] = quotedMessageId - message?[messageKey.quotedMessage.rawValue] = quotedMessage - return message - } + public func pinMesage() -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/message?action=pin") return self } @discardableResult - public func replyWithGiphyInThread(toLastMessage: Bool = true, alsoSendInChannel: Bool = false) -> Self { - startTypingInThread() - stopTypingInThread() - - let quotedMessage = toLastMessage ? server.lastMessageInThread : server.firstMessageInThread - let quotedMessageId = quotedMessage?[messageKey.id.rawValue] as? String - let parentId = threadParentId ?? (server.lastMessage?[messageKey.id.rawValue] as? String) - server.websocketMessage( - channelId: server.currentChannelId, - messageId: TestData.uniqueId, - parentId: parentId, - messageType: .ephemeral, - eventType: .messageNew, - user: participant() - ) { message in - message?[messageKey.parentId.rawValue] = parentId - message?[messageKey.showReplyInChannel.rawValue] = alsoSendInChannel - message?[messageKey.quotedMessageId.rawValue] = quotedMessageId - message?[messageKey.quotedMessage.rawValue] = quotedMessage - return message - } + public func unpinMesage() -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/message?action=unpin") return self } - private func participant() -> [String: Any]? { - let json = TestData.toJson(.message) - guard let message = json[JSONKey.message] as? [String: Any] else { - return nil - } - - return server.setUpUser(source: message, details: user) + @discardableResult + public func uploadAttachment(type: AttachmentType, count: Int = 1) -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/message?\(type.attachment)=\(count)") + return self } @discardableResult - public func uploadAttachment( + public func quoteMessageWithAttachment( type: AttachmentType, count: Int = 1, - asReplyToFirstMessage: Bool = false, - asReplyToLastMessage: Bool = false, - inThread: Bool = false, - alsoInChannel: Bool = false, - waitForAppearance: Bool = true, - waitForChannelQuery: Bool = true, - waitBeforeSending: TimeInterval = 0, - file: StaticString = #filePath, - line: UInt = #line - ) -> Self { - if waitBeforeSending > 0 { - wait(waitBeforeSending) - } - - if waitForChannelQuery { - server.waitForChannelQueryUpdate() - } - - var parentId: String? - - if inThread { - startTypingInThread() - stopTypingInThread() - parentId = self.threadParentId ?? (self.server.lastMessage?[messageKey.id.rawValue] as? String) - } else { - startTyping() - stopTyping() - } - - server.websocketMessage( - channelId: inThread ? nil : server.currentChannelId, - messageId: TestData.uniqueId, - parentId: parentId, - messageType: !inThread ? .regular : .reply, - eventType: .messageNew, - user: participant() - ) { message in - var attachments: [[String: Any]] = [] - var file: [String: Any] = [:] - file[AttachmentCodingKeys.type.rawValue] = type.rawValue + last: Bool = true + ) -> ParticipantRobot { + let quote = last ? "quote_last=true" : "quote_first=true" + _ = mockServer.postRequest(endpoint: "participant/message?\(quote)&\(type.attachment)=\(count)") + return self + } - switch type { - case .image: - file[AttachmentCodingKeys.imageURL.rawValue] = Attachments.image - case .video: - file[AttachmentCodingKeys.assetURL.rawValue] = Attachments.video - file[AttachmentFile.CodingKeys.mimeType.rawValue] = "video/mp4" - default: - file[AttachmentCodingKeys.assetURL.rawValue] = Attachments.file - file[AttachmentFile.CodingKeys.mimeType.rawValue] = "application/pdf" - } + @discardableResult + public func uploadAttachmentInThread( + type: AttachmentType, + count: Int = 1, + alsoSendInChannel: Bool = false + ) -> ParticipantRobot { + let endpoint = "participant/message?\(type.attachment)=\(count)&thread=true&thread_and_channel=\(alsoSendInChannel)" + _ = mockServer.postRequest(endpoint: endpoint) + return self + } - if type != .image { - file[AttachmentFile.CodingKeys.size.rawValue] = 123_456 - } + @discardableResult + public func quoteMessageWithAttachmentInThread( + type: AttachmentType, + count: Int = 1, + alsoSendInChannel: Bool = false, + last: Bool = true + ) -> ParticipantRobot { + let quote = last ? "quote_last=true" : "quote_first=true" + let endpoint = "participant/message?" + + "\(quote)&\(type.attachment)=\(count)&thread=true&thread_and_channel=\(alsoSendInChannel)" + _ = mockServer.postRequest(endpoint: endpoint) + return self + } - for i in 1...count { - file[AttachmentCodingKeys.title.rawValue] = "\(type.rawValue)_\(i)" - attachments.append(file) - } - - if asReplyToFirstMessage || asReplyToLastMessage { - let quotedMessage: [String: Any]? - if inThread { - quotedMessage = asReplyToLastMessage ? self.server.lastMessageInThread : self.server.firstMessageInThread - } else { - quotedMessage = asReplyToLastMessage ? self.server.lastMessage : self.server.firstMessage - } - let quotedMessageId = quotedMessage?[messageKey.id.rawValue] as? String - message?[messageKey.quotedMessageId.rawValue] = quotedMessageId - message?[messageKey.quotedMessage.rawValue] = quotedMessage - } - - if inThread { - message?[messageKey.parentId.rawValue] = parentId - message?[messageKey.showReplyInChannel.rawValue] = alsoInChannel - } - - message?[messageKey.attachments.rawValue] = attachments - return message + @discardableResult + public func addReaction(type: ReactionType, delay: Int = 0) -> ParticipantRobot { + var endpoint = "participant/reaction?type=\(type.reaction)" + if delay > 0 { + endpoint += "&delay=\(delay)" } + _ = mockServer.postRequest(endpoint: endpoint) + return self + } - if waitForAppearance { - server.waitForWebsocketMessage(withText: "") - } + @discardableResult + public func deleteReaction(type: ReactionType) -> ParticipantRobot { + _ = mockServer.postRequest(endpoint: "participant/reaction?type=\(type.reaction)&delete=true") return self } } diff --git a/TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift b/TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift deleted file mode 100644 index 38bdc5e6ec8..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift +++ /dev/null @@ -1,200 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -// swiftlint:disable function_body_length -public func demoServer(_ publicDir: String) -> HttpServer { - print(publicDir) - - let server = HttpServer() - - server["/public/:path"] = shareFilesFromDirectory(publicDir) - - server["/files/:path"] = directoryBrowser("/") - - server["/"] = scopes { - html { - body { - ul(server.routes) { service in - li { - a { href = service; inner = service } - } - } - } - } - } - - server["/magic"] = { .ok(.htmlBody("You asked for " + $0.path), ["XXX-Custom-Header": "value"]) } - - server["/test/:param1/:param2"] = { request in - scopes { - html { - body { - h3 { inner = "Address: \(request.address ?? "unknown")" } - h3 { inner = "Url: \(request.path)" } - h3 { inner = "Method: \(request.method)" } - - h3 { inner = "Query:" } - - table(request.queryParams) { param in - tr { - td { inner = param.0 } - td { inner = param.1 } - } - } - - h3 { inner = "Headers:" } - - table(request.headers) { header in - tr { - td { inner = header.0 } - td { inner = header.1 } - } - } - - h3 { inner = "Route params:" } - - table(request.params) { param in - tr { - td { inner = param.0 } - td { inner = param.1 } - } - } - } - } - }(request) - } - - server.GET["/upload"] = scopes { - html { - body { - form { - method = "POST" - action = "/upload" - enctype = "multipart/form-data" - - input { name = "my_file1"; type = "file" } - input { name = "my_file2"; type = "file" } - input { name = "my_file3"; type = "file" } - - button { - type = "submit" - inner = "Upload" - } - } - } - } - } - - server.POST["/upload"] = { request in - var response = "" - for multipart in request.parseMultiPartFormData() { - guard let name = multipart.name, let fileName = multipart.fileName else { continue } - response += "Name: \(name) File name: \(fileName) Size: \(multipart.body.count)
" - } - return HttpResponse.ok(.htmlBody(response), ["XXX-Custom-Header": "value"]) - } - - server.GET["/login"] = scopes { - html { - head { - script { src = "http://cdn.staticfile.org/jquery/2.1.4/jquery.min.js" } - stylesheet { href = "http://cdn.staticfile.org/twitter-bootstrap/3.3.0/css/bootstrap.min.css" } - } - body { - h3 { inner = "Sign In" } - - form { - method = "POST" - action = "/login" - - fieldset { - input { placeholder = "E-mail"; name = "email"; type = "email"; autofocus = "" } - input { placeholder = "Password"; name = "password"; type = "password"; autofocus = "" } - a { - href = "/login" - button { - type = "submit" - inner = "Login" - } - } - } - } - javascript { - src = "http://cdn.staticfile.org/twitter-bootstrap/3.3.0/js/bootstrap.min.js" - } - } - } - } - - server.POST["/login"] = { request in - let formFields = request.parseUrlencodedForm() - return HttpResponse.ok(.htmlBody(formFields.map({ "\($0.0) = \($0.1)" }).joined(separator: "
")), ["XXX-Custom-Header": "value"]) - } - - server["/demo"] = scopes { - html { - body { - center { - h2 { inner = "Hello Swift" } - img { src = "https://devimages.apple.com.edgekey.net/swift/images/swift-hero_2x.png" } - } - } - } - } - - server["/raw"] = { _ in - return HttpResponse.raw(200, "OK", ["XXX-Custom-Header": "value"], { try $0.write([UInt8]("test".utf8)) }) - } - - server["/redirect/permanently"] = { _ in - return .movedPermanently("http://www.google.com") - } - - server["/redirect/temporarily"] = { _ in - return .movedTemporarily("http://www.google.com") - } - - server["/long"] = { _ in - var longResponse = "" - for index in 0..<1000 { longResponse += "(\(index)),->" } - return .ok(.htmlBody(longResponse), ["XXX-Custom-Header": "value"]) - } - - server["/wildcard/*/test/*/:param"] = { request in - return .ok(.htmlBody(request.path), ["XXX-Custom-Header": "value"]) - } - - server["/stream"] = { _ in - return HttpResponse.raw(200, "OK", nil, { writer in - for index in 0...100 { - try writer.write([UInt8]("[chunk \(index)]".utf8)) - } - }) - } - - server["/websocket-echo"] = websocket(text: { (session, text) in - session.writeText(text) - }, binary: { (session, binary) in - session.writeBinary(binary) - }, pong: { (_, _) in - // Got a pong frame - }, connected: { _ in - // New client connected - }, disconnected: { _ in - // Client disconnected - }) - - server.notFoundHandler = { _ in - return .movedPermanently("https://github.com/404") - } - - server.middleware.append { request in - print("Middleware: \(request.address ?? "unknown address") -> \(request.method) -> \(request.path)") - return nil - } - - return server -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/Errno.swift b/TestTools/StreamChatTestMockServer/Swifter/Errno.swift deleted file mode 100644 index b40700bdb93..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/Errno.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -public class Errno { - public class func description() -> String { - // https://forums.developer.apple.com/thread/113919 - return String(cString: strerror(errno)) - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/Files.swift b/TestTools/StreamChatTestMockServer/Swifter/Files.swift deleted file mode 100644 index 27884b7a540..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/Files.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -public func shareFile(_ path: String) -> ((HttpRequest) -> HttpResponse) { - return { _ in - if let file = try? path.openForReading() { - let mimeType = path.mimeType() - var responseHeader: [String: String] = ["Content-Type": mimeType] - - if let attr = try? FileManager.default.attributesOfItem(atPath: path), - let fileSize = attr[FileAttributeKey.size] as? UInt64 { - responseHeader["Content-Length"] = String(fileSize) - } - return .raw(200, "OK", responseHeader, { writer in - try? writer.write(file) - file.close() - }) - } - return .notFound() - } -} - -public func shareFilesFromDirectory(_ directoryPath: String, defaults: [String] = ["index.html", "default.html"]) -> ((HttpRequest) -> HttpResponse) { - return { request in - guard let fileRelativePath = request.params.first else { - return .notFound() - } - if fileRelativePath.value.isEmpty { - for path in defaults { - if let file = try? (directoryPath + String.pathSeparator + path).openForReading() { - return .raw(200, "OK", [:], { writer in - try? writer.write(file) - file.close() - }) - } - } - } - let filePath = directoryPath + String.pathSeparator + fileRelativePath.value - - if let file = try? filePath.openForReading() { - let mimeType = fileRelativePath.value.mimeType() - var responseHeader: [String: String] = ["Content-Type": mimeType] - - if let attr = try? FileManager.default.attributesOfItem(atPath: filePath), - let fileSize = attr[FileAttributeKey.size] as? UInt64 { - responseHeader["Content-Length"] = String(fileSize) - } - - return .raw(200, "OK", responseHeader, { writer in - try? writer.write(file) - file.close() - }) - } - return .notFound() - } -} - -public func directoryBrowser(_ dir: String) -> ((HttpRequest) -> HttpResponse) { - return { request in - guard let (_, value) = request.params.first else { - return .notFound() - } - let filePath = dir + String.pathSeparator + value - do { - guard try filePath.exists() else { - return .notFound() - } - if try filePath.directory() { - var files = try filePath.files() - files.sort(by: { $0.lowercased() < $1.lowercased() }) - return scopes { - html { - body { - table(files) { file in - tr { - td { - a { - href = request.path + "/" + file - inner = file - } - } - } - } - } - } - }(request) - } else { - guard let file = try? filePath.openForReading() else { - return .notFound() - } - return .raw(200, "OK", [:], { writer in - try? writer.write(file) - file.close() - }) - } - } catch { - return HttpResponse.internalServerError(.text("Internal Server Error")) - } - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift deleted file mode 100644 index 6f0e1c523c7..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -enum HttpParserError: Error, Equatable { - case invalidStatusLine(String) - case negativeContentLength -} - -public class HttpParser { - public init() {} - - public func readHttpRequest(_ socket: Socket) throws -> HttpRequest { - let statusLine = try socket.readLine() - let statusLineTokens = statusLine.components(separatedBy: " ") - if statusLineTokens.count < 3 { - throw HttpParserError.invalidStatusLine(statusLine) - } - let request = HttpRequest() - request.method = statusLineTokens[0] - let encodedPath = statusLineTokens[1].addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? statusLineTokens[1] - let urlComponents = URLComponents(string: encodedPath) - request.path = urlComponents?.path ?? "" - request.queryParams = urlComponents?.queryItems?.map { ($0.name, $0.value ?? "") } ?? [] - request.headers = try readHeaders(socket) - if let contentLength = request.headers["content-length"], let contentLengthValue = Int(contentLength) { - // Prevent a buffer overflow and runtime error trying to create an `UnsafeMutableBufferPointer` with - // a negative length - guard contentLengthValue >= 0 else { - throw HttpParserError.negativeContentLength - } - request.body = try readBody(socket, size: contentLengthValue) - } - return request - } - - private func readBody(_ socket: Socket, size: Int) throws -> [UInt8] { - return try socket.read(length: size) - } - - private func readHeaders(_ socket: Socket) throws -> [String: String] { - var headers = [String: String]() - while case let headerLine = try socket.readLine(), !headerLine.isEmpty { - let headerTokens = headerLine.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: true).map(String.init) - if let name = headerTokens.first, let value = headerTokens.last { - headers[name.lowercased()] = value.trimmingCharacters(in: .whitespaces) - } - } - return headers - } - - func supportsKeepAlive(_ headers: [String: String]) -> Bool { - if let value = headers["connection"] { - return value.trimmingCharacters(in: .whitespaces) == "keep-alive" - } - return false - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift deleted file mode 100644 index 23b7c21101e..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift +++ /dev/null @@ -1,170 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -public class HttpRequest { - public var path: String = "" - public var queryParams: [(String, String)] = [] - public var method: String = "" - public var headers: [String: String] = [:] - public var body: [UInt8] = [] - public var address: String? = "" - public var params: [String: String] = [:] - - public init() {} - - public func hasTokenForHeader(_ headerName: String, token: String) -> Bool { - guard let headerValue = headers[headerName] else { - return false - } - return !headerValue.components(separatedBy: ",").filter({ $0.trimmingCharacters(in: .whitespaces).lowercased() == token }).isEmpty - } - - public func parseUrlencodedForm() -> [(String, String)] { - guard let contentTypeHeader = headers["content-type"] else { - return [] - } - let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) } - guard let contentType = contentTypeHeaderTokens.first, contentType == "application/x-www-form-urlencoded" else { - return [] - } - guard let utf8String = String(bytes: body, encoding: .utf8) else { - // Consider to throw an exception here (examine the encoding from headers). - return [] - } - return utf8String.components(separatedBy: "&").map { param -> (String, String) in - let tokens = param.components(separatedBy: "=") - if let name = tokens.first?.removingPercentEncoding, let value = tokens.last?.removingPercentEncoding, tokens.count == 2 { - return ( - name.replacingOccurrences(of: "+", with: " "), - value.replacingOccurrences(of: "+", with: " ") - ) - } - return ("", "") - } - } - - public struct MultiPart { - public let headers: [String: String] - public let body: [UInt8] - - public var name: String? { - return valueFor("content-disposition", parameter: "name")?.unquote() - } - - public var fileName: String? { - return valueFor("content-disposition", parameter: "filename")?.unquote() - } - - private func valueFor(_ headerName: String, parameter: String) -> String? { - return headers.reduce([String]()) { (combined, header: (key: String, value: String)) -> [String] in - guard header.key == headerName else { - return combined - } - let headerValueParams = header.value.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) } - return headerValueParams.reduce(combined, { (results, token) -> [String] in - let parameterTokens = token.components(separatedBy: "=") - if parameterTokens.first == parameter, let value = parameterTokens.last { - return results + [value] - } - return results - }) - }.first - } - } - - public func parseMultiPartFormData() -> [MultiPart] { - guard let contentTypeHeader = headers["content-type"] else { - return [] - } - let contentTypeHeaderTokens = contentTypeHeader.components(separatedBy: ";").map { $0.trimmingCharacters(in: .whitespaces) } - guard let contentType = contentTypeHeaderTokens.first, contentType == "multipart/form-data" else { - return [] - } - var boundary: String? - contentTypeHeaderTokens.forEach({ - let tokens = $0.components(separatedBy: "=") - if let key = tokens.first, key == "boundary" && tokens.count == 2 { - boundary = tokens.last - } - }) - if let boundary = boundary, !boundary.utf8.isEmpty { - return parseMultiPartFormData(body, boundary: "--\(boundary)") - } - return [] - } - - private func parseMultiPartFormData(_ data: [UInt8], boundary: String) -> [MultiPart] { - var generator = data.makeIterator() - var result = [MultiPart]() - while let part = nextMultiPart(&generator, boundary: boundary, isFirst: result.isEmpty) { - result.append(part) - } - return result - } - - private func nextMultiPart(_ generator: inout IndexingIterator<[UInt8]>, boundary: String, isFirst: Bool) -> MultiPart? { - if isFirst { - guard nextUTF8MultiPartLine(&generator) == boundary else { - return nil - } - } else { - /* ignore */ _ = nextUTF8MultiPartLine(&generator) - } - var headers = [String: String]() - while let line = nextUTF8MultiPartLine(&generator), !line.isEmpty { - let tokens = line.components(separatedBy: ":") - if let name = tokens.first, let value = tokens.last, tokens.count == 2 { - headers[name.lowercased()] = value.trimmingCharacters(in: .whitespaces) - } - } - guard let body = nextMultiPartBody(&generator, boundary: boundary) else { - return nil - } - return MultiPart(headers: headers, body: body) - } - - private func nextUTF8MultiPartLine(_ generator: inout IndexingIterator<[UInt8]>) -> String? { - var temp = [UInt8]() - while let value = generator.next() { - if value > HttpRequest.CR { - temp.append(value) - } - if value == HttpRequest.NL { - break - } - } - return String(bytes: temp, encoding: String.Encoding.utf8) - } - - // swiftlint:disable identifier_name - static let CR = UInt8(13) - static let NL = UInt8(10) - - private func nextMultiPartBody(_ generator: inout IndexingIterator<[UInt8]>, boundary: String) -> [UInt8]? { - var body = [UInt8]() - let boundaryArray = [UInt8](boundary.utf8) - var matchOffset = 0 - while let x = generator.next() { - matchOffset = (x == boundaryArray[matchOffset] ? matchOffset + 1 : 0) - body.append(x) - if matchOffset == boundaryArray.count { - #if swift(>=4.2) - body.removeSubrange(body.count - matchOffset..(body.count - matchOffset..) throws - func write(_ data: NSData) throws - func write(_ data: Data) throws -} - -public enum HttpResponseBody { - case json(Any) - case html(String) - case htmlBody(String) - case text(String) - case data(Data, contentType: String? = nil) - case custom(Any, (Any) throws -> String) - - func content() -> (Int, ((HttpResponseBodyWriter) throws -> Void)?) { - do { - switch self { - case .json(let object): - guard JSONSerialization.isValidJSONObject(object) else { - throw SerializationError.invalidObject - } - let data = try JSONSerialization.data(withJSONObject: object) - return (data.count, { - try $0.write(data) - }) - case .text(let body): - let data = [UInt8](body.utf8) - return (data.count, { - try $0.write(data) - }) - case .html(let html): - let data = [UInt8](html.utf8) - return (data.count, { - try $0.write(data) - }) - case .htmlBody(let body): - let serialized = "\(body)" - let data = [UInt8](serialized.utf8) - return (data.count, { - try $0.write(data) - }) - case .data(let data, _): - return (data.count, { - try $0.write(data) - }) - case .custom(let object, let closure): - let serialized = try closure(object) - let data = [UInt8](serialized.utf8) - return (data.count, { - try $0.write(data) - }) - } - } catch { - let data = [UInt8]("Serialization error: \(error)".utf8) - return (data.count, { - try $0.write(data) - }) - } - } -} - -// swiftlint:disable cyclomatic_complexity -public enum HttpResponse { - case switchProtocols([String: String], (Socket) -> Void) - case ok(HttpResponseBody, [String: String] = [:]), created, accepted - case movedPermanently(String) - case movedTemporarily(String) - case badRequest(HttpResponseBody?), unauthorized(HttpResponseBody?), forbidden(HttpResponseBody?), notFound(HttpResponseBody? = nil), notAcceptable(HttpResponseBody?), tooManyRequests(HttpResponseBody?), internalServerError(HttpResponseBody?) - case raw(Int, String, [String: String]?, ((HttpResponseBodyWriter) throws -> Void)?) - - public var statusCode: Int { - switch self { - case .switchProtocols: return 101 - case .ok: return 200 - case .created: return 201 - case .accepted: return 202 - case .movedPermanently: return 301 - case .movedTemporarily: return 307 - case .badRequest: return 400 - case .unauthorized: return 401 - case .forbidden: return 403 - case .notFound: return 404 - case .notAcceptable: return 406 - case .tooManyRequests: return 429 - case .internalServerError: return 500 - case .raw(let code, _, _, _): return code - } - } - - public var reasonPhrase: String { - switch self { - case .switchProtocols: return "Switching Protocols" - case .ok: return "OK" - case .created: return "Created" - case .accepted: return "Accepted" - case .movedPermanently: return "Moved Permanently" - case .movedTemporarily: return "Moved Temporarily" - case .badRequest: return "Bad Request" - case .unauthorized: return "Unauthorized" - case .forbidden: return "Forbidden" - case .notFound: return "Not Found" - case .notAcceptable: return "Not Acceptable" - case .tooManyRequests: return "Too Many Requests" - case .internalServerError: return "Internal Server Error" - case .raw(_, let phrase, _, _): return phrase - } - } - - public func headers() -> [String: String] { - var headers = ["Server": "Swifter \(HttpServer.VERSION)"] - switch self { - case .switchProtocols(let switchHeaders, _): - for (key, value) in switchHeaders { - headers[key] = value - } - case .ok(let body, let customHeaders): - for (key, value) in customHeaders { - headers.updateValue(value, forKey: key) - } - switch body { - case .json: headers["Content-Type"] = "application/json" - case .html, .htmlBody: headers["Content-Type"] = "text/html" - case .text: headers["Content-Type"] = "text/plain" - case .data(_, let contentType): headers["Content-Type"] = contentType - default: break - } - case .movedPermanently(let location): - headers["Location"] = location - case .movedTemporarily(let location): - headers["Location"] = location - case .raw(_, _, let rawHeaders, _): - if let rawHeaders = rawHeaders { - for (key, value) in rawHeaders { - headers.updateValue(value, forKey: key) - } - } - default: break - } - return headers - } - - func content() -> (length: Int, write: ((HttpResponseBodyWriter) throws -> Void)?) { - switch self { - case .ok(let body, _): return body.content() - case .badRequest(let body), .unauthorized(let body), .forbidden(let body), .notFound(let body), .tooManyRequests(let body), .internalServerError(let body): return body?.content() ?? (-1, nil) - case .raw(_, _, _, let writer): return (-1, writer) - default: return (-1, nil) - } - } - - func socketSession() -> ((Socket) -> Void)? { - switch self { - case .switchProtocols(_, let handler): return handler - default: return nil - } - } -} - -/** - Makes it possible to compare handler responses with '==', but - ignores any associated values. This should generally be what - you want. E.g.: - - let resp = handler(updatedRequest) - if resp == .NotFound { - print("Client requested not found: \(request.url)") - } - */ - -func == (inLeft: HttpResponse, inRight: HttpResponse) -> Bool { - return inLeft.statusCode == inRight.statusCode -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift deleted file mode 100644 index baf1cc86d5a..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift +++ /dev/null @@ -1,180 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -open class HttpRouter { - public init() {} - - private class Node { - /// The children nodes that form the route - var nodes = [String: Node]() - - /// Define whether or not this node is the end of a route - var isEndOfRoute: Bool = false - - /// The closure to handle the route - var handler: ((HttpRequest) -> HttpResponse)? - } - - private var rootNode = Node() - - /// The Queue to handle the thread safe access to the routes - private let queue = DispatchQueue(label: "swifter.httpserverio.httprouter") - - public func routes() -> [String] { - var routes = [String]() - for (_, child) in rootNode.nodes { - routes.append(contentsOf: routesForNode(child)) - } - return routes - } - - private func routesForNode(_ node: Node, prefix: String = "") -> [String] { - var result = [String]() - if node.handler != nil { - result.append(prefix) - } - for (key, child) in node.nodes { - result.append(contentsOf: routesForNode(child, prefix: prefix + "/" + key)) - } - return result - } - - public func register(_ method: String?, path: String, handler: ((HttpRequest) -> HttpResponse)?) { - var pathSegments = stripQuery(path).split("/") - if let method = method { - pathSegments.insert(method, at: 0) - } else { - pathSegments.insert("*", at: 0) - } - var pathSegmentsGenerator = pathSegments.makeIterator() - inflate(&rootNode, generator: &pathSegmentsGenerator).handler = handler - } - - public func route(_ method: String?, path: String) -> ([String: String], (HttpRequest) -> HttpResponse)? { - return queue.sync { - if let method = method { - let pathSegments = (method + "/" + stripQuery(path)).split("/") - var pathSegmentsGenerator = pathSegments.makeIterator() - var params = [String: String]() - if let handler = findHandler(&rootNode, params: ¶ms, generator: &pathSegmentsGenerator) { - return (params, handler) - } - } - - let pathSegments = ("*/" + stripQuery(path)).split("/") - var pathSegmentsGenerator = pathSegments.makeIterator() - var params = [String: String]() - if let handler = findHandler(&rootNode, params: ¶ms, generator: &pathSegmentsGenerator) { - return (params, handler) - } - - return nil - } - } - - private func inflate(_ node: inout Node, generator: inout IndexingIterator<[String]>) -> Node { - var currentNode = node - - while let pathSegment = generator.next() { - if let nextNode = currentNode.nodes[pathSegment] { - currentNode = nextNode - } else { - currentNode.nodes[pathSegment] = Node() - currentNode = currentNode.nodes[pathSegment]! - } - } - - currentNode.isEndOfRoute = true - return currentNode - } - - private func findHandler(_ node: inout Node, params: inout [String: String], generator: inout IndexingIterator<[String]>) -> ((HttpRequest) -> HttpResponse)? { - var matchedRoutes = [Node]() - let pattern = generator.map { $0 } - let numberOfElements = pattern.count - - findHandler(&node, params: ¶ms, pattern: pattern, matchedNodes: &matchedRoutes, index: 0, count: numberOfElements) - return matchedRoutes.first?.handler - } - - // swiftlint:disable function_parameter_count - /// Find the handlers for a specified route - /// - /// - Parameters: - /// - node: The root node of the tree representing all the routes - /// - params: The parameters of the match - /// - pattern: The pattern or route to find in the routes tree - /// - matchedNodes: An array with the nodes matching the route - /// - index: The index of current position in the generator - /// - count: The number of elements if the route to match - private func findHandler(_ node: inout Node, params: inout [String: String], pattern: [String], matchedNodes: inout [Node], index: Int, count: Int) { - if index < count, let pathToken = pattern[index].removingPercentEncoding { - var currentIndex = index + 1 - let variableNodes = node.nodes.filter { $0.0.first == ":" } - if let variableNode = variableNodes.first { - if currentIndex == count && variableNode.1.isEndOfRoute { - // if it's the last element of the pattern and it's a variable, stop the search and - // append a tail as a value for the variable. - let tail = pattern[currentIndex.. String { - if let path = path.components(separatedBy: "?").first { - return path - } - return path - } -} - -extension String { - func split(_ separator: Character) -> [String] { - return self.split { $0 == separator }.map(String.init) - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift deleted file mode 100644 index 9d4241cdfe2..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -open class HttpServer: HttpServerIO { - public static let VERSION: String = { - #if os(Linux) - return "1.5.0" - #else - let bundle = Bundle(for: HttpServer.self) - guard let version = bundle.infoDictionary?["CFBundleShortVersionString"] as? String else { return "Unspecified" } - return version - #endif - }() - - private let router = HttpRouter() - - override public init() { - self.DELETE = MethodRoute(method: "DELETE", router: router) - self.PATCH = MethodRoute(method: "PATCH", router: router) - self.HEAD = MethodRoute(method: "HEAD", router: router) - self.POST = MethodRoute(method: "POST", router: router) - self.GET = MethodRoute(method: "GET", router: router) - self.PUT = MethodRoute(method: "PUT", router: router) - - self.delete = MethodRoute(method: "DELETE", router: router) - self.patch = MethodRoute(method: "PATCH", router: router) - self.head = MethodRoute(method: "HEAD", router: router) - self.post = MethodRoute(method: "POST", router: router) - self.get = MethodRoute(method: "GET", router: router) - self.put = MethodRoute(method: "PUT", router: router) - } - - public var DELETE, PATCH, HEAD, POST, GET, PUT: MethodRoute - public var delete, patch, head, post, get, put: MethodRoute - - public subscript(path: String) -> ((HttpRequest) -> HttpResponse)? { - get { return nil } - set { - router.register(nil, path: path, handler: newValue) - } - } - - public var routes: [String] { - return router.routes() - } - - public var notFoundHandler: ((HttpRequest) -> HttpResponse)? - - public var middleware = [(HttpRequest) -> HttpResponse?]() - - override open func dispatch(_ request: HttpRequest) -> ([String: String], (HttpRequest) -> HttpResponse) { - for layer in middleware { - if let response = layer(request) { - return ([:], { _ in response }) - } - } - if let result = router.route(request.method, path: request.path) { - return result - } - if let notFoundHandler = self.notFoundHandler { - return ([:], notFoundHandler) - } - return super.dispatch(request) - } - - public struct MethodRoute { - public let method: String - public let router: HttpRouter - public subscript(path: String) -> ((HttpRequest) -> HttpResponse)? { - get { return nil } - set { - router.register(method, path: path, handler: newValue) - } - } - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift b/TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift deleted file mode 100644 index 90131d43c19..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift +++ /dev/null @@ -1,199 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Dispatch -import Foundation - -public protocol HttpServerIODelegate: AnyObject { - func socketConnectionReceived(_ socket: Socket) -} - -open class HttpServerIO { - public weak var delegate: HttpServerIODelegate? - - private var socket = Socket(socketFileDescriptor: -1) - private var sockets = Set() - - public enum HttpServerIOState: Int32 { - case starting - case running - case stopping - case stopped - } - - private var stateValue: Int32 = HttpServerIOState.stopped.rawValue - - public private(set) var state: HttpServerIOState { - get { - return HttpServerIOState(rawValue: stateValue)! - } - set(state) { - #if !os(Linux) - OSAtomicCompareAndSwapInt(self.state.rawValue, state.rawValue, &stateValue) - #else - self.stateValue = state.rawValue - #endif - } - } - - public var operating: Bool { return self.state == .running } - - /// String representation of the IPv4 address to receive requests from. - /// It's only used when the server is started with `forceIPv4` option set to true. - /// Otherwise, `listenAddressIPv6` will be used. - public var listenAddressIPv4: String? - - /// String representation of the IPv6 address to receive requests from. - /// It's only used when the server is started with `forceIPv4` option set to false. - /// Otherwise, `listenAddressIPv4` will be used. - public var listenAddressIPv6: String? - - private let queue = DispatchQueue(label: "swifter.httpserverio.clientsockets") - - public func port() throws -> Int { - return Int(try socket.port()) - } - - public func isIPv4() throws -> Bool { - return try socket.isIPv4() - } - - deinit { - stop() - } - - @available(macOS 10.10, *) - public func start(_ port: in_port_t = 8080, forceIPv4: Bool = false, priority: DispatchQoS.QoSClass = DispatchQoS.QoSClass.background) throws { - guard !self.operating else { return } - stop() - self.state = .starting - let address = forceIPv4 ? listenAddressIPv4 : listenAddressIPv6 - self.socket = try Socket.tcpSocketForListen(port, forceIPv4, SOMAXCONN, address) - self.state = .running - DispatchQueue.global(qos: priority).async { [weak self] in - guard let strongSelf = self else { return } - guard strongSelf.operating else { return } - while let socket = try? strongSelf.socket.acceptClientSocket() { - DispatchQueue.global(qos: priority).async { [weak self] in - guard let strongSelf = self else { return } - guard strongSelf.operating else { return } - strongSelf.queue.async { - strongSelf.sockets.insert(socket) - } - - strongSelf.handleConnection(socket) - - strongSelf.queue.async { - strongSelf.sockets.remove(socket) - } - } - } - strongSelf.stop() - } - } - - public func stop() { - guard self.operating else { return } - self.state = .stopping - // Shutdown connected peers because they can live in 'keep-alive' or 'websocket' loops. - for socket in self.sockets { - socket.close() - } - self.queue.sync { - self.sockets.removeAll(keepingCapacity: true) - } - socket.close() - self.state = .stopped - } - - open func dispatch(_ request: HttpRequest) -> ([String: String], (HttpRequest) -> HttpResponse) { - return ([:], { _ in HttpResponse.notFound(nil) }) - } - - private func handleConnection(_ socket: Socket) { - let parser = HttpParser() - while self.operating, let request = try? parser.readHttpRequest(socket) { - let request = request - request.address = try? socket.peername() - let (params, handler) = self.dispatch(request) - request.params = params - let response = handler(request) - var keepConnection = parser.supportsKeepAlive(request.headers) - do { - if self.operating { - keepConnection = try self.respond(socket, response: response, keepAlive: keepConnection) - } - } catch { - print("Failed to send response: \(error)") - } - if let session = response.socketSession() { - delegate?.socketConnectionReceived(socket) - session(socket) - break - } - if !keepConnection { break } - } - socket.close() - } - - private struct InnerWriteContext: HttpResponseBodyWriter { - let socket: Socket - - func write(_ file: String.File) throws { - try socket.writeFile(file) - } - - func write(_ data: [UInt8]) throws { - try write(ArraySlice(data)) - } - - func write(_ data: ArraySlice) throws { - try socket.writeUInt8(data) - } - - func write(_ data: NSData) throws { - try socket.writeData(data) - } - - func write(_ data: Data) throws { - try socket.writeData(data) - } - } - - private func respond(_ socket: Socket, response: HttpResponse, keepAlive: Bool) throws -> Bool { - guard self.operating else { return false } - - // Some web-socket clients (like Jetfire) expects to have header section in a single packet. - // We can't promise that but make sure we invoke "write" only once for response header section. - - var responseHeader = String() - - responseHeader.append("HTTP/1.1 \(response.statusCode) \(response.reasonPhrase)\r\n") - - let content = response.content() - - if content.length >= 0 { - responseHeader.append("Content-Length: \(content.length)\r\n") - } - - if keepAlive && content.length != -1 { - responseHeader.append("Connection: keep-alive\r\n") - } - - for (name, value) in response.headers() { - responseHeader.append("\(name): \(value)\r\n") - } - - responseHeader.append("\r\n") - - try socket.writeUTF8(responseHeader) - - if let writeClosure = content.write { - let context = InnerWriteContext(socket: socket) - try writeClosure(context) - } - - return keepAlive && content.length != -1 - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift b/TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift deleted file mode 100644 index 97074330040..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -internal let DEFAULT_MIME_TYPE = "application/octet-stream" - -internal let mimeTypes = [ - "html": "text/html", - "htm": "text/html", - "shtml": "text/html", - "css": "text/css", - "xml": "text/xml", - "gif": "image/gif", - "jpeg": "image/jpeg", - "jpg": "image/jpeg", - "js": "application/javascript", - "atom": "application/atom+xml", - "rss": "application/rss+xml", - "mml": "text/mathml", - "txt": "text/plain", - "jad": "text/vnd.sun.j2me.app-descriptor", - "wml": "text/vnd.wap.wml", - "htc": "text/x-component", - "png": "image/png", - "tif": "image/tiff", - "tiff": "image/tiff", - "wbmp": "image/vnd.wap.wbmp", - "ico": "image/x-icon", - "jng": "image/x-jng", - "bmp": "image/x-ms-bmp", - "svg": "image/svg+xml", - "svgz": "image/svg+xml", - "webp": "image/webp", - "woff": "application/font-woff", - "jar": "application/java-archive", - "war": "application/java-archive", - "ear": "application/java-archive", - "json": "application/json", - "hqx": "application/mac-binhex40", - "doc": "application/msword", - "pdf": "application/pdf", - "ps": "application/postscript", - "eps": "application/postscript", - "ai": "application/postscript", - "rtf": "application/rtf", - "m3u8": "application/vnd.apple.mpegurl", - "xls": "application/vnd.ms-excel", - "eot": "application/vnd.ms-fontobject", - "ppt": "application/vnd.ms-powerpoint", - "wmlc": "application/vnd.wap.wmlc", - "kml": "application/vnd.google-earth.kml+xml", - "kmz": "application/vnd.google-earth.kmz", - "7z": "application/x-7z-compressed", - "cco": "application/x-cocoa", - "jardiff": "application/x-java-archive-diff", - "jnlp": "application/x-java-jnlp-file", - "run": "application/x-makeself", - "pl": "application/x-perl", - "pm": "application/x-perl", - "prc": "application/x-pilot", - "pdb": "application/x-pilot", - "rar": "application/x-rar-compressed", - "rpm": "application/x-redhat-package-manager", - "sea": "application/x-sea", - "swf": "application/x-shockwave-flash", - "sit": "application/x-stuffit", - "tcl": "application/x-tcl", - "tk": "application/x-tcl", - "der": "application/x-x509-ca-cert", - "pem": "application/x-x509-ca-cert", - "crt": "application/x-x509-ca-cert", - "xpi": "application/x-xpinstall", - "xhtml": "application/xhtml+xml", - "xspf": "application/xspf+xml", - "zip": "application/zip", - "bin": "application/octet-stream", - "exe": "application/octet-stream", - "dll": "application/octet-stream", - "deb": "application/octet-stream", - "dmg": "application/octet-stream", - "iso": "application/octet-stream", - "img": "application/octet-stream", - "msi": "application/octet-stream", - "msp": "application/octet-stream", - "msm": "application/octet-stream", - "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "mid": "audio/midi", - "midi": "audio/midi", - "kar": "audio/midi", - "mp3": "audio/mpeg", - "ogg": "audio/ogg", - "m4a": "audio/x-m4a", - "ra": "audio/x-realaudio", - "3gpp": "video/3gpp", - "3gp": "video/3gpp", - "ts": "video/mp2t", - "mp4": "video/mp4", - "mpeg": "video/mpeg", - "mpg": "video/mpeg", - "mov": "video/quicktime", - "webm": "video/webm", - "flv": "video/x-flv", - "m4v": "video/x-m4v", - "mng": "video/x-mng", - "asx": "video/x-ms-asf", - "asf": "video/x-ms-asf", - "wmv": "video/x-ms-wmv", - "avi": "video/x-msvideo" -] - -internal func matchMimeType(extens: String?) -> String { - if extens != nil && mimeTypes.contains(where: { $0.0 == extens!.lowercased() }) { - return mimeTypes[extens!.lowercased()]! - } - return DEFAULT_MIME_TYPE -} - -extension NSURL { - public func mimeType() -> String { - return matchMimeType(extens: self.pathExtension) - } -} - -extension NSString { - public func mimeType() -> String { - return matchMimeType(extens: self.pathExtension) - } -} - -extension String { - public func mimeType() -> String { - return (NSString(string: self)).mimeType() - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/Process.swift b/TestTools/StreamChatTestMockServer/Swifter/Process.swift deleted file mode 100644 index c9be00de85c..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/Process.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -public class Process { - public static var pid: Int { - return Int(getpid()) - } - - public static var tid: UInt64 { - #if os(Linux) - return UInt64(pthread_self()) - #else - var tid: __uint64_t = 0 - pthread_threadid_np(nil, &tid) - return UInt64(tid) - #endif - } - - private static var signalsWatchers = [(Int32) -> Void]() - private static var signalsObserved = false - - public static func watchSignals(_ callback: @escaping (Int32) -> Void) { - if !signalsObserved { - [SIGTERM, SIGHUP, SIGSTOP, SIGINT].forEach { item in - signal(item) { signum in - Process.signalsWatchers.forEach { $0(signum) } - } - } - signalsObserved = true - } - signalsWatchers.append(callback) - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/Scopes.swift b/TestTools/StreamChatTestMockServer/Swifter/Scopes.swift deleted file mode 100644 index 7128cbbb59f..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/Scopes.swift +++ /dev/null @@ -1,883 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -// swiftlint:disable file_length -import Foundation - -public func scopes(_ scope: @escaping Closure) -> ((HttpRequest) -> HttpResponse) { - return { _ in - scopesBuffer[Process.tid] = "" - scope() - return .raw(200, "OK", ["Content-Type": "text/html"], { - try? $0.write([UInt8](("" + (scopesBuffer[Process.tid] ?? "")).utf8)) - }) - } -} - -public typealias Closure = () -> Void - -public var idd: String? -public var dir: String? -public var rel: String? -public var rev: String? -public var alt: String? -public var forr: String? -public var src: String? -public var type: String? -public var href: String? -public var text: String? -public var abbr: String? -public var size: String? -public var face: String? -public var char: String? -public var cite: String? -public var span: String? -public var data: String? -public var axis: String? -public var Name: String? -public var name: String? -public var code: String? -public var link: String? -public var lang: String? -public var cols: String? -public var rows: String? -public var ismap: String? -public var shape: String? -public var style: String? -public var alink: String? -public var width: String? -public var rules: String? -public var align: String? -public var frame: String? -public var vlink: String? -public var deferr: String? -public var color: String? -public var media: String? -public var title: String? -public var scope: String? -public var classs: String? -public var manifest: String? -public var value: String? -public var clear: String? -public var start: String? -public var label: String? -public var action: String? -public var height: String? -public var method: String? -public var acceptt: String? -public var object: String? -public var scheme: String? -public var coords: String? -public var usemap: String? -public var onblur: String? -public var nohref: String? -public var nowrap: String? -public var hspace: String? -public var border: String? -public var valign: String? -public var vspace: String? -public var onload: String? -public var target: String? -public var prompt: String? -public var onfocus: String? -public var enctype: String? -public var onclick: String? -public var ontouchstart: String? -public var onkeyup: String? -public var profile: String? -public var version: String? -public var onreset: String? -public var charset: String? -public var standby: String? -public var colspan: String? -public var charoff: String? -public var classid: String? -public var compact: String? -public var declare: String? -public var rowspan: String? -public var checked: String? -public var archive: String? -public var bgcolor: String? -public var content: String? -public var noshade: String? -public var summary: String? -public var headers: String? -public var onselect: String? -public var readonly: String? -public var tabindex: String? -public var onchange: String? -public var noresize: String? -public var disabled: String? -public var longdesc: String? -public var codebase: String? -public var language: String? -public var datetime: String? -public var selected: String? -public var hreflang: String? -public var onsubmit: String? -public var multiple: String? -public var onunload: String? -public var codetype: String? -public var scrolling: String? -public var onkeydown: String? -public var maxlength: String? -public var valuetype: String? -public var accesskey: String? -public var onmouseup: String? -public var autofocus: String? -public var onkeypress: String? -public var ondblclick: String? -public var onmouseout: String? -public var httpEquiv: String? -public var dataText: String? -public var background: String? -public var onmousemove: String? -public var onmouseover: String? -public var cellpadding: String? -public var onmousedown: String? -public var frameborder: String? -public var marginwidth: String? -public var cellspacing: String? -public var placeholder: String? -public var marginheight: String? -public var acceptCharset: String? - -public var inner: String? - -public func a(_ closure: Closure) { element("a", closure) } -public func b(_ closure: Closure) { element("b", closure) } -public func i(_ closure: Closure) { element("i", closure) } -public func p(_ closure: Closure) { element("p", closure) } -public func q(_ closure: Closure) { element("q", closure) } -public func s(_ closure: Closure) { element("s", closure) } -public func u(_ closure: Closure) { element("u", closure) } - -public func br(_ closure: Closure) { element("br", closure) } -public func dd(_ closure: Closure) { element("dd", closure) } -public func dl(_ closure: Closure) { element("dl", closure) } -public func dt(_ closure: Closure) { element("dt", closure) } -public func em(_ closure: Closure) { element("em", closure) } -public func hr(_ closure: Closure) { element("hr", closure) } -public func li(_ closure: Closure) { element("li", closure) } -public func ol(_ closure: Closure) { element("ol", closure) } -public func rp(_ closure: Closure) { element("rp", closure) } -public func rt(_ closure: Closure) { element("rt", closure) } -public func td(_ closure: Closure) { element("td", closure) } -public func th(_ closure: Closure) { element("th", closure) } -public func tr(_ closure: Closure) { element("tr", closure) } -public func tt(_ closure: Closure) { element("tt", closure) } -public func ul(_ closure: Closure) { element("ul", closure) } - -public func ul(_ collection: T, _ closure: @escaping (T.Iterator.Element) -> Void) { - element("ul", { - for item in collection { - closure(item) - } - }) -} - -public func h1(_ closure: Closure) { element("h1", closure) } -public func h2(_ closure: Closure) { element("h2", closure) } -public func h3(_ closure: Closure) { element("h3", closure) } -public func h4(_ closure: Closure) { element("h4", closure) } -public func h5(_ closure: Closure) { element("h5", closure) } -public func h6(_ closure: Closure) { element("h6", closure) } - -public func bdi(_ closure: Closure) { element("bdi", closure) } -public func bdo(_ closure: Closure) { element("bdo", closure) } -public func big(_ closure: Closure) { element("big", closure) } -public func col(_ closure: Closure) { element("col", closure) } -public func del(_ closure: Closure) { element("del", closure) } -public func dfn(_ closure: Closure) { element("dfn", closure) } -public func dir(_ closure: Closure) { element("dir", closure) } -public func div(_ closure: Closure) { element("div", closure) } -public func img(_ closure: Closure) { element("img", closure) } -public func ins(_ closure: Closure) { element("ins", closure) } -public func kbd(_ closure: Closure) { element("kbd", closure) } -public func map(_ closure: Closure) { element("map", closure) } -public func nav(_ closure: Closure) { element("nav", closure) } -public func pre(_ closure: Closure) { element("pre", closure) } -public func rtc(_ closure: Closure) { element("rtc", closure) } -public func sub(_ closure: Closure) { element("sub", closure) } -public func sup(_ closure: Closure) { element("sup", closure) } - -public func varr(_ closure: Closure) { element("var", closure) } -public func wbr(_ closure: Closure) { element("wbr", closure) } -public func xmp(_ closure: Closure) { element("xmp", closure) } - -public func abbr(_ closure: Closure) { element("abbr", closure) } -public func area(_ closure: Closure) { element("area", closure) } -public func base(_ closure: Closure) { element("base", closure) } -public func body(_ closure: Closure) { element("body", closure) } -public func cite(_ closure: Closure) { element("cite", closure) } -public func code(_ closure: Closure) { element("code", closure) } -public func data(_ closure: Closure) { element("data", closure) } -public func font(_ closure: Closure) { element("font", closure) } -public func form(_ closure: Closure) { element("form", closure) } -public func head(_ closure: Closure) { element("head", closure) } -public func html(_ closure: Closure) { element("html", closure) } -public func link(_ closure: Closure) { element("link", closure) } -public func main(_ closure: Closure) { element("main", closure) } -public func mark(_ closure: Closure) { element("mark", closure) } -public func menu(_ closure: Closure) { element("menu", closure) } -public func meta(_ closure: Closure) { element("meta", closure) } -public func nobr(_ closure: Closure) { element("nobr", closure) } -public func ruby(_ closure: Closure) { element("ruby", closure) } -public func samp(_ closure: Closure) { element("samp", closure) } -public func span(_ closure: Closure) { element("span", closure) } -public func time(_ closure: Closure) { element("time", closure) } - -public func aside(_ closure: Closure) { element("aside", closure) } -public func audio(_ closure: Closure) { element("audio", closure) } -public func blink(_ closure: Closure) { element("blink", closure) } -public func embed(_ closure: Closure) { element("embed", closure) } -public func frame(_ closure: Closure) { element("frame", closure) } -public func image(_ closure: Closure) { element("image", closure) } -public func input(_ closure: Closure) { element("input", closure) } -public func label(_ closure: Closure) { element("label", closure) } -public func meter(_ closure: Closure) { element("meter", closure) } -public func param(_ closure: Closure) { element("param", closure) } -public func small(_ closure: Closure) { element("small", closure) } -public func style(_ closure: Closure) { element("style", closure) } -public func table(_ closure: Closure) { element("table", closure) } - -public func table(_ collection: T, closure: @escaping (T.Iterator.Element) -> Void) { - element("table", { - for item in collection { - closure(item) - } - }) -} - -public func tbody(_ closure: Closure) { element("tbody", closure) } - -public func tbody(_ collection: T, closure: @escaping (T.Iterator.Element) -> Void) { - element("tbody", { - for item in collection { - closure(item) - } - }) -} - -public func tfoot(_ closure: Closure) { element("tfoot", closure) } -public func thead(_ closure: Closure) { element("thead", closure) } -public func title(_ closure: Closure) { element("title", closure) } -public func track(_ closure: Closure) { element("track", closure) } -public func video(_ closure: Closure) { element("video", closure) } - -public func applet(_ closure: Closure) { element("applet", closure) } -public func button(_ closure: Closure) { element("button", closure) } -public func canvas(_ closure: Closure) { element("canvas", closure) } -public func center(_ closure: Closure) { element("center", closure) } -public func dialog(_ closure: Closure) { element("dialog", closure) } -public func figure(_ closure: Closure) { element("figure", closure) } -public func footer(_ closure: Closure) { element("footer", closure) } -public func header(_ closure: Closure) { element("header", closure) } -public func hgroup(_ closure: Closure) { element("hgroup", closure) } -public func iframe(_ closure: Closure) { element("iframe", closure) } -public func keygen(_ closure: Closure) { element("keygen", closure) } -public func legend(_ closure: Closure) { element("legend", closure) } -public func object(_ closure: Closure) { element("object", closure) } -public func option(_ closure: Closure) { element("option", closure) } -public func output(_ closure: Closure) { element("output", closure) } -public func script(_ closure: Closure) { element("script", closure) } -public func select(_ closure: Closure) { element("select", closure) } -public func shadow(_ closure: Closure) { element("shadow", closure) } -public func source(_ closure: Closure) { element("source", closure) } -public func spacer(_ closure: Closure) { element("spacer", closure) } -public func strike(_ closure: Closure) { element("strike", closure) } -public func strong(_ closure: Closure) { element("strong", closure) } - -public func acronym(_ closure: Closure) { element("acronym", closure) } -public func address(_ closure: Closure) { element("address", closure) } -public func article(_ closure: Closure) { element("article", closure) } -public func bgsound(_ closure: Closure) { element("bgsound", closure) } -public func caption(_ closure: Closure) { element("caption", closure) } -public func command(_ closure: Closure) { element("command", closure) } -public func content(_ closure: Closure) { element("content", closure) } -public func details(_ closure: Closure) { element("details", closure) } -public func elementt(_ closure: Closure) { element("element", closure) } -public func isindex(_ closure: Closure) { element("isindex", closure) } -public func listing(_ closure: Closure) { element("listing", closure) } -public func marquee(_ closure: Closure) { element("marquee", closure) } -public func noembed(_ closure: Closure) { element("noembed", closure) } -public func picture(_ closure: Closure) { element("picture", closure) } -public func section(_ closure: Closure) { element("section", closure) } -public func summary(_ closure: Closure) { element("summary", closure) } - -public func basefont(_ closure: Closure) { element("basefont", closure) } -public func colgroup(_ closure: Closure) { element("colgroup", closure) } -public func datalist(_ closure: Closure) { element("datalist", closure) } -public func fieldset(_ closure: Closure) { element("fieldset", closure) } -public func frameset(_ closure: Closure) { element("frameset", closure) } -public func menuitem(_ closure: Closure) { element("menuitem", closure) } -public func multicol(_ closure: Closure) { element("multicol", closure) } -public func noframes(_ closure: Closure) { element("noframes", closure) } -public func noscript(_ closure: Closure) { element("noscript", closure) } -public func optgroup(_ closure: Closure) { element("optgroup", closure) } -public func progress(_ closure: Closure) { element("progress", closure) } -public func template(_ closure: Closure) { element("template", closure) } -public func textarea(_ closure: Closure) { element("textarea", closure) } - -public func plaintext(_ closure: Closure) { element("plaintext", closure) } -public func javascript(_ closure: Closure) { element("script", ["type": "text/javascript"], closure) } -public func blockquote(_ closure: Closure) { element("blockquote", closure) } -public func figcaption(_ closure: Closure) { element("figcaption", closure) } - -public func stylesheet(_ closure: Closure) { element("link", ["rel": "stylesheet", "type": "text/css"], closure) } - -public func element(_ node: String, _ closure: Closure) { evaluate(node, [:], closure) } -public func element(_ node: String, _ attrs: [String: String?] = [:], _ closure: Closure) { evaluate(node, attrs, closure) } - -var scopesBuffer = [UInt64: String]() - -// swiftlint:disable cyclomatic_complexity function_body_length -private func evaluate(_ node: String, _ attrs: [String: String?] = [:], _ closure: Closure) { - // Push the attributes. - - let stackid = idd - let stackdir = dir - let stackrel = rel - let stackrev = rev - let stackalt = alt - let stackfor = forr - let stacksrc = src - let stacktype = type - let stackhref = href - let stacktext = text - let stackabbr = abbr - let stacksize = size - let stackface = face - let stackchar = char - let stackcite = cite - let stackspan = span - let stackdata = data - let stackaxis = axis - let stackName = Name - let stackname = name - let stackcode = code - let stacklink = link - let stacklang = lang - let stackcols = cols - let stackrows = rows - let stackismap = ismap - let stackshape = shape - let stackstyle = style - let stackalink = alink - let stackwidth = width - let stackrules = rules - let stackalign = align - let stackframe = frame - let stackvlink = vlink - let stackdefer = deferr - let stackcolor = color - let stackmedia = media - let stacktitle = title - let stackscope = scope - let stackclass = classs - let stackmanifest = manifest - let stackvalue = value - let stackclear = clear - let stackstart = start - let stacklabel = label - let stackaction = action - let stackheight = height - let stackmethod = method - let stackaccept = acceptt - let stackobject = object - let stackscheme = scheme - let stackcoords = coords - let stackusemap = usemap - let stackonblur = onblur - let stacknohref = nohref - let stacknowrap = nowrap - let stackhspace = hspace - let stackborder = border - let stackvalign = valign - let stackvspace = vspace - let stackonload = onload - let stacktarget = target - let stackprompt = prompt - let stackonfocus = onfocus - let stackenctype = enctype - let stackonclick = onclick - let stackontouchstart = ontouchstart - let stackonkeyup = onkeyup - let stackprofile = profile - let stackversion = version - let stackonreset = onreset - let stackcharset = charset - let stackstandby = standby - let stackcolspan = colspan - let stackcharoff = charoff - let stackclassid = classid - let stackcompact = compact - let stackdeclare = declare - let stackrowspan = rowspan - let stackchecked = checked - let stackarchive = archive - let stackbgcolor = bgcolor - let stackcontent = content - let stacknoshade = noshade - let stacksummary = summary - let stackheaders = headers - let stackonselect = onselect - let stackreadonly = readonly - let stacktabindex = tabindex - let stackonchange = onchange - let stacknoresize = noresize - let stackdisabled = disabled - let stacklongdesc = longdesc - let stackcodebase = codebase - let stacklanguage = language - let stackdatetime = datetime - let stackselected = selected - let stackhreflang = hreflang - let stackonsubmit = onsubmit - let stackmultiple = multiple - let stackonunload = onunload - let stackcodetype = codetype - let stackscrolling = scrolling - let stackonkeydown = onkeydown - let stackmaxlength = maxlength - let stackvaluetype = valuetype - let stackaccesskey = accesskey - let stackonmouseup = onmouseup - let stackonkeypress = onkeypress - let stackondblclick = ondblclick - let stackonmouseout = onmouseout - let stackhttpEquiv = httpEquiv - let stackdataText = dataText - let stackbackground = background - let stackonmousemove = onmousemove - let stackonmouseover = onmouseover - let stackcellpadding = cellpadding - let stackonmousedown = onmousedown - let stackframeborder = frameborder - let stackmarginwidth = marginwidth - let stackcellspacing = cellspacing - let stackplaceholder = placeholder - let stackmarginheight = marginheight - let stackacceptCharset = acceptCharset - let stackinner = inner - - // Reset the values before a nested scope evalutation. - - idd = nil - dir = nil - rel = nil - rev = nil - alt = nil - forr = nil - src = nil - type = nil - href = nil - text = nil - abbr = nil - size = nil - face = nil - char = nil - cite = nil - span = nil - data = nil - axis = nil - Name = nil - name = nil - code = nil - link = nil - lang = nil - cols = nil - rows = nil - ismap = nil - shape = nil - style = nil - alink = nil - width = nil - rules = nil - align = nil - frame = nil - vlink = nil - deferr = nil - color = nil - media = nil - title = nil - scope = nil - classs = nil - manifest = nil - value = nil - clear = nil - start = nil - label = nil - action = nil - height = nil - method = nil - acceptt = nil - object = nil - scheme = nil - coords = nil - usemap = nil - onblur = nil - nohref = nil - nowrap = nil - hspace = nil - border = nil - valign = nil - vspace = nil - onload = nil - target = nil - prompt = nil - onfocus = nil - enctype = nil - onclick = nil - ontouchstart = nil - onkeyup = nil - profile = nil - version = nil - onreset = nil - charset = nil - standby = nil - colspan = nil - charoff = nil - classid = nil - compact = nil - declare = nil - rowspan = nil - checked = nil - archive = nil - bgcolor = nil - content = nil - noshade = nil - summary = nil - headers = nil - onselect = nil - readonly = nil - tabindex = nil - onchange = nil - noresize = nil - disabled = nil - longdesc = nil - codebase = nil - language = nil - datetime = nil - selected = nil - hreflang = nil - onsubmit = nil - multiple = nil - onunload = nil - codetype = nil - scrolling = nil - onkeydown = nil - maxlength = nil - valuetype = nil - accesskey = nil - onmouseup = nil - onkeypress = nil - ondblclick = nil - onmouseout = nil - httpEquiv = nil - dataText = nil - background = nil - onmousemove = nil - onmouseover = nil - cellpadding = nil - onmousedown = nil - frameborder = nil - placeholder = nil - marginwidth = nil - cellspacing = nil - marginheight = nil - acceptCharset = nil - inner = nil - - scopesBuffer[Process.tid] = (scopesBuffer[Process.tid] ?? "") + "<" + node - - // Save the current output before the nested scope evalutation. - - var output = scopesBuffer[Process.tid] ?? "" - - // Clear the output buffer for the evalutation. - - scopesBuffer[Process.tid] = "" - - // Evaluate the nested scope. - - closure() - - // Render attributes set by the evalutation. - - var mergedAttributes = [String: String?]() - - if let idd = idd { mergedAttributes["id"] = idd } - if let dir = dir { mergedAttributes["dir"] = dir } - if let rel = rel { mergedAttributes["rel"] = rel } - if let rev = rev { mergedAttributes["rev"] = rev } - if let alt = alt { mergedAttributes["alt"] = alt } - if let forr = forr { mergedAttributes["for"] = forr } - if let src = src { mergedAttributes["src"] = src } - if let type = type { mergedAttributes["type"] = type } - if let href = href { mergedAttributes["href"] = href } - if let text = text { mergedAttributes["text"] = text } - if let abbr = abbr { mergedAttributes["abbr"] = abbr } - if let size = size { mergedAttributes["size"] = size } - if let face = face { mergedAttributes["face"] = face } - if let char = char { mergedAttributes["char"] = char } - if let cite = cite { mergedAttributes["cite"] = cite } - if let span = span { mergedAttributes["span"] = span } - if let data = data { mergedAttributes["data"] = data } - if let axis = axis { mergedAttributes["axis"] = axis } - if let Name = Name { mergedAttributes["Name"] = Name } - if let name = name { mergedAttributes["name"] = name } - if let code = code { mergedAttributes["code"] = code } - if let link = link { mergedAttributes["link"] = link } - if let lang = lang { mergedAttributes["lang"] = lang } - if let cols = cols { mergedAttributes["cols"] = cols } - if let rows = rows { mergedAttributes["rows"] = rows } - if let ismap = ismap { mergedAttributes["ismap"] = ismap } - if let shape = shape { mergedAttributes["shape"] = shape } - if let style = style { mergedAttributes["style"] = style } - if let alink = alink { mergedAttributes["alink"] = alink } - if let width = width { mergedAttributes["width"] = width } - if let rules = rules { mergedAttributes["rules"] = rules } - if let align = align { mergedAttributes["align"] = align } - if let frame = frame { mergedAttributes["frame"] = frame } - if let vlink = vlink { mergedAttributes["vlink"] = vlink } - if let deferr = deferr { mergedAttributes["defer"] = deferr } - if let color = color { mergedAttributes["color"] = color } - if let media = media { mergedAttributes["media"] = media } - if let title = title { mergedAttributes["title"] = title } - if let scope = scope { mergedAttributes["scope"] = scope } - if let classs = classs { mergedAttributes["class"] = classs } - if let manifest = manifest { mergedAttributes["manifest"] = manifest } - if let value = value { mergedAttributes["value"] = value } - if let clear = clear { mergedAttributes["clear"] = clear } - if let start = start { mergedAttributes["start"] = start } - if let label = label { mergedAttributes["label"] = label } - if let action = action { mergedAttributes["action"] = action } - if let height = height { mergedAttributes["height"] = height } - if let method = method { mergedAttributes["method"] = method } - if let acceptt = acceptt { mergedAttributes["accept"] = acceptt } - if let object = object { mergedAttributes["object"] = object } - if let scheme = scheme { mergedAttributes["scheme"] = scheme } - if let coords = coords { mergedAttributes["coords"] = coords } - if let usemap = usemap { mergedAttributes["usemap"] = usemap } - if let onblur = onblur { mergedAttributes["onblur"] = onblur } - if let nohref = nohref { mergedAttributes["nohref"] = nohref } - if let nowrap = nowrap { mergedAttributes["nowrap"] = nowrap } - if let hspace = hspace { mergedAttributes["hspace"] = hspace } - if let border = border { mergedAttributes["border"] = border } - if let valign = valign { mergedAttributes["valign"] = valign } - if let vspace = vspace { mergedAttributes["vspace"] = vspace } - if let onload = onload { mergedAttributes["onload"] = onload } - if let target = target { mergedAttributes["target"] = target } - if let prompt = prompt { mergedAttributes["prompt"] = prompt } - if let onfocus = onfocus { mergedAttributes["onfocus"] = onfocus } - if let enctype = enctype { mergedAttributes["enctype"] = enctype } - if let onclick = onclick { mergedAttributes["onclick"] = onclick } - if let ontouchstart = ontouchstart { mergedAttributes["ontouchstart"] = ontouchstart } - if let onkeyup = onkeyup { mergedAttributes["onkeyup"] = onkeyup } - if let profile = profile { mergedAttributes["profile"] = profile } - if let version = version { mergedAttributes["version"] = version } - if let onreset = onreset { mergedAttributes["onreset"] = onreset } - if let charset = charset { mergedAttributes["charset"] = charset } - if let standby = standby { mergedAttributes["standby"] = standby } - if let colspan = colspan { mergedAttributes["colspan"] = colspan } - if let charoff = charoff { mergedAttributes["charoff"] = charoff } - if let classid = classid { mergedAttributes["classid"] = classid } - if let compact = compact { mergedAttributes["compact"] = compact } - if let declare = declare { mergedAttributes["declare"] = declare } - if let rowspan = rowspan { mergedAttributes["rowspan"] = rowspan } - if let checked = checked { mergedAttributes["checked"] = checked } - if let archive = archive { mergedAttributes["archive"] = archive } - if let bgcolor = bgcolor { mergedAttributes["bgcolor"] = bgcolor } - if let content = content { mergedAttributes["content"] = content } - if let noshade = noshade { mergedAttributes["noshade"] = noshade } - if let summary = summary { mergedAttributes["summary"] = summary } - if let headers = headers { mergedAttributes["headers"] = headers } - if let onselect = onselect { mergedAttributes["onselect"] = onselect } - if let readonly = readonly { mergedAttributes["readonly"] = readonly } - if let tabindex = tabindex { mergedAttributes["tabindex"] = tabindex } - if let onchange = onchange { mergedAttributes["onchange"] = onchange } - if let noresize = noresize { mergedAttributes["noresize"] = noresize } - if let disabled = disabled { mergedAttributes["disabled"] = disabled } - if let longdesc = longdesc { mergedAttributes["longdesc"] = longdesc } - if let codebase = codebase { mergedAttributes["codebase"] = codebase } - if let language = language { mergedAttributes["language"] = language } - if let datetime = datetime { mergedAttributes["datetime"] = datetime } - if let selected = selected { mergedAttributes["selected"] = selected } - if let hreflang = hreflang { mergedAttributes["hreflang"] = hreflang } - if let onsubmit = onsubmit { mergedAttributes["onsubmit"] = onsubmit } - if let multiple = multiple { mergedAttributes["multiple"] = multiple } - if let onunload = onunload { mergedAttributes["onunload"] = onunload } - if let codetype = codetype { mergedAttributes["codetype"] = codetype } - if let scrolling = scrolling { mergedAttributes["scrolling"] = scrolling } - if let onkeydown = onkeydown { mergedAttributes["onkeydown"] = onkeydown } - if let maxlength = maxlength { mergedAttributes["maxlength"] = maxlength } - if let valuetype = valuetype { mergedAttributes["valuetype"] = valuetype } - if let accesskey = accesskey { mergedAttributes["accesskey"] = accesskey } - if let onmouseup = onmouseup { mergedAttributes["onmouseup"] = onmouseup } - if let onkeypress = onkeypress { mergedAttributes["onkeypress"] = onkeypress } - if let ondblclick = ondblclick { mergedAttributes["ondblclick"] = ondblclick } - if let onmouseout = onmouseout { mergedAttributes["onmouseout"] = onmouseout } - if let httpEquiv = httpEquiv { mergedAttributes["http-equiv"] = httpEquiv } - if let dataText = dataText { mergedAttributes["data-text"] = dataText } - if let background = background { mergedAttributes["background"] = background } - if let onmousemove = onmousemove { mergedAttributes["onmousemove"] = onmousemove } - if let onmouseover = onmouseover { mergedAttributes["onmouseover"] = onmouseover } - if let cellpadding = cellpadding { mergedAttributes["cellpadding"] = cellpadding } - if let onmousedown = onmousedown { mergedAttributes["onmousedown"] = onmousedown } - if let frameborder = frameborder { mergedAttributes["frameborder"] = frameborder } - if let marginwidth = marginwidth { mergedAttributes["marginwidth"] = marginwidth } - if let cellspacing = cellspacing { mergedAttributes["cellspacing"] = cellspacing } - if let placeholder = placeholder { mergedAttributes["placeholder"] = placeholder } - if let marginheight = marginheight { mergedAttributes["marginheight"] = marginheight } - if let acceptCharset = acceptCharset { mergedAttributes["accept-charset"] = acceptCharset } - - for item in attrs.enumerated() { - mergedAttributes.updateValue(item.element.1, forKey: item.element.0) - } - - output += mergedAttributes.reduce("") { result, item in - if let value = item.value { - return result + " \(item.key)=\"\(value)\"" - } else { - return result - } - } - - if let inner = inner { - scopesBuffer[Process.tid] = output + ">" + (inner) + "" - } else { - let current = scopesBuffer[Process.tid] ?? "" - scopesBuffer[Process.tid] = output + ">" + current + "" - } - - // Pop the attributes. - - idd = stackid - dir = stackdir - rel = stackrel - rev = stackrev - alt = stackalt - forr = stackfor - src = stacksrc - type = stacktype - href = stackhref - text = stacktext - abbr = stackabbr - size = stacksize - face = stackface - char = stackchar - cite = stackcite - span = stackspan - data = stackdata - axis = stackaxis - Name = stackName - name = stackname - code = stackcode - link = stacklink - lang = stacklang - cols = stackcols - rows = stackrows - ismap = stackismap - shape = stackshape - style = stackstyle - alink = stackalink - width = stackwidth - rules = stackrules - align = stackalign - frame = stackframe - vlink = stackvlink - deferr = stackdefer - color = stackcolor - media = stackmedia - title = stacktitle - scope = stackscope - classs = stackclass - manifest = stackmanifest - value = stackvalue - clear = stackclear - start = stackstart - label = stacklabel - action = stackaction - height = stackheight - method = stackmethod - acceptt = stackaccept - object = stackobject - scheme = stackscheme - coords = stackcoords - usemap = stackusemap - onblur = stackonblur - nohref = stacknohref - nowrap = stacknowrap - hspace = stackhspace - border = stackborder - valign = stackvalign - vspace = stackvspace - onload = stackonload - target = stacktarget - prompt = stackprompt - onfocus = stackonfocus - enctype = stackenctype - onclick = stackonclick - ontouchstart = stackontouchstart - onkeyup = stackonkeyup - profile = stackprofile - version = stackversion - onreset = stackonreset - charset = stackcharset - standby = stackstandby - colspan = stackcolspan - charoff = stackcharoff - classid = stackclassid - compact = stackcompact - declare = stackdeclare - rowspan = stackrowspan - checked = stackchecked - archive = stackarchive - bgcolor = stackbgcolor - content = stackcontent - noshade = stacknoshade - summary = stacksummary - headers = stackheaders - onselect = stackonselect - readonly = stackreadonly - tabindex = stacktabindex - onchange = stackonchange - noresize = stacknoresize - disabled = stackdisabled - longdesc = stacklongdesc - codebase = stackcodebase - language = stacklanguage - datetime = stackdatetime - selected = stackselected - hreflang = stackhreflang - onsubmit = stackonsubmit - multiple = stackmultiple - onunload = stackonunload - codetype = stackcodetype - scrolling = stackscrolling - onkeydown = stackonkeydown - maxlength = stackmaxlength - valuetype = stackvaluetype - accesskey = stackaccesskey - onmouseup = stackonmouseup - onkeypress = stackonkeypress - ondblclick = stackondblclick - onmouseout = stackonmouseout - httpEquiv = stackhttpEquiv - dataText = stackdataText - background = stackbackground - onmousemove = stackonmousemove - onmouseover = stackonmouseover - cellpadding = stackcellpadding - onmousedown = stackonmousedown - frameborder = stackframeborder - placeholder = stackplaceholder - marginwidth = stackmarginwidth - cellspacing = stackcellspacing - marginheight = stackmarginheight - acceptCharset = stackacceptCharset - - inner = stackinner -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift b/TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift deleted file mode 100644 index 857ef2b221b..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -#if os(iOS) || os(tvOS) || os(Linux) -// swiftlint:disable type_name function_parameter_count -struct sf_hdtr {} - -private func sendfileImpl(_ source: UnsafeMutablePointer, _ target: Int32, _: off_t, _: UnsafeMutablePointer, _: UnsafeMutablePointer, _: Int32) -> Int32 { - var buffer = [UInt8](repeating: 0, count: 1024) - while true { - let readResult = fread(&buffer, 1, buffer.count, source) - guard readResult > 0 else { - return Int32(readResult) - } - var writeCounter = 0 - while writeCounter < readResult { - let writeResult = buffer.withUnsafeBytes { (ptr) -> Int in - let start = ptr.baseAddress! + writeCounter - let len = readResult - writeCounter - #if os(Linux) - return send(target, start, len, Int32(MSG_NOSIGNAL)) - #else - return write(target, start, len) - #endif - } - guard writeResult > 0 else { - return Int32(writeResult) - } - writeCounter += writeResult - } - } -} -#endif - -extension Socket { - public func writeFile(_ file: String.File) throws { - var offset: off_t = 0 - var sf: sf_hdtr = sf_hdtr() - - #if os(iOS) || os(tvOS) || os(Linux) - let result = sendfileImpl(file.pointer, self.socketFileDescriptor, 0, &offset, &sf, 0) - #else - let result = sendfile(fileno(file.pointer), self.socketFileDescriptor, 0, &offset, &sf, 0) - #endif - - if result == -1 { - throw SocketError.writeFailed("sendfile: " + Errno.description()) - } - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift b/TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift deleted file mode 100644 index 90b9d302195..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -extension Socket { - // swiftlint:disable function_body_length - /// - Parameters: - /// - listenAddress: String representation of the address the socket should accept - /// connections from. It should be in IPv4 format if forceIPv4 == true, - /// otherwise - in IPv6. - public class func tcpSocketForListen(_ port: in_port_t, _ forceIPv4: Bool = false, _ maxPendingConnection: Int32 = SOMAXCONN, _ listenAddress: String? = nil) throws -> Socket { - #if os(Linux) - let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, Int32(SOCK_STREAM.rawValue), 0) - #else - let socketFileDescriptor = socket(forceIPv4 ? AF_INET : AF_INET6, SOCK_STREAM, 0) - #endif - - if socketFileDescriptor == -1 { - throw SocketError.socketCreationFailed(Errno.description()) - } - - var value: Int32 = 1 - if setsockopt(socketFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(MemoryLayout.size)) == -1 { - let details = Errno.description() - Socket.close(socketFileDescriptor) - throw SocketError.socketSettingReUseAddrFailed(details) - } - Socket.setNoSigPipe(socketFileDescriptor) - - var bindResult: Int32 = -1 - if forceIPv4 { - #if os(Linux) - var addr = sockaddr_in( - sin_family: sa_family_t(AF_INET), - sin_port: port.bigEndian, - sin_addr: in_addr(s_addr: in_addr_t(0)), - sin_zero: (0, 0, 0, 0, 0, 0, 0, 0) - ) - #else - var addr = sockaddr_in( - sin_len: UInt8(MemoryLayout.stride), - sin_family: UInt8(AF_INET), - sin_port: port.bigEndian, - sin_addr: in_addr(s_addr: in_addr_t(0)), - sin_zero: (0, 0, 0, 0, 0, 0, 0, 0) - ) - #endif - if let address = listenAddress { - if address.withCString({ cstring in inet_pton(AF_INET, cstring, &addr.sin_addr) }) == 1 { - // print("\(address) is converted to \(addr.sin_addr).") - } else { - // print("\(address) is not converted.") - } - } - bindResult = withUnsafePointer(to: &addr) { - bind(socketFileDescriptor, UnsafePointer(OpaquePointer($0)), socklen_t(MemoryLayout.size)) - } - } else { - #if os(Linux) - var addr = sockaddr_in6( - sin6_family: sa_family_t(AF_INET6), - sin6_port: port.bigEndian, - sin6_flowinfo: 0, - sin6_addr: in6addr_any, - sin6_scope_id: 0 - ) - #else - var addr = sockaddr_in6( - sin6_len: UInt8(MemoryLayout.stride), - sin6_family: UInt8(AF_INET6), - sin6_port: port.bigEndian, - sin6_flowinfo: 0, - sin6_addr: in6addr_any, - sin6_scope_id: 0 - ) - #endif - if let address = listenAddress { - if address.withCString({ cstring in inet_pton(AF_INET6, cstring, &addr.sin6_addr) }) == 1 { - // print("\(address) is converted to \(addr.sin6_addr).") - } else { - // print("\(address) is not converted.") - } - } - bindResult = withUnsafePointer(to: &addr) { - bind(socketFileDescriptor, UnsafePointer(OpaquePointer($0)), socklen_t(MemoryLayout.size)) - } - } - - if bindResult == -1 { - let details = Errno.description() - Socket.close(socketFileDescriptor) - throw SocketError.bindFailed(details) - } - - if listen(socketFileDescriptor, maxPendingConnection) == -1 { - let details = Errno.description() - Socket.close(socketFileDescriptor) - throw SocketError.listenFailed(details) - } - return Socket(socketFileDescriptor: socketFileDescriptor) - } - - public func acceptClientSocket() throws -> Socket { - var addr = sockaddr() - var len: socklen_t = 0 - let clientSocket = accept(self.socketFileDescriptor, &addr, &len) - if clientSocket == -1 { - throw SocketError.acceptFailed(Errno.description()) - } - Socket.setNoSigPipe(clientSocket) - return Socket(socketFileDescriptor: clientSocket) - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/Socket.swift b/TestTools/StreamChatTestMockServer/Swifter/Socket.swift deleted file mode 100644 index 22458c954bf..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/Socket.swift +++ /dev/null @@ -1,232 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -public enum SocketError: Error { - case socketCreationFailed(String) - case socketSettingReUseAddrFailed(String) - case bindFailed(String) - case listenFailed(String) - case writeFailed(String) - case getPeerNameFailed(String) - case convertingPeerNameFailed - case getNameInfoFailed(String) - case acceptFailed(String) - case recvFailed(String) - case getSockNameFailed(String) -} - -// swiftlint: disable identifier_name -open class Socket: Hashable, Equatable { - let socketFileDescriptor: Int32 - private var shutdown = false - - public init(socketFileDescriptor: Int32) { - self.socketFileDescriptor = socketFileDescriptor - } - - deinit { - close() - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(self.socketFileDescriptor) - } - - public func close() { - if shutdown { - return - } - shutdown = true - Socket.close(self.socketFileDescriptor) - } - - public func port() throws -> in_port_t { - var addr = sockaddr_in() - return try withUnsafePointer(to: &addr) { pointer in - var len = socklen_t(MemoryLayout.size) - if getsockname(socketFileDescriptor, UnsafeMutablePointer(OpaquePointer(pointer)), &len) != 0 { - throw SocketError.getSockNameFailed(Errno.description()) - } - let sin_port = pointer.pointee.sin_port - #if os(Linux) - return ntohs(sin_port) - #else - return Int(OSHostByteOrder()) != OSLittleEndian ? sin_port.littleEndian : sin_port.bigEndian - #endif - } - } - - public func isIPv4() throws -> Bool { - var addr = sockaddr_in() - return try withUnsafePointer(to: &addr) { pointer in - var len = socklen_t(MemoryLayout.size) - if getsockname(socketFileDescriptor, UnsafeMutablePointer(OpaquePointer(pointer)), &len) != 0 { - throw SocketError.getSockNameFailed(Errno.description()) - } - return Int32(pointer.pointee.sin_family) == AF_INET - } - } - - public func writeUTF8(_ string: String) throws { - try writeUInt8(ArraySlice(string.utf8)) - } - - public func writeUInt8(_ data: [UInt8]) throws { - try writeUInt8(ArraySlice(data)) - } - - public func writeUInt8(_ data: ArraySlice) throws { - try data.withUnsafeBufferPointer { - try writeBuffer($0.baseAddress!, length: data.count) - } - } - - public func writeData(_ data: NSData) throws { - try writeBuffer(data.bytes, length: data.length) - } - - public func writeData(_ data: Data) throws { - #if compiler(>=5.0) - try data.withUnsafeBytes { (body: UnsafeRawBufferPointer) in - if let baseAddress = body.baseAddress, !body.isEmpty { - let pointer = baseAddress.assumingMemoryBound(to: UInt8.self) - try self.writeBuffer(pointer, length: data.count) - } - } - #else - try data.withUnsafeBytes { (pointer: UnsafePointer) in - try self.writeBuffer(pointer, length: data.count) - } - #endif - } - - private func writeBuffer(_ pointer: UnsafeRawPointer, length: Int) throws { - var sent = 0 - while sent < length { - #if os(Linux) - let result = send(self.socketFileDescriptor, pointer + sent, Int(length - sent), Int32(MSG_NOSIGNAL)) - #else - let result = write(self.socketFileDescriptor, pointer + sent, Int(length - sent)) - #endif - if result <= 0 { - throw SocketError.writeFailed(Errno.description()) - } - sent += result - } - } - - /// Read a single byte off the socket. This method is optimized for reading - /// a single byte. For reading multiple bytes, use read(length:), which will - /// pre-allocate heap space and read directly into it. - /// - /// - Returns: A single byte - /// - Throws: SocketError.recvFailed if unable to read from the socket - open func read() throws -> UInt8 { - var byte: UInt8 = 0 - - #if os(Linux) - let count = Glibc.read(self.socketFileDescriptor as Int32, &byte, 1) - #else - let count = Darwin.read(self.socketFileDescriptor as Int32, &byte, 1) - #endif - - guard count > 0 else { - throw SocketError.recvFailed(Errno.description()) - } - return byte - } - - /// Read up to `length` bytes from this socket - /// - /// - Parameter length: The maximum bytes to read - /// - Returns: A buffer containing the bytes read - /// - Throws: SocketError.recvFailed if unable to read bytes from the socket - open func read(length: Int) throws -> [UInt8] { - return try [UInt8](unsafeUninitializedCapacity: length) { buffer, bytesRead in - bytesRead = try read(into: &buffer, length: length) - } - } - - static let kBufferLength = 1024 - - /// Read up to `length` bytes from this socket into an existing buffer - /// - /// - Parameter into: The buffer to read into (must be at least length bytes in size) - /// - Parameter length: The maximum bytes to read - /// - Returns: The number of bytes read - /// - Throws: SocketError.recvFailed if unable to read bytes from the socket - func read(into buffer: inout UnsafeMutableBufferPointer, length: Int) throws -> Int { - var offset = 0 - guard let baseAddress = buffer.baseAddress else { return 0 } - - while offset < length { - // Compute next read length in bytes. The bytes read is never more than kBufferLength at once. - let readLength = offset + Socket.kBufferLength < length ? Socket.kBufferLength : length - offset - - #if os(Linux) - let bytesRead = Glibc.read(self.socketFileDescriptor as Int32, baseAddress + offset, readLength) - #else - let bytesRead = Darwin.read(self.socketFileDescriptor as Int32, baseAddress + offset, readLength) - #endif - - guard bytesRead > 0 else { - throw SocketError.recvFailed(Errno.description()) - } - - offset += bytesRead - } - - return offset - } - - private static let CR: UInt8 = 13 - private static let NL: UInt8 = 10 - - public func readLine() throws -> String { - var characters: String = "" - var index: UInt8 = 0 - repeat { - index = try self.read() - if index > Socket.CR { characters.append(Character(UnicodeScalar(index))) } - } while index != Socket.NL - return characters - } - - public func peername() throws -> String { - var addr = sockaddr(), len: socklen_t = socklen_t(MemoryLayout.size) - if getpeername(self.socketFileDescriptor, &addr, &len) != 0 { - throw SocketError.getPeerNameFailed(Errno.description()) - } - var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - if getnameinfo(&addr, len, &hostBuffer, socklen_t(hostBuffer.count), nil, 0, NI_NUMERICHOST) != 0 { - throw SocketError.getNameInfoFailed(Errno.description()) - } - return String(cString: hostBuffer) - } - - public class func setNoSigPipe(_ socket: Int32) { - #if os(Linux) - // There is no SO_NOSIGPIPE in Linux (nor some other systems). You can instead use the MSG_NOSIGNAL flag when calling send(), - // or use signal(SIGPIPE, SIG_IGN) to make your entire application ignore SIGPIPE. - #else - // Prevents crashes when blocking calls are pending and the app is paused ( via Home button ). - var no_sig_pipe: Int32 = 1 - setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &no_sig_pipe, socklen_t(MemoryLayout.size)) - #endif - } - - public class func close(_ socket: Int32) { - #if os(Linux) - _ = Glibc.close(socket) - #else - _ = Darwin.close(socket) - #endif - } -} - -public func == (socket1: Socket, socket2: Socket) -> Bool { - return socket1.socketFileDescriptor == socket2.socketFileDescriptor -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift b/TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift deleted file mode 100644 index d29fa5566b1..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -extension String { - public static func toBase64(_ data: [UInt8]) -> String { - return Data(data).base64EncodedString() - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/String+File.swift b/TestTools/StreamChatTestMockServer/Swifter/String+File.swift deleted file mode 100644 index 86cca0c87bb..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/String+File.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -extension String { - public enum FileError: Error { - case error(Int32) - } - - public class File { - let pointer: UnsafeMutablePointer - - public init(_ pointer: UnsafeMutablePointer) { - self.pointer = pointer - } - - public func close() { - fclose(pointer) - } - - public func seek(_ offset: Int) -> Bool { - return (fseek(pointer, offset, SEEK_SET) == 0) - } - - public func read(_ data: inout [UInt8]) throws -> Int { - if data.count <= 0 { - return data.count - } - let count = fread(&data, 1, data.count, self.pointer) - if count == data.count { - return count - } - if feof(self.pointer) != 0 { - return count - } - if ferror(self.pointer) != 0 { - throw FileError.error(errno) - } - throw FileError.error(0) - } - - public func write(_ data: [UInt8]) throws { - if data.count <= 0 { - return - } - try data.withUnsafeBufferPointer { - if fwrite($0.baseAddress, 1, data.count, self.pointer) != data.count { - throw FileError.error(errno) - } - } - } - - public static func currentWorkingDirectory() throws -> String { - guard let path = getcwd(nil, 0) else { - throw FileError.error(errno) - } - return String(cString: path) - } - } - - public static var pathSeparator = "/" - - public func openNewForWriting() throws -> File { - return try openFileForMode(self, "wb") - } - - public func openForReading() throws -> File { - return try openFileForMode(self, "rb") - } - - public func openForWritingAndReading() throws -> File { - return try openFileForMode(self, "r+b") - } - - public func openFileForMode(_ path: String, _ mode: String) throws -> File { - guard let file = path.withCString({ pathPointer in mode.withCString({ fopen(pathPointer, $0) }) }) else { - throw FileError.error(errno) - } - return File(file) - } - - public func exists() throws -> Bool { - return try self.withStat { - if $0 != nil { - return true - } - return false - } - } - - public func directory() throws -> Bool { - return try self.withStat { - if let stat = $0 { - return stat.st_mode & S_IFMT == S_IFDIR - } - return false - } - } - - public func files() throws -> [String] { - guard let dir = self.withCString({ opendir($0) }) else { - throw FileError.error(errno) - } - defer { closedir(dir) } - var results = [String]() - while let ent = readdir(dir) { - var name = ent.pointee.d_name - let fileName = withUnsafePointer(to: &name) { (ptr) -> String? in - #if os(Linux) - return String(validatingUTF8: ptr.withMemoryRebound(to: CChar.self, capacity: Int(ent.pointee.d_reclen), { (ptrc) -> [CChar] in - return [CChar](UnsafeBufferPointer(start: ptrc, count: 256)) - })) - #else - var buffer = ptr.withMemoryRebound(to: CChar.self, capacity: Int(ent.pointee.d_reclen), { (ptrc) -> [CChar] in - return [CChar](UnsafeBufferPointer(start: ptrc, count: Int(ent.pointee.d_namlen))) - }) - buffer.append(0) - return String(validatingUTF8: buffer) - #endif - } - if let fileName = fileName { - results.append(fileName) - } - } - return results - } - - private func withStat(_ closure: ((stat?) throws -> T)) throws -> T { - return try self.withCString({ - var statBuffer = stat() - if stat($0, &statBuffer) == 0 { - return try closure(statBuffer) - } - if errno == ENOENT { - return try closure(nil) - } - throw FileError.error(errno) - }) - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift b/TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift deleted file mode 100644 index c05c60e85e6..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -extension String { - public func unquote() -> String { - var scalars = self.unicodeScalars - if scalars.first == "\"" && scalars.last == "\"" && scalars.count >= 2 { - scalars.removeFirst() - scalars.removeLast() - return String(scalars) - } - return self - } -} - -extension UnicodeScalar { - public func asWhitespace() -> UInt8? { - if self.value >= 9 && self.value <= 13 { - return UInt8(self.value) - } - if self.value == 32 { - return UInt8(self.value) - } - return nil - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift b/TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift deleted file mode 100644 index 6745922d981..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -// swiftlint:disable identifier_name function_body_length -public enum SHA1 { - public static func hash(_ input: [UInt8]) -> [UInt8] { - // Alghorithm from: https://en.wikipedia.org/wiki/SHA-1 - - var message = input - - var h0 = UInt32(littleEndian: 0x67452301) - var h1 = UInt32(littleEndian: 0xefcdab89) - var h2 = UInt32(littleEndian: 0x98badcfe) - var h3 = UInt32(littleEndian: 0x10325476) - var h4 = UInt32(littleEndian: 0xc3d2e1f0) - - // ml = message length in bits (always a multiple of the number of bits in a character). - - let ml = UInt64(message.count * 8) - - // append the bit '1' to the message e.g. by adding 0x80 if message length is a multiple of 8 bits. - - message.append(0x80) - - // append 0 ≤ k < 512 bits '0', such that the resulting message length in bits is congruent to −64 ≡ 448 (mod 512) - - let padBytesCount = (message.count + 8) % 64 - - message.append(contentsOf: [UInt8](repeating: 0, count: 64 - padBytesCount)) - - // append ml, in a 64-bit big-endian integer. Thus, the total length is a multiple of 512 bits. - - var mlBigEndian = ml.bigEndian - withUnsafePointer(to: &mlBigEndian) { - message.append(contentsOf: Array(UnsafeBufferPointer(start: UnsafePointer(OpaquePointer($0)), count: 8))) - } - - // Process the message in successive 512-bit chunks ( 64 bytes chunks ): - - for chunkStart in 0..(OpaquePointer($0.baseAddress! + (index * 4))).pointee }) - words.append(value.bigEndian) - } - - // Extend the sixteen 32-bit words into eighty 32-bit words: - - for index in 16...79 { - let value: UInt32 = ((words[index - 3]) ^ (words[index - 8]) ^ (words[index - 14]) ^ (words[index - 16])) - words.append(rotateLeft(value, 1)) - } - - // Initialize hash value for this chunk: - - var a = h0 - var b = h1 - var c = h2 - var d = h3 - var e = h4 - - for i in 0..<80 { - var f = UInt32(0) - var k = UInt32(0) - switch i { - case 0...19: - f = (b & c) | ((~b) & d) - k = 0x5a827999 - case 20...39: - f = b ^ c ^ d - k = 0x6ed9eba1 - case 40...59: - f = (b & c) | (b & d) | (c & d) - k = 0x8f1bbcdc - case 60...79: - f = b ^ c ^ d - k = 0xca62c1d6 - default: break - } - let temp = (rotateLeft(a, 5) &+ f &+ e &+ k &+ words[i]) & 0xffffffff - e = d - d = c - c = rotateLeft(b, 30) - b = a - a = temp - } - - // Add this chunk's hash to result so far: - - h0 = (h0 &+ a) & 0xffffffff - h1 = (h1 &+ b) & 0xffffffff - h2 = (h2 &+ c) & 0xffffffff - h3 = (h3 &+ d) & 0xffffffff - h4 = (h4 &+ e) & 0xffffffff - } - - // Produce the final hash value (big-endian) as a 160 bit number: - - var digest = [UInt8]() - - [h0, h1, h2, h3, h4].forEach { value in - var bigEndianVersion = value.bigEndian - withUnsafePointer(to: &bigEndianVersion) { - digest.append(contentsOf: Array(UnsafeBufferPointer(start: UnsafePointer(OpaquePointer($0)), count: 4))) - } - } - - return digest - } - - private static func rotateLeft(_ v: UInt32, _ n: UInt32) -> UInt32 { - return ((v << n) & 0xffffffff) | (v >> (32 - n)) - } -} - -extension String { - public func sha1() -> [UInt8] { - return SHA1.hash([UInt8](self.utf8)) - } - - public func sha1() -> String { - return self.sha1().reduce("") { $0 + String(format: "%02x", $1) } - } -} diff --git a/TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift b/TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift deleted file mode 100644 index 6cca6dd7830..00000000000 --- a/TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift +++ /dev/null @@ -1,294 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -import Foundation - -@available(*, deprecated, message: "Use websocket(text:binary:pong:connected:disconnected:) instead.") -public func websocket( - _ text: @escaping (WebSocketSession, String) -> Void, - _ binary: @escaping (WebSocketSession, [UInt8]) -> Void, - _ pong: @escaping (WebSocketSession, [UInt8]) -> Void -) -> ((HttpRequest) -> HttpResponse) { - return websocket(text: text, binary: binary, pong: pong) -} - -// swiftlint:disable function_body_length -public func websocket( - text: ((WebSocketSession, String) -> Void)? = nil, - binary: ((WebSocketSession, [UInt8]) -> Void)? = nil, - pong: ((WebSocketSession, [UInt8]) -> Void)? = nil, - connected: ((WebSocketSession) -> Void)? = nil, - disconnected: ((WebSocketSession) -> Void)? = nil -) -> ((HttpRequest) -> HttpResponse) { - return { request in - guard request.hasTokenForHeader("upgrade", token: "websocket") else { - return .badRequest(.text("Invalid value of 'Upgrade' header: \(request.headers["upgrade"] ?? "unknown")")) - } - guard request.hasTokenForHeader("connection", token: "upgrade") else { - return .badRequest(.text("Invalid value of 'Connection' header: \(request.headers["connection"] ?? "unknown")")) - } - guard let secWebSocketKey = request.headers["sec-websocket-key"] else { - return .badRequest(.text("Invalid value of 'Sec-Websocket-Key' header: \(request.headers["sec-websocket-key"] ?? "unknown")")) - } - let protocolSessionClosure: ((Socket) -> Void) = { socket in - let session = WebSocketSession(socket) - var fragmentedOpCode = WebSocketSession.OpCode.close - var payload = [UInt8]() // Used for fragmented frames. - - func handleTextPayload(_ frame: WebSocketSession.Frame) throws { - if let handleText = text { - if frame.fin { - if !payload.isEmpty { - throw WebSocketSession.WsError.protocolError("Continuing fragmented frame cannot have an operation code.") - } - var textFramePayload = frame.payload.map { Int8(bitPattern: $0) } - textFramePayload.append(0) - if let text = String(validatingUTF8: textFramePayload) { - handleText(session, text) - } else { - throw WebSocketSession.WsError.invalidUTF8("") - } - } else { - payload.append(contentsOf: frame.payload) - fragmentedOpCode = .text - } - } - } - - func handleBinaryPayload(_ frame: WebSocketSession.Frame) throws { - if let handleBinary = binary { - if frame.fin { - if !payload.isEmpty { - throw WebSocketSession.WsError.protocolError("Continuing fragmented frame cannot have an operation code.") - } - handleBinary(session, frame.payload) - } else { - payload.append(contentsOf: frame.payload) - fragmentedOpCode = .binary - } - } - } - - func handleOperationCode(_ frame: WebSocketSession.Frame) throws { - switch frame.opcode { - case .continue: - // There is no message to continue, failed immediatelly. - if fragmentedOpCode == .close { - socket.close() - } - frame.opcode = fragmentedOpCode - if frame.fin { - payload.append(contentsOf: frame.payload) - frame.payload = payload - // Clean the buffer. - payload = [] - // Reset the OpCode. - fragmentedOpCode = WebSocketSession.OpCode.close - } - try handleOperationCode(frame) - case .text: - try handleTextPayload(frame) - case .binary: - try handleBinaryPayload(frame) - case .close: - throw WebSocketSession.Control.close - case .ping: - if frame.payload.count > 125 { - throw WebSocketSession.WsError.protocolError("Payload gretter than 125 octets.") - } else { - session.writeFrame(ArraySlice(frame.payload), .pong) - } - case .pong: - if let handlePong = pong { - handlePong(session, frame.payload) - } - } - } - - func read() throws { - while true { - let frame = try session.readFrame() - try handleOperationCode(frame) - } - } - - connected?(session) - - do { - try read() - } catch { - switch error { - case WebSocketSession.Control.close: - // Normal close - break - case WebSocketSession.WsError.unknownOpCode: - print("Unknown Op Code: \(error)") - case WebSocketSession.WsError.unMaskedFrame: - print("Unmasked frame: \(error)") - case WebSocketSession.WsError.invalidUTF8: - print("Invalid UTF8 character: \(error)") - case WebSocketSession.WsError.protocolError: - print("Protocol error: \(error)") - default: - print("Unkown error \(error)") - } - // If an error occurs, send the close handshake. - session.writeCloseFrame() - } - - disconnected?(session) - } - let secWebSocketAccept = String.toBase64((secWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").sha1()) - let headers = ["Upgrade": "WebSocket", "Connection": "Upgrade", "Sec-WebSocket-Accept": secWebSocketAccept] - return HttpResponse.switchProtocols(headers, protocolSessionClosure) - } -} - -public class WebSocketSession: Hashable, Equatable { - public enum WsError: Error { case unknownOpCode(String), unMaskedFrame(String), protocolError(String), invalidUTF8(String) } - public enum OpCode: UInt8 { case `continue` = 0x00, close = 0x08, ping = 0x09, pong = 0x0a, text = 0x01, binary = 0x02 } - public enum Control: Error { case close } - - public class Frame { - public var opcode = OpCode.close - public var fin = false - public var rsv1: UInt8 = 0 - public var rsv2: UInt8 = 0 - public var rsv3: UInt8 = 0 - public var payload = [UInt8]() - } - - public let socket: Socket - - public init(_ socket: Socket) { - self.socket = socket - } - - deinit { - writeCloseFrame() - socket.close() - } - - public func writeText(_ text: String) { - self.writeFrame(ArraySlice(text.utf8), OpCode.text) - } - - public func writeBinary(_ binary: [UInt8]) { - self.writeBinary(ArraySlice(binary)) - } - - public func writeBinary(_ binary: ArraySlice) { - self.writeFrame(binary, OpCode.binary) - } - - public func writeFrame(_ data: ArraySlice, _ op: OpCode, _ fin: Bool = true) { - let finAndOpCode = UInt8(fin ? 0x80 : 0x00) | op.rawValue - let maskAndLngth = encodeLengthAndMaskFlag(UInt64(data.count), false) - do { - try self.socket.writeUInt8([finAndOpCode]) - try self.socket.writeUInt8(maskAndLngth) - try self.socket.writeUInt8(data) - } catch { - print(error) - } - } - - public func writeCloseFrame() { - writeFrame(ArraySlice("".utf8), .close) - } - - private func encodeLengthAndMaskFlag(_ len: UInt64, _ masked: Bool) -> [UInt8] { - let encodedLngth = UInt8(masked ? 0x80 : 0x00) - var encodedBytes = [UInt8]() - switch len { - case 0...125: - encodedBytes.append(encodedLngth | UInt8(len)) - case 126...UInt64(UINT16_MAX): - encodedBytes.append(encodedLngth | 0x7e) - encodedBytes.append(UInt8(len >> 8 & 0xff)) - encodedBytes.append(UInt8(len >> 0 & 0xff)) - default: - encodedBytes.append(encodedLngth | 0x7f) - encodedBytes.append(UInt8(len >> 56 & 0xff)) - encodedBytes.append(UInt8(len >> 48 & 0xff)) - encodedBytes.append(UInt8(len >> 40 & 0xff)) - encodedBytes.append(UInt8(len >> 32 & 0xff)) - encodedBytes.append(UInt8(len >> 24 & 0xff)) - encodedBytes.append(UInt8(len >> 16 & 0xff)) - encodedBytes.append(UInt8(len >> 08 & 0xff)) - encodedBytes.append(UInt8(len >> 00 & 0xff)) - } - return encodedBytes - } - - // swiftlint:disable function_body_length - public func readFrame() throws -> Frame { - let frm = Frame() - let fst = try socket.read() - frm.fin = fst & 0x80 != 0 - frm.rsv1 = fst & 0x40 - frm.rsv2 = fst & 0x20 - frm.rsv3 = fst & 0x10 - guard frm.rsv1 == 0 && frm.rsv2 == 0 && frm.rsv3 == 0 - else { - throw WsError.protocolError("Reserved frame bit has not been negociated.") - } - let opc = fst & 0x0f - guard let opcode = OpCode(rawValue: opc) else { - // "If an unknown opcode is received, the receiving endpoint MUST _Fail the WebSocket Connection_." - // http://tools.ietf.org/html/rfc6455#section-5.2 ( Page 29 ) - throw WsError.unknownOpCode("\(opc)") - } - if frm.fin == false { - switch opcode { - case .ping, .pong, .close: - // Control frames must not be fragmented - // https://tools.ietf.org/html/rfc6455#section-5.5 ( Page 35 ) - throw WsError.protocolError("Control frames must not be fragmented.") - default: - break - } - } - frm.opcode = opcode - let sec = try socket.read() - let msk = sec & 0x80 != 0 - guard msk else { - // "...a client MUST mask all frames that it sends to the server." - // http://tools.ietf.org/html/rfc6455#section-5.1 - throw WsError.unMaskedFrame("A client must mask all frames that it sends to the server.") - } - var len = UInt64(sec & 0x7f) - if len == 0x7e { - let b0 = UInt64(try socket.read()) << 8 - let b1 = UInt64(try socket.read()) - len = UInt64(littleEndian: b0 | b1) - } else if len == 0x7f { - let b0 = UInt64(try socket.read()) << 54 - let b1 = UInt64(try socket.read()) << 48 - let b2 = UInt64(try socket.read()) << 40 - let b3 = UInt64(try socket.read()) << 32 - let b4 = UInt64(try socket.read()) << 24 - let b5 = UInt64(try socket.read()) << 16 - let b6 = UInt64(try socket.read()) << 8 - let b7 = UInt64(try socket.read()) - len = UInt64(littleEndian: b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7) - } - - let mask = [try socket.read(), try socket.read(), try socket.read(), try socket.read()] - // Read payload all at once, then apply mask (calling `socket.read` byte-by-byte is super slow). - frm.payload = try socket.read(length: Int(len)) - for index in 0.. Bool { - return webSocketSession1.socket == webSocketSession2.socket -} diff --git a/TestTools/StreamChatTestMockServer/Utilities/TestData.swift b/TestTools/StreamChatTestMockServer/Utilities/TestData.swift deleted file mode 100644 index 446b9e940eb..00000000000 --- a/TestTools/StreamChatTestMockServer/Utilities/TestData.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright © 2026 Stream.io Inc. All rights reserved. -// - -@testable import StreamChat -import XCTest - -public enum TestData { - public static var uniqueId: String { UUID().uuidString } - - public static var currentDate: String { - stringTimestamp(Date()) - } - - public static func stringTimestamp(_ date: Date) -> String { - try! XCTUnwrap(DateFormatter.Stream.rfc3339DateString(from: date)) - } - - public static var currentTimeInterval: TimeInterval { - Date().timeIntervalSince1970 * 1000 - } - - public static var waitingEndTime: TimeInterval { - currentTimeInterval + 10000 - } - - public static func getMockResponse(fromFile file: MockFile) -> String { - String(decoding: XCTestCase.mockData(fromFile: file.filePath, bundle: .testTools), as: UTF8.self) - } - - public static func mockData(fromFile file: MockFile) -> [UInt8] { - [UInt8](XCTestCase.mockData(fromFile: file.filePath, bundle: .testTools)) - } - - public static func toJson(_ requestBody: [UInt8]) -> [String: Any] { - String(bytes: requestBody, encoding: .utf8)!.json - } - - public static func toJson(_ file: MockFile) -> [String: Any] { - toJson(mockData(fromFile: file)) - } - - public enum Reactions: String { - case love - case lol = "haha" - case wow - case sad - case like - } -} - -extension XCTestCase { - /// Loads a data from a file. - /// - Parameters: - /// - name: a file name. - /// - extension: a file extension. JSON by default. - /// - Returns: a file data. - public static func mockData(fromFile name: String, bundle: Bundle, extension: String = "json") -> Data { - guard let url = bundle.url(forResource: name, withExtension: `extension`) else { - XCTFail("\n❌ Mock file \"\(name).json\" not found in bundle \(bundle.bundleURL.lastPathComponent)") - return .init() - } - - return try! Data(contentsOf: url) - } -} diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7921a395588..0da0c8379e7 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -19,18 +19,19 @@ xcmetrics_path = "metrics/#{github_repo.split('/').last}-xcmetrics.json" testlab_bucket = 'gs://test-lab-af3rt9m4yh360-mqm1zzm767nhc' swift_environment_path = File.absolute_path('../Sources/StreamChat/Generated/SystemEnvironment+Version.swift') is_localhost = !is_ci +mock_server_driver_port = 4566 @force_check = false before_all do |lane| if is_ci setup_ci setup_git_config - select_xcode(version: xcode_version) unless [:sonar_upload, :allure_launch, :allure_upload, :pod_lint, :sync_mock_server, :copyright, :merge_main].include?(lane) + select_xcode(version: xcode_version) unless [:sonar_upload, :allure_launch, :allure_upload, :copyright, :merge_main].include?(lane) end end after_all do |lane| - stop_sinatra if lane == :test_e2e_mock + stop_mock_server if lane == :test_e2e_mock end desc "Build .xcframeworks" @@ -350,14 +351,33 @@ lane :test do |options| slather end -desc 'Starts Sinatra web server' -lane :start_sinatra do - sh('bundle exec ruby sinatra.rb > sinatra_log.txt 2>&1 &') +lane :start_mock_server do |options| + mock_server_repo = 'stream-chat-test-mock-server' + stop_mock_server if is_localhost + + if options[:local_server] + mock_server_repo = options[:local_server] + else + branch = options[:branch].to_s.empty? ? 'main' : options[:branch] + sh("rm -rf #{mock_server_repo}") if File.directory?(mock_server_repo) + sh("git clone -b #{branch} https://github.com/#{github_repo.split('/').first}/#{mock_server_repo}.git") + end + + pids = sh("lsof -ti :#{mock_server_driver_port} || true", log: false).strip + sh("kill -9 #{pids} || true", log: false) unless pids.empty? + + Dir.chdir(mock_server_repo) do + FileUtils.mkdir_p('logs') + sh("bundle exec ruby driver.rb #{mock_server_driver_port} > logs/driver.log 2>&1 &") + end end -desc 'Stops Sinatra web server' -lane :stop_sinatra do - sh('lsof -t -i:4567 | xargs kill -9') +lane :stop_mock_server do + begin + Net::HTTP.get_response(URI("http://localhost:#{mock_server_driver_port}/stop")) + rescue StandardError + nil + end end lane :build_test_app_and_frameworks do @@ -563,7 +583,7 @@ desc 'Runs e2e ui tests using mock server in Debug config' lane :test_e2e_mock do |options| next unless is_check_required(sources: sources_matrix[:e2e], force_check: @force_check) - start_sinatra + start_mock_server scan_options = { project: xcode_project, @@ -782,17 +802,6 @@ private_lane :update_testplan_on_ci do |options| update_testplan(path: options[:path], env_vars: { key: 'CI', value: 'TRUE' }) if is_ci end -lane :sync_mock_server do - sh('bundle exec ruby sync_mock_server.rb') - next unless is_ci - - pr_create( - title: '[CI] Sync Mock Server', - head_branch: "ci/sync-mock-server-#{Time.now.to_i}", - git_add: 'TestTools/StreamChatTestMockServer/Fixtures/' - ) -end - desc 'Run fastlane linting' lane :rubocop do next unless is_check_required(sources: sources_matrix[:ruby], force_check: @force_check) diff --git a/fastlane/sinatra.rb b/fastlane/sinatra.rb deleted file mode 100644 index 175109191d1..00000000000 --- a/fastlane/sinatra.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'sinatra' -require 'fileutils' -require 'stream-chat' - -jwt = { expiration_timeout: {}, generation_error_timeout: {} } - -post '/push/:udid/:bundle_id' do - push_data_file = 'push_payload.json' - File.write(push_data_file, request.body.read) - puts `xcrun simctl push #{params['udid']} #{params['bundle_id']} #{push_data_file}` -end - -post '/record_video/:udid/:test_name' do - recordings_dir = 'recordings' - video_base_name = "#{recordings_dir}/#{params['test_name']}" - recordings = (0..Dir["#{recordings_dir}/*"].length + 1).to_a - body = JSON.parse(request.body.read) - FileUtils.mkdir_p(recordings_dir) - - video_file = '' - if body['delete'] - recordings.reverse_each do |i| - video_file = "#{video_base_name}_#{i}.mp4" - break if File.exist?(video_file) - end - else - recordings.each do |i| - video_file = "#{video_base_name}_#{i}.mp4" - break unless File.exist?(video_file) - end - end - - if body['stop'] - simctl_processes = `pgrep simctl`.strip.split("\n") - simctl_processes.each { |pid| `kill -s SIGINT #{pid}` } - File.delete(video_file) if body['delete'] && File.exist?(video_file) - else - puts `xcrun simctl io #{params['udid']} recordVideo --codec h264 --force #{video_file} &` - end -end - -get '/jwt/:udid' do - time = Time.now.to_i - if time < jwt[:generation_error_timeout][params['udid']].to_i - halt(500, 'Intentional error') - elsif time < jwt[:expiration_timeout][params['udid']].to_i - 'eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoibHVrZV9za3l3YWxrZXIiLCJleHAiOjE2NjgwMTIzNTN9.UJ-LDHZFDP10sqpZU9bzPAChgersjDfqKjoi5Plg8qI' - else - client = StreamChat::Client.new(params[:api_key], ENV.fetch('STREAM_DEMO_APP_SECRET')) - expiration = time + 5 - client.create_token(params[:user_name], expiration) - end -end - -post '/jwt/revoke/:udid' do - jwt[:expiration_timeout] = install_jwt_timeout(udid: params['udid'], duration: params['duration']) - halt(200) -end - -post '/jwt/break/:udid' do - jwt[:generation_error_timeout] = install_jwt_timeout(udid: params['udid'], duration: params['duration']) - halt(200) -end - -def install_jwt_timeout(udid:, duration:) - { udid => Time.now.to_i + duration.to_i } -end diff --git a/fastlane/sync_mock_server.rb b/fastlane/sync_mock_server.rb deleted file mode 100644 index ff046fcbf38..00000000000 --- a/fastlane/sync_mock_server.rb +++ /dev/null @@ -1,281 +0,0 @@ -require 'net/http' -require 'json' -require 'securerandom' -require 'faye/websocket' -require 'eventmachine' -require 'uri' - -STREAM_BASE_URL = 'chat.stream-io-api.com' -STREAM_HTTP_URL = "https://#{STREAM_BASE_URL}" -STREAM_WSS_URL = "wss://#{STREAM_BASE_URL}/connect" -STREAM_DEMO_API_KEY = '8br4watad788' -STREAM_DEMO_TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibHVrZV9za3l3YWxrZXIifQ.kFSLHRB5X62t0Zlc7nwczWUfsQMwfkpylC6jCUZ6Mc0' -STREAM_USER_ID = 'luke_skywalker' -STREAM_HEADERS = { - 'Authorization' => STREAM_DEMO_TOKEN, - 'Stream-Auth-Type' => 'jwt', - 'Content-Type' => 'application/json' -} -MOCK_SERVER_FIXTURES_PATH = '../TestTools/StreamChatTestMockServer/Fixtures/JSONs' -TEST_TOOLS_FIXTURES_PATH = '../TestTools/StreamChatTestTools/Fixtures/Images' - -def connect_endpoint - payload = { - user_id: STREAM_USER_ID, - user_details: { - id: STREAM_USER_ID, - name: 'Luke Skywalker', - image: 'https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg', - birthland: 'Tatooine' - }, - server_determines_connection_id: true - }.to_json - query_params = ["api_key=#{STREAM_DEMO_API_KEY}", "json=#{URI.encode_www_form_component(payload)}"] - "#{STREAM_WSS_URL}?#{query_params.join('&')}" -end - -def establish_websocket_connection(event_data) - health_check = JSON.parse(event_data) - health_check['me']['channel_mutes'] = [] - health_check['me']['mutes'] = [] - health_check['me']['devices'] = [] - save_json(health_check, 'ws_health_check.json') - health_check['connection_id'] -end - -def request_channels(connection_id) - payload = { - filter_conditions: { - members: { '$in': [STREAM_USER_ID] } - }, - limit: 20, - member_limit: 30, - message_limit: 25, - watch: true - }.to_json - query_params = [ - "api_key=#{STREAM_DEMO_API_KEY}", - "connection_id=#{connection_id}", - "payload=#{URI.encode_www_form_component(payload)}" - ] - endpoint = "#{STREAM_HTTP_URL}/channels?#{query_params.join('&')}" - response = http_get(endpoint) - response['channels'] = [response['channels'][0]] - response['channels'][0]['members'].each_with_index do |member, i| - response['channels'][0]['read'][i] = {} - response['channels'][0]['read'][i]['user'] = member['user'] - response['channels'][0]['read'][i]['unread_messages'] = 0 - response['channels'][0]['read'][i]['last_read'] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ') - end - - save_json(response, 'http_channels.json') -end - -def send_typing_event(channel_id) - payload = { event: { type: 'typing.start' } }.to_json - endpoint = "#{STREAM_HTTP_URL}/channels/messaging/#{channel_id}/event?api_key=#{STREAM_DEMO_API_KEY}" - response = http_post(endpoint, payload) - save_json(response, 'http_events.json') -end - -def send_message(channel_id, text, filename) - message_id = SecureRandom.uuid - payload = { - message: { - id: message_id, - show_in_channel: false, - pinned: false, - silent: false, - text: text - } - }.to_json - endpoint = "#{STREAM_HTTP_URL}/channels/messaging/#{channel_id}/message?api_key=#{STREAM_DEMO_API_KEY}" - response = http_post(endpoint, payload) - save_json(response, filename) - message_id -end - -def send_youtube_link(channel_id) - send_message(channel_id, 'https://youtube.com/watch?v=xOX7MsrbaPY', 'http_youtube_link.json') -end - -def send_ephemeral_message(channel_id) - send_message(channel_id, '/giphy Test', 'http_message_ephemeral.json') -end - -def send_unsplash_link(channel_id) - send_message(channel_id, 'https://unsplash.com/photos/1_2d3MRbI9c', 'http_unsplash_link.json') -end - -def send_giphy_link(channel_id) - send_message(channel_id, 'https://giphy.com/gifs/test-gw3IWyGkC0rsazTi', 'http_giphy_link.json') -end - -def create_channel(connection_id) - payload = { - data: { - members: [STREAM_USER_ID, 'lando_calrissian', 'count_dooku'], - name: 'Sync Mock Server' - }, - presence: true, - state: true, - watch: true, - messages: { limit: 25 } - }.to_json - channel_id = SecureRandom.uuid - query_params = ["api_key=#{STREAM_DEMO_API_KEY}", "connection_id=#{connection_id}"] - endpoint = "#{STREAM_HTTP_URL}/channels/messaging/#{channel_id}/query?#{query_params.join('&')}" - response = http_post(endpoint, payload) - save_json(response, 'http_channel_creation.json') - channel_id -end - -def add_reaction(message_id) - payload = { - enforce_unique: false, - reaction: { - type: 'like', - score: 1 - } - }.to_json - endpoint = "#{STREAM_HTTP_URL}/messages/#{message_id}/reaction?api_key=#{STREAM_DEMO_API_KEY}" - response = http_post(endpoint, payload) - save_json(response, 'http_reaction.json') -end - -def truncate_channel_with_message(channel_id) - payload = { - hard_delete: true, - skip_push: false, - message: { - id: SecureRandom.uuid, - show_in_channel: false, - pinned: false, - silent: false, - text: 'Channel truncated' - } - }.to_json - endpoint = "#{STREAM_HTTP_URL}/channels/messaging/#{channel_id}/truncate?api_key=#{STREAM_DEMO_API_KEY}" - response = http_post(endpoint, payload) - save_json(response, 'http_truncate.json') -end - -def add_member_to_channel(channel_id) - payload = { - add_members: ['leia_organa'], - hide_history: false - }.to_json - endpoint = "#{STREAM_HTTP_URL}/channels/messaging/#{channel_id}?api_key=#{STREAM_DEMO_API_KEY}" - response = http_post(endpoint, payload) - save_json(response, 'http_add_member.json') -end - -def remove_channel(channel_id) - endpoint = "#{STREAM_HTTP_URL}/channels/messaging/#{channel_id}?api_key=#{STREAM_DEMO_API_KEY}" - response = http_delete(endpoint) - save_json(response, 'http_channel_removal.json') -end - -def send_attachment(channel_id) - boundary = "----RubyMultipartPostBoundary" - image_path = File.expand_path("#{TEST_TOOLS_FIXTURES_PATH}/yoda.jpg") - image_basename = File.basename(image_path) - image = File.open(image_path, 'rb') - payload = [] - payload << "--#{boundary}\r\n" - payload << "Content-Disposition: form-data; name=\"file\"; filename=\"#{image_basename}\"\r\n" - payload << "Content-Type: image/jpeg\r\n\r\n" - payload << image - payload << "\r\n--#{boundary}--\r\n" - headers = STREAM_HEADERS.dup - headers['Content-Type'] = "multipart/form-data; boundary=#{boundary}" - endpoint = "#{STREAM_HTTP_URL}/channels/messaging/#{channel_id}/image?api_key=#{STREAM_DEMO_API_KEY}" - response = http_post(endpoint, payload.join, headers) - save_json(response, 'http_attachment.json') -end - -def save_json(data, filename) - File.write("#{MOCK_SERVER_FIXTURES_PATH}/#{filename}", JSON.pretty_generate(data)) - puts("✅ #{filename}") -end - -def http_get(url, headers = STREAM_HEADERS) - uri = URI(url) - request = Net::HTTP::Get.new(uri, headers) - response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(request) - end - JSON.parse(response.body) -end - -def http_post(url, payload, headers = STREAM_HEADERS) - uri = URI(url) - request = Net::HTTP::Post.new(uri, headers) - request.body = payload - response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(request) - end - JSON.parse(response.body) -end - -def http_delete(url, headers = STREAM_HEADERS) - uri = URI(url) - request = Net::HTTP::Delete.new(uri, headers) - response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| - http.request(request) - end - JSON.parse(response.body) -end - -EM.run do - ws = Faye::WebSocket::Client.new(connect_endpoint, nil, headers: STREAM_HEADERS) - - ws.on(:message) do |event| - case JSON.parse(event.data)['type'] - when 'health.check' - next if @connection_id - - @connection_id = establish_websocket_connection(event.data) - channel_id = create_channel(@connection_id) - request_channels(@connection_id) - send_typing_event(channel_id) - message_id = send_message(channel_id, 'Test', 'http_message.json') - add_reaction(message_id) - add_member_to_channel(channel_id) - send_attachment(channel_id) - send_ephemeral_message(channel_id) - send_youtube_link(channel_id) - send_unsplash_link(channel_id) - send_giphy_link(channel_id) - truncate_channel_with_message(channel_id) - remove_channel(channel_id) - when 'typing.start' - save_json(JSON.parse(event.data), 'ws_events.json') - when 'message.new' - next if @new_message - - @new_message = 1 - save_json(JSON.parse(event.data), 'ws_message.json') - when 'reaction.new' - save_json(JSON.parse(event.data), 'ws_reaction.json') - when 'member.added' - save_json(JSON.parse(event.data), 'ws_events_member.json') - when 'channel.updated' - json = JSON.parse(event.data) - json['user']['privacy_settings']['typing_indicators']['enabled'] = true - json['user']['privacy_settings']['read_receipts']['enabled'] = true - json['channel']['members'].each do |member| - member['user']['privacy_settings']['typing_indicators']['enabled'] = true - member['user']['privacy_settings']['read_receipts']['enabled'] = true - end - save_json(json, 'ws_events_channel.json') - when 'channel.deleted' - ws.close - end - end - - ws.on(:close) do |event| - ws = nil - exit 0 - end -end