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) + "" + node + ">"
- } else {
- let current = scopesBuffer[Process.tid] ?? ""
- scopesBuffer[Process.tid] = output + ">" + current + "" + node + ">"
- }
-
- // 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