Skip to content

Conversation

@testableapple
Copy link
Contributor

@testableapple testableapple commented Jan 9, 2026

🔗 Issue Links

Resolve https://linear.app/stream/issue/IOS-932

🎯 Goal

Share mock server across mobile platforms.

🛠 Implementation

  • Get rid of Swifter
  • Implement a custom mock server using Sinatra
  • Move all the existing ruby/sinatra scripts to the mock server
  • Unblock some tests that were blocked due to Swifter flakiness
  • Comment out the iOS 16 e2e cron check due to unexpected hanging (also happens on develop)

📝 Notes

🧪 Testing

On CI:

Locally:

bundle exec fastlane start_mock_server
bundle exec fastlane test_e2e_mock device:"iPhone 17" # or run any test(s) on Xcode
bundle exec fastlane stop_mock_server

Summary by CodeRabbit

  • Chores

    • CI workflows updated to collect new mock-server logs and switch to a driver-based mock server; legacy sync workflow and Sinatra tooling removed.
    • Test mock-server infrastructure reorganized and simplified (new driver protocol, request helpers, and enums).
  • Tests

    • Many UI and backend tests updated to use new mock-server APIs and renamed helpers; added token invalidation authentication tests.
  • Documentation

    • Removed CI cron-checks badge from README.

✏️ Tip: You can customize this high-level summary in your review settings.

# Conflicts:
#	Gemfile.lock
#	README.md
#	StreamChat.xcodeproj/project.pbxproj
#	StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
#	StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift
#	StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift
#	TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift
#	TestTools/StreamChatTestMockServer/Extensions/String.swift
#	TestTools/StreamChatTestMockServer/Extensions/Swifter.swift
#	TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift
#	TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift
#	TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift
#	TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift
#	TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift
#	TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift
#	TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift
#	TestTools/StreamChatTestMockServer/MockServer/MessageList.swift
#	TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift
#	TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift
#	TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift
#	TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift
#	TestTools/StreamChatTestMockServer/MockServer/User.swift
#	TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift
#	TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift
#	TestTools/StreamChatTestMockServer/Swifter/Errno.swift
#	TestTools/StreamChatTestMockServer/Swifter/Files.swift
#	TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift
#	TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift
#	TestTools/StreamChatTestMockServer/Swifter/HttpResponse.swift
#	TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift
#	TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift
#	TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift
#	TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift
#	TestTools/StreamChatTestMockServer/Swifter/Process.swift
#	TestTools/StreamChatTestMockServer/Swifter/Scopes.swift
#	TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift
#	TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift
#	TestTools/StreamChatTestMockServer/Swifter/Socket.swift
#	TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift
#	TestTools/StreamChatTestMockServer/Swifter/String+File.swift
#	TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift
#	TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift
#	TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift
#	TestTools/StreamChatTestMockServer/Utilities/TestData.swift
#	fastlane/Fastfile
@testableapple testableapple requested a review from a team as a code owner January 9, 2026 13:01
@testableapple testableapple added the 🤖 CI/CD Any work related to CI/CD label Jan 9, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Migrate test infrastructure from an in-process Sinatra/Swifter mock server and JSON fixtures to a driver-based external mock server. Remove embedded Swifter server and many fixtures; introduce a driver-driven StreamMockServer client, new programmatic mock endpoints/types, update Fastlane/CI, robots, and test callsites to use HTTP driver endpoints.

Changes

Cohort / File(s) Summary
CI Workflows & Artifacts
.github/workflows/cron-checks.yml, .github/workflows/smoke-checks.yml
Updated artifact paths to collect fastlane/stream-chat-test-mock-server/logs/* instead of fastlane/sinatra_log.txt.
Workflow Removal
.github/workflows/sync-mock-server.yml
Deleted scheduled/manual workflow that synced the Sinatra mock server.
Repo Metadata
.gitignore, Gemfile, README.md
Ignore stream-chat-test-mock-server; removed stream-chat-ruby gem; removed cron-checks badge in README.
Fastlane & Scripts
fastlane/Fastfile, fastlane/sinatra.rb, fastlane/sync_mock_server.rb
Added start/stop lanes and driver port; removed Sinatra start/stop and sync scripts; start/stop now use mock-server driver.
Xcode Project
StreamChat.xcodeproj/...
Project file updated: removed JSON fixtures and wiring; added Swift source references (data/types/launch args). Review resource mapping and build phases.
Swifter Framework (embedded)
TestTools/StreamChatTestMockServer/Swifter/*
Removed entire Swifter HTTP/WebSocket server and related utilities (many files).
Fixtures → Programmatic Data
TestTools/StreamChatTestMockServer/Fixtures/JSONs/*
Deleted numerous JSON fixture files used by the old mock server.
Mock Server Core
TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift
Reworked in-process server into a driver client: new initializer init(driverPort:testName:), postRequest/getRequest, stores url/port; removed internal state/websocket-based features.
Mock Server Helpers Removed
MockServer/* (many files)
Removed large suite of mock-server endpoint implementations, helpers, builders, and public enums/constants (channels, messages, reactions, websockets, attachments, members, configs, test data).
New Mock Types & Enums
TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift
Added public enums: AttachmentType, ReactionType, MessageDeliveryStatus.
Robots / Backend API
TestTools/StreamChatTestMockServer/Robots/*
BackendRobot and ParticipantRobot refactored to call mockServer.postRequest/getRequest; added HTTP-driven methods (generateChannels, delayNewMessages, token controls, many participant actions) and updated signatures/returns. Review new endpoints and callsites.
Test Base & Setup Changes
StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift and many Tests/*
Switched servermockServer, added useMockServer and backendHandler(), removed many setUp/tearDown overrides, and updated many test callsites to new API shapes (e.g., generateChannels(channelsCount:), uploadGiphy, sendMessageInThread, delayNewMessages).
UI Test Helpers & Robots
StreamChatUITestsAppUITests/Robots/*.swift, Pages/*.swift
Numerous API/signature updates: delivery-status types now StreamChatTestMockServer.MessageDeliveryStatus, reaction/attachment types, new assertions (e.g., assertFirstMessageIsVisible, assertChannelPreviewIsEmpty), and parameterized mentions/participants.
JWT & App Launch Changes
StreamChatUITestsApp/ViewController.swift, MockServer LaunchArgument
JWT retrieval now uses MOCK_SERVER_PORT and /jwt/get?platform=ios; removed old MockServerConfiguration enum.
Test Files Added/Removed
StreamChatUITestsAppUITests/Tests/*
Added multiple authentication token invalidation tests and a participant file-upload test; removed some test files and the Tags helper (StreamTestCase+Tags).
Utilities Removed
TestTools/.../Extensions/*
Removed string/dictionary/swifter extensions used by old in-process server (JSON helpers, route registration, delayed responses).

Sequence Diagram(s)

sequenceDiagram
  participant TestRunner as XCTest/Test Runner
  participant MockClient as StreamMockServer (client)
  participant MockDriver as Mock Server Driver (HTTP)
  participant App as XCUIApplication / App

  TestRunner->>MockClient: init(driverPort, testName)
  MockClient->>MockDriver: GET /start?testName=...
  MockDriver-->>MockClient: 200 OK { port, url }
  TestRunner->>App: launch with env MOCK_SERVER_PORT
  TestRunner->>MockClient: POST /<endpoint> (e.g., generateChannels, delayNewMessages)
  MockClient->>MockDriver: POST /<endpoint> (JSON)
  MockDriver->>App: trigger events / serve responses (via driver)
  App-->>TestRunner: UI updates / assertions
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

"🐰 I hopped from Sinatra to driver, bright and spry,
Logs now stream where mock-servers lie.
Fixtures folded into enums and code,
Robots hum HTTP — tests lighten the load.
A cheerful rabbit cheers: hop, test, go fly!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title '[CI] Implement new cross-platform mock server' accurately summarizes the main change of replacing Swifter with a new cross-platform mock server implementation.
Linked Issues check ✅ Passed Changes fulfill IOS-932 requirements: Swifter removed, custom mock server implemented, Sinatra scripts consolidated, Fastfile updated with new lanes, and test infrastructure refactored for cross-platform support.
Out of Scope Changes check ✅ Passed All changes align with mock server migration scope: test infrastructure updates, workflow modifications, fixture removal, and API refactoring directly support the Swifter-to-mock-server transition objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0be1a4e and 6e782bb.

📒 Files selected for processing (1)
  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
🧰 Additional context used
📓 Path-based instructions (1)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Write Swift code compatible with iOS deployment targets specified in Package.swift and podspec files; do not lower deployment targets without approval
Use SwiftLint with strict mode and respect .swiftlint.yml rules; justify and scope any exceptions rather than suppressing rules broadly
Run SwiftFormat for code formatting and respect repository-specific style conventions
Never commit API keys or customer data; use obvious placeholders (e.g., YOUR_STREAM_KEY) in example code
Follow the project's zero-warnings policy: fix new warnings and avoid introducing any

Files:

  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Tests/**/*.swift : Add or extend tests in the matching module's Tests/ folder for changes to StreamChat or StreamChatUI code, covering core models, API surfaces, and view controller behaviors; use fakes/mocks from test helpers provided by the repo
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Maintain high test coverage when changing code in the Stream iOS Chat SDK
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChat/**/*.swift : Ensure public API changes in StreamChat and StreamChatUI include inline documentation and migration notes
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChatUI/**/*.swift : Ensure public API changes in StreamChatUI include inline documentation and migration notes
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Tests/**/*.swift : Add or extend tests in the matching module's Tests/ folder for changes to StreamChat or StreamChatUI code, covering core models, API surfaces, and view controller behaviors; use fakes/mocks from test helpers provided by the repo

Applied to files:

  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChatUI/**/*.swift : Ensure public API changes in StreamChatUI include inline documentation and migration notes

Applied to files:

  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChat/**/*.swift : Ensure public API changes in StreamChat and StreamChatUI include inline documentation and migration notes

Applied to files:

  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Maintain high test coverage when changing code in the Stream iOS Chat SDK

Applied to files:

  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to CHANGELOG* : Update CHANGELOG entries for user-visible SDK changes in StreamChat and StreamChatUI

Applied to files:

  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Prioritize backwards compatibility and API stability when changing code in the Stream iOS Chat SDK

Applied to files:

  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
🧬 Code graph analysis (1)
StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift (10)
TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift (1)
  • generateChannels (15-46)
StreamChatUITestsAppUITests/Robots/UserRobot.swift (5)
  • sendMessage (94-112)
  • sleep (67-72)
  • sendMessageInThread (281-305)
  • openThread (224-237)
  • deleteMessage (121-128)
TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift (7)
  • sendMessage (53-62)
  • startTyping (17-21)
  • stopTyping (29-33)
  • sleep (41-45)
  • sendMultipleMessages (86-98)
  • sendMessageInThread (100-108)
  • deleteMessage (120-124)
Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift (1)
  • sendMessage (155-174)
Sources/StreamChat/Controllers/ChannelController/ChannelController.swift (1)
  • message (2157-2162)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/TypingEventsSender_Mock.swift (2)
  • startTyping (34-39)
  • stopTyping (41-47)
Sources/StreamChat/Workers/TypingEventsSender.swift (3)
  • startTyping (63-72)
  • stopTyping (74-88)
  • stopTyping (105-111)
StreamChatUITestsAppUITests/Pages/ChannelListPage.swift (1)
  • name (38-40)
Sources/StreamChat/Controllers/MessageController/MessageController.swift (1)
  • deleteMessage (350-356)
Sources/StreamChat/Repositories/MessageRepository.swift (1)
  • replies (466-476)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build SDKs (Old Xcode)
  • GitHub Check: Test LLC (Debug)
  • GitHub Check: Build Test App and Frameworks
  • GitHub Check: Metrics
🔇 Additional comments (11)
StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift (11)

194-218: LGTM!

The typing indicator test correctly uses the new simplified startTyping()/stopTyping() APIs and properly references participantRobot.name for the assertion. The test flow is clean and matches the new robot implementations.


260-263: Test skipped with Linear tracking issue.

The skip is appropriately placed with a tracking issue. Note that all code after line 263 is unreachable until the issue is resolved. This is acceptable for temporarily skipped tests.


314-317: LGTM!

The fluent sleep(1) call before sending the message ensures proper timing when the app is in the background. This aligns with the new ParticipantRobot.sleep(_:) API.


556-568: LGTM!

The mention-related APIs are properly updated to use explicit parameters (participantRobot.id for mentioning, participantRobot.name for assertion), which improves clarity and aligns with the new robot signatures.


818-821: LGTM!

The rename from sendGiphy to uploadGiphy better reflects the action (uploading without immediately sending when send: false).


719-721: LGTM!

The waitForThreadIcon: true parameter is correctly used when the thread reply was just sent by the participant and the UI needs time to update the thread icon visibility.


961-963: Verify expected thread reply count after deletion.

The assertion expects replies: 1 after the thread reply is deleted. This seems to indicate that deleted replies still count toward the total. Please confirm this matches the expected behavior, as it could be confusing if the UI shows "1 reply" but the reply is deleted.


1041-1062: LGTM!

The hard delete test correctly uses participantRobot.deleteMessage(hard: true) and properly asserts the message is hard-deleted with assertHardDeletedMessage(withText:).


500-502: LGTM!

The generateChannels(channelsCount:messagesCount:repliesCount:) call correctly uses the new parameter names and includes repliesCount for thread pagination testing.


749-771: LGTM. The thread typing indicator test correctly uses the startTypingInThread() and stopTypingInThread() methods from ParticipantRobot. The pattern mirrors the channel-level typing test appropriately, and the assertion correctly captures the typing user name.


777-800: LGTM!

The mockServer.forbiddenWord property provides cleaner, type-safe access compared to the previous server.forbiddenWords.first pattern. The usage at line 781 is correct and aligns with the mock server migration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift (1)

43-55: Use optional binding instead of force unwrap on the static property.

While StreamMockServer.port is initialized before backendHandler() runs in the current code flow, force unwrapping a static property that persists across tests is error-prone. Replace the force unwrap with optional binding to handle potential future changes and improve code robustness.

🔒 Suggested fix
 private func backendHandler() {
+    guard let port = StreamMockServer.port else {
+        XCTFail("StreamMockServer.port is not set")
+        return
+    }
     app.setEnvironmentVariables([
         .websocketHost: "ws://localhost",
         .httpHost: "http://localhost",
-        .port: StreamMockServer.port!
+        .port: port
     ])
     
     if useMockServer {
         app.setLaunchArguments(.useMockServer)
     } else if let switchApiKey {
         app.setEnvironmentVariables([.customApiKey: switchApiKey])
     }
 }
🤖 Fix all issues with AI agents
In @StreamChatUITestsApp/ViewController.swift:
- Line 213: Replace the forced unwrap of
ProcessInfo.processInfo.environment["MOCK_SERVER_PORT"] with safe optional
handling: use guard let portString =
ProcessInfo.processInfo.environment["MOCK_SERVER_PORT"] to check presence, then
convert to an Int (guard let port = Int(portString)) and handle the failure path
(return, throw, use a sensible default, or call XCTFail with a clear message).
Update the binding currently named port so it is non‑optional after these guards
and ensure any downstream code that used the forced-unwrapped value uses this
safely unwrapped variable instead.

In @StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift:
- Around line 347-359: The test can crash when the message list is empty because
computing cells.count - 1 underflows; in assertFirstMessageIsVisible add a guard
that cells is not empty before calling messageCell(withIndex:), and if empty
call XCTFail with a clear message (include file and line) and return self so the
test fails gracefully; then compute the last index safely (e.g., let lastIndex =
cells.count - 1) and continue using messageCell(withIndex:) and the existing
assertions.

In @StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift:
- Line 10: Replace the hard-coded demo API key assigned to switchApiKey
("8br4watad788") with an obvious placeholder string (e.g., "YOUR_STREAM_KEY") so
test/example code does not contain real/demo credentials; update the assignment
to set switchApiKey to the placeholder and leave a comment if needed to indicate
where to obtain a real key for testing.

In @TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift:
- Around line 65-91: getRequest currently force-unwraps url in its default
parameter (baseUrl: String = url!), which can crash if url is nil; mirror the
postRequest fix: make baseUrl optional (e.g., baseUrl: String? = nil), then
inside getRequest resolve a safe base string by using the provided baseUrl or
falling back to the instance url only after safely unwrapping it (or return an
early default result when neither is present), and use that safe base value to
construct the URL for the request.
- Around line 34-63: The postRequest function currently force-unwraps the shared
property url via the default parameter baseUrl: String = url!, which can crash
if url is nil; change the signature to accept an optional baseUrl (e.g.,
baseUrl: String? = nil) and inside postRequest resolve a non-optional base using
let base = baseUrl ?? url and guard that base is non-nil (or return an explicit
error/status), then build the request with that safe base; remove the
force-unwrap and ensure you handle the missing-base case deterministically
before creating the URL and request.

In @TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift:
- Around line 179-189: Two methods are misspelled: rename pinMesage to
pinMessage and unpinMesage to unpinMessage in ParticipantRobot so the API is
clear; update the method declarations (preserve @discardableResult, return type
ParticipantRobot and the existing mockServer.postRequest(endpoint: "...") calls)
and then update any callers/tests to use the new names; keep the endpoint
strings ("participant/message?action=pin" and "...?action=unpin") unchanged.
- Around line 64-84: The code force-unwraps
ProcessInfo.processInfo.environment["SIMULATOR_UDID"] inside
ParticipantRobot.sendPushNotification which will crash outside a simulator;
replace the forced unwrap with a guard let udid =
ProcessInfo.processInfo.environment["SIMULATOR_UDID"] else {
fatalError("SIMULATOR_UDID not set: sendPushNotification requires a simulator
UDID") } (or preconditionFailure/log + return self if you prefer non-fatal
behavior) before building the endpoint and calling mockServer.postRequest so the
failure is reported with a clear message instead of an unexpected crash.
🧹 Nitpick comments (9)
.gitignore (1)

103-103: Add trailing slash for consistency with other directory entries.

The mock server directory entry should follow the same pattern as other build/generated directories in the file (e.g., build/, .build/, Pods/ on lines 14, 54, and 61).

♻️ Proposed fix
-stream-chat-test-mock-server
+stream-chat-test-mock-server/
fastlane/Fastfile (1)

356-356: Consider removing redundant cleanup call.

The stop_mock_server call on line 356 is only executed on localhost, but lines 366-367 unconditionally kill any process on port 4566 regardless of environment. The conditional stop adds an HTTP request overhead without clear benefit.

♻️ Simplify cleanup logic
 lane :start_mock_server do |options|
   mock_server_repo = 'stream-chat-test-mock-server'
-  stop_mock_server if is_localhost

   if options[:local_server]
StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift (1)

80-141: Consider improving variable naming and assertion consistency.

The test uses a variable named message to represent test scenario identifiers ("null", "empty", "42"), which can be confusing since these are also used as message content. Additionally, lines 137-139 use hardcoded strings instead of the message variables.

♻️ Suggested improvements

Consider renaming variables to clarify their purpose and using them consistently in assertions:

         WHEN("participant sends a message (push body param is nil)") {
-            let message = "null"
+            let testScenario = "null"
             participantRobot
                 .sleep(1)
-                .sendMessage(message)
+                .sendMessage(testScenario)
                 .sendPushNotification(
-                    title: message,
-                    body: message,
+                    title: testScenario,
+                    body: testScenario,
                     bundleId: app.bundleId(),
-                    rest: message
+                    rest: testScenario
                 )
         }
         THEN("user does not receive a push notification") {
             userRobot.assertPushNotificationDoesNotAppear()
         }
         WHEN("participant sends a message (push body param is empty)") {
-            let message = "empty"
+            let testScenario = "empty"
             participantRobot
-                .sendMessage(message)
+                .sendMessage(testScenario)
                 .sendPushNotification(
-                    title: message,
-                    body: message,
+                    title: testScenario,
+                    body: testScenario,
                     bundleId: app.bundleId(),
-                    rest: message
+                    rest: testScenario
                 )
         }
         THEN("user does not receive a push notification") {
             userRobot.assertPushNotificationDoesNotAppear()
         }
         WHEN("participant sends a message (push body param contains incorrect type)") {
-            let message = "42"
+            let testScenario = "42"
             participantRobot
-                .sendMessage(message)
+                .sendMessage(testScenario)
                 .sendPushNotification(
-                    title: message,
-                    body: message,
+                    title: testScenario,
+                    body: testScenario,
                     bundleId: app.bundleId(),
                     rest: "incorrect_type"
                 )
         }

And update the assertions to use the variables:

         THEN("message list updates") {
+            let firstScenario = "null"
+            let secondScenario = "empty"
+            let thirdScenario = "42"
             userRobot
-                .assertMessage("null", at: 2)
-                .assertMessage("empty", at: 1)
-                .assertMessage("42", at: 0)
+                .assertMessage(firstScenario, at: 2)
+                .assertMessage(secondScenario, at: 1)
+                .assertMessage(thirdScenario, at: 0)
         }

Or extract these test scenarios as constants at the class level for better maintainability.

TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift (1)

29-38: Consider making the computed property public or removing it.

The status computed property is internal while the enum is public. If external modules need to access the string value, they can use rawValue directly, making the status property potentially redundant. The same applies to attachment and reaction properties in the other enums.

♻️ Option: Remove redundant computed properties

If rawValue is sufficient for all use cases:

 public enum MessageDeliveryStatus: String {
     case read
     case pending
     case sent
     case failed
-    
-    var status: String {
-        return rawValue
-    }
 }

Similar changes for AttachmentType.attachment and ReactionType.reaction.

StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift (1)

42-45: Consider replacing sleep with an explicit synchronization mechanism.

The hardcoded 2-second sleep could make the test brittle and slow. If the mock server provides a way to confirm message processing or delivery, prefer that over a fixed delay.

TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift (2)

15-46: URL-encode query parameter values to handle special characters.

The messagesText and repliesText parameters are inserted directly into the query string without URL encoding. If these contain special characters (spaces, &, =, etc.), the endpoint URL will be malformed.

♻️ Suggested fix using percent encoding
     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)&"
+            let encoded = messagesText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? messagesText
+            messagesTextQueryParam = "messages_text=\(encoded)&"
         }
         var repliesTextQueryParam = ""
         if let repliesText {
-            repliesTextQueryParam = "replies_text=\(repliesText)&"
+            let encoded = repliesText.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? repliesText
+            repliesTextQueryParam = "replies_text=\(encoded)&"
         }

95-120: Force-unwrap on static URL may crash if mock server hasn't been initialized.

StreamMockServer.url! on Line 98 will crash if the mock server was never initialized. While waitForMockServerToStart is typically called after initialization, adding a guard or using optional binding would make this more defensive.

♻️ Defensive guard
 private func waitForMockServerToStart() {
+    guard let baseUrl = StreamMockServer.url else {
+        XCTFail("MockServer URL is not configured")
+        return
+    }
     let startTime = Date().timeIntervalSince1970
     while Date().timeIntervalSince1970 - startTime < 5.0 {
-        var request = URLRequest(url: URL(string: "\(StreamMockServer.url!)/ping")!)
+        var request = URLRequest(url: URL(string: "\(baseUrl)/ping")!)
         request.httpMethod = "GET"
TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift (2)

11-11: Remove unused urlSession property.

The urlSession property is declared but never used; URLSession.shared is used directly in the request methods.

♻️ Remove unused property
 public final class StreamMockServer {
     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

12-15: Duplicate jwtTimeout declarations.

jwtTimeout is declared both as a static property (Line 12) and an instance property (Line 15). This is redundant and could lead to confusion about which one to use.

♻️ Remove duplicate instance property
     public static let jwtTimeout: UInt32 = 5
     public let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"] ?? ""
     public let forbiddenWord: String = "wth"
-    public let jwtTimeout: UInt32 = 5
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ad9a4de and e6ed5e0.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (93)
  • .github/workflows/cron-checks.yml
  • .github/workflows/smoke-checks.yml
  • .github/workflows/sync-mock-server.yml
  • .gitignore
  • Gemfile
  • README.md
  • StreamChat.xcodeproj/project.pbxproj
  • StreamChatUITestsApp/ViewController.swift
  • StreamChatUITestsAppUITests/Pages/ChannelListPage.swift
  • StreamChatUITestsAppUITests/Pages/MessageListPage.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot.swift
  • StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift
  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
  • StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift
  • StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift
  • StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift
  • StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift
  • StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift
  • StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift
  • TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift
  • TestTools/StreamChatTestMockServer/Extensions/String.swift
  • TestTools/StreamChatTestMockServer/Extensions/Swifter.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_add_member.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_attachment.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_creation.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_removal.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channels.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_events.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_member.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message_ephemeral.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_reaction.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_truncate.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_unsplash_link.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_youtube_link.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/push_notification.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_channel.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_member.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_health_check.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_message.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_reaction.json
  • TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift
  • TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift
  • TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift
  • TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift
  • TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift
  • TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift
  • TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift
  • TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift
  • TestTools/StreamChatTestMockServer/MockServer/MessageList.swift
  • TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift
  • TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift
  • TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift
  • TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift
  • TestTools/StreamChatTestMockServer/MockServer/User.swift
  • TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift
  • TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift
  • TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift
  • TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift
  • TestTools/StreamChatTestMockServer/Swifter/Errno.swift
  • TestTools/StreamChatTestMockServer/Swifter/Files.swift
  • TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift
  • TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift
  • TestTools/StreamChatTestMockServer/Swifter/HttpResponse.swift
  • TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift
  • TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift
  • TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift
  • TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift
  • TestTools/StreamChatTestMockServer/Swifter/Process.swift
  • TestTools/StreamChatTestMockServer/Swifter/Scopes.swift
  • TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift
  • TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift
  • TestTools/StreamChatTestMockServer/Swifter/Socket.swift
  • TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift
  • TestTools/StreamChatTestMockServer/Swifter/String+File.swift
  • TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift
  • TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift
  • TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift
  • TestTools/StreamChatTestMockServer/Utilities/TestData.swift
  • fastlane/Fastfile
  • fastlane/sinatra.rb
  • fastlane/sync_mock_server.rb
💤 Files with no reviewable changes (64)
  • README.md
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events.json
  • TestTools/StreamChatTestMockServer/MockServer/MembersResponse.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_creation.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channel_removal.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_attachment.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_health_check.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_truncate.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_reaction.json
  • TestTools/StreamChatTestMockServer/Swifter/Errno.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_message.json
  • .github/workflows/sync-mock-server.yml
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_channel.json
  • StreamChatUITestsAppUITests/Tests/StreamTestCase+Tags.swift
  • TestTools/StreamChatTestMockServer/MockServer/MessageResponses.swift
  • TestTools/StreamChatTestMockServer/MockServer/ChannelConfig.swift
  • TestTools/StreamChatTestMockServer/Swifter/DemoServer.swift
  • TestTools/StreamChatTestMockServer/MockServer/User.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_channels.json
  • TestTools/StreamChatTestMockServer/Swifter/HttpParser.swift
  • StreamChatUITestsAppUITests/Tests/UserDetails_Tests.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_unsplash_link.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_events_member.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/push_notification.json
  • TestTools/StreamChatTestMockServer/Swifter/Socket+File.swift
  • TestTools/StreamChatTestMockServer/Swifter/HttpResponse.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_events.json
  • TestTools/StreamChatTestMockServer/Swifter/HttpRequest.swift
  • TestTools/StreamChatTestMockServer/MockServer/ChannelResponses.swift
  • fastlane/sinatra.rb
  • TestTools/StreamChatTestMockServer/Extensions/String.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/ws_reaction.json
  • TestTools/StreamChatTestMockServer/Swifter/Files.swift
  • TestTools/StreamChatTestMockServer/Swifter/HttpRouter.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_member.json
  • TestTools/StreamChatTestMockServer/MockServer/DeviceRemoteControl.swift
  • Gemfile
  • StreamChatUITestsAppUITests/Tests/Reactions_Tests.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_add_member.json
  • TestTools/StreamChatTestMockServer/Swifter/String+BASE64.swift
  • TestTools/StreamChatTestMockServer/Swifter/String+Misc.swift
  • TestTools/StreamChatTestMockServer/Swifter/Socket+Server.swift
  • TestTools/StreamChatTestMockServer/MockServer/MessageList.swift
  • TestTools/StreamChatTestMockServer/Swifter/HttpServer.swift
  • TestTools/StreamChatTestMockServer/Extensions/Dictionary.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message_ephemeral.json
  • TestTools/StreamChatTestMockServer/Swifter/HttpServerIO.swift
  • TestTools/StreamChatTestMockServer/MockServer/WebsocketResponses.swift
  • TestTools/StreamChatTestMockServer/Extensions/Swifter.swift
  • TestTools/StreamChatTestMockServer/Swifter/String+SHA1.swift
  • TestTools/StreamChatTestMockServer/MockServer/EventResponses.swift
  • TestTools/StreamChatTestMockServer/Utilities/TestData.swift
  • TestTools/StreamChatTestMockServer/MockServer/AttachmentResponses.swift
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_youtube_link.json
  • TestTools/StreamChatTestMockServer/Fixtures/JSONs/http_message.json
  • TestTools/StreamChatTestMockServer/Swifter/String+File.swift
  • TestTools/StreamChatTestMockServer/Swifter/MimeTypes.swift
  • TestTools/StreamChatTestMockServer/Swifter/Socket.swift
  • fastlane/sync_mock_server.rb
  • TestTools/StreamChatTestMockServer/MockServer/ReactionResponses.swift
  • TestTools/StreamChatTestMockServer/Swifter/Process.swift
  • TestTools/StreamChatTestMockServer/Swifter/WebSockets.swift
  • TestTools/StreamChatTestMockServer/MockServer/MockServerAttributes.swift
  • TestTools/StreamChatTestMockServer/Swifter/Scopes.swift
🧰 Additional context used
📓 Path-based instructions (1)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Write Swift code compatible with iOS deployment targets specified in Package.swift and podspec files; do not lower deployment targets without approval
Use SwiftLint with strict mode and respect .swiftlint.yml rules; justify and scope any exceptions rather than suppressing rules broadly
Run SwiftFormat for code formatting and respect repository-specific style conventions
Never commit API keys or customer data; use obvious placeholders (e.g., YOUR_STREAM_KEY) in example code
Follow the project's zero-warnings policy: fix new warnings and avoid introducing any

Files:

  • TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift
  • StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift
  • StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift
  • TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift
  • StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift
  • StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift
  • StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift
  • TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift
  • StreamChatUITestsAppUITests/Pages/MessageListPage.swift
  • StreamChatUITestsAppUITests/Pages/ChannelListPage.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift
  • StreamChatUITestsApp/ViewController.swift
  • TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot.swift
  • StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift
  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
  • TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Tests/**/*.swift : Add or extend tests in the matching module's Tests/ folder for changes to StreamChat or StreamChatUI code, covering core models, API surfaces, and view controller behaviors; use fakes/mocks from test helpers provided by the repo
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Tests/**/*.swift : Add or extend tests in the matching module's Tests/ folder for changes to StreamChat or StreamChatUI code, covering core models, API surfaces, and view controller behaviors; use fakes/mocks from test helpers provided by the repo

Applied to files:

  • .github/workflows/cron-checks.yml
  • TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift
  • StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift
  • StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift
  • StreamChat.xcodeproj/project.pbxproj
  • .github/workflows/smoke-checks.yml
  • TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift
  • .gitignore
  • StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift
  • StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift
  • StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift
  • TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift
  • StreamChatUITestsAppUITests/Pages/MessageListPage.swift
  • StreamChatUITestsAppUITests/Pages/ChannelListPage.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift
  • StreamChatUITestsApp/ViewController.swift
  • TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot.swift
  • StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift
  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
  • fastlane/Fastfile
  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
  • TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChat/**/*.swift : Ensure public API changes in StreamChat and StreamChatUI include inline documentation and migration notes

Applied to files:

  • TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift
  • StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift
  • StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift
  • StreamChat.xcodeproj/project.pbxproj
  • TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift
  • StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift
  • StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift
  • StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift
  • TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift
  • StreamChatUITestsAppUITests/Pages/MessageListPage.swift
  • StreamChatUITestsAppUITests/Pages/ChannelListPage.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift
  • StreamChatUITestsApp/ViewController.swift
  • TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot.swift
  • StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift
  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
  • TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChatUI/**/*.swift : Ensure public API changes in StreamChatUI include inline documentation and migration notes

Applied to files:

  • TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift
  • StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift
  • StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift
  • StreamChat.xcodeproj/project.pbxproj
  • TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift
  • StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift
  • StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift
  • StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift
  • TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift
  • StreamChatUITestsAppUITests/Pages/MessageListPage.swift
  • StreamChatUITestsAppUITests/Pages/ChannelListPage.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift
  • StreamChatUITestsApp/ViewController.swift
  • TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot.swift
  • StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift
  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
  • TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Maintain high test coverage when changing code in the Stream iOS Chat SDK

Applied to files:

  • StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift
  • StreamChat.xcodeproj/project.pbxproj
  • TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift
  • StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift
  • StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift
  • StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift
  • StreamChatUITestsAppUITests/Pages/MessageListPage.swift
  • StreamChatUITestsAppUITests/Pages/ChannelListPage.swift
  • StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift
  • TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift
  • StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot.swift
  • StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift
  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
  • StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
  • TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to CHANGELOG* : Update CHANGELOG entries for user-visible SDK changes in StreamChat and StreamChatUI

Applied to files:

  • StreamChat.xcodeproj/project.pbxproj
  • .gitignore
  • StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift
  • StreamChatUITestsAppUITests/Pages/MessageListPage.swift
  • StreamChatUITestsAppUITests/Pages/ChannelListPage.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot.swift
  • StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift
  • StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift
  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
  • TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to **/*.swift : Never commit API keys or customer data; use obvious placeholders (e.g., YOUR_STREAM_KEY) in example code

Applied to files:

  • StreamChat.xcodeproj/project.pbxproj
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Prioritize backwards compatibility and API stability when changing code in the Stream iOS Chat SDK

Applied to files:

  • TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: For UI changes in StreamChatUI, attach comparison screenshots (before/after) in pull requests where feasible

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Prefer additive changes and avoid breaking public APIs in StreamChat and StreamChatUI

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
🧬 Code graph analysis (13)
StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift (4)
StreamChatUITestsAppUITests/Robots/UserRobot.swift (2)
  • login (20-24)
  • openChannel (53-58)
TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift (1)
  • uploadAttachment (191-195)
TestTools/StreamChatTestTools/TestData/DummyData/MessageAttachmentPayload.swift (1)
  • file (72-86)
StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift (1)
  • assertFile (1127-1141)
TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift (1)
TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift (1)
  • postRequest (34-63)
StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift (1)
TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift (1)
  • generateChannels (15-46)
StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift (3)
StreamChatUITestsAppUITests/Robots/UserRobot.swift (1)
  • uploadGiphy (381-393)
TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift (1)
  • uploadGiphy (149-153)
TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift (1)
  • generateChannels (15-46)
StreamChatUITestsAppUITests/Pages/MessageListPage.swift (1)
StreamChatUITestsAppUITests/Pages/ChannelListPage.swift (1)
  • statusCheckmark (58-67)
StreamChatUITestsAppUITests/Pages/ChannelListPage.swift (1)
StreamChatUITestsAppUITests/Pages/MessageListPage.swift (1)
  • statusCheckmark (211-220)
StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift (3)
StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift (1)
  • setUpWithError (8-12)
StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift (1)
  • setUpWithError (9-13)
TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift (1)
  • setEnvironmentVariables (40-44)
StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift (4)
TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift (2)
  • delayNewMessages (54-58)
  • generateChannels (15-46)
TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift (2)
  • readMessage (47-51)
  • sendMessageInThread (100-108)
StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift (2)
  • assertMessageDeliveryStatus (567-577)
  • assertMessageReadCount (579-595)
StreamChatUITestsAppUITests/Robots/UserRobot.swift (5)
  • removeParticipant (513-519)
  • sendMessageInThread (281-305)
  • login (20-24)
  • setConnectivity (545-550)
  • openChannel (53-58)
StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift (3)
TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift (1)
  • delayNewMessages (54-58)
TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift (2)
  • readMessage (47-51)
  • sendMessageInThread (100-108)
StreamChatUITestsAppUITests/Robots/UserRobot.swift (1)
  • sendMessageInThread (281-305)
StreamChatUITestsAppUITests/Robots/UserRobot.swift (4)
TestTools/StreamChatTestTools/Extensions/EndpoinPath+Equatable.swift (1)
  • _ (9-53)
Sources/StreamChat/Controllers/MessageController/MessageController.swift (2)
  • addReaction (720-742)
  • deleteReaction (748-757)
Sources/StreamChat/Workers/MessageUpdater.swift (4)
  • addReaction (583-629)
  • addReaction (1169-1191)
  • deleteReaction (636-660)
  • deleteReaction (1257-1263)
StreamChatUITestsAppUITests/Pages/MessageListPage.swift (3)
  • threadReplyCountButton (171-173)
  • text (183-185)
  • text (191-193)
StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift (4)
TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift (1)
  • generateChannels (15-46)
StreamChatUITestsAppUITests/Robots/UserRobot.swift (8)
  • sendMessage (94-112)
  • sleep (67-72)
  • scrollMessageListUpSlow (358-364)
  • mentionParticipant (480-490)
  • sendMessageInThread (281-305)
  • openThread (224-237)
  • uploadGiphy (381-393)
  • deleteMessage (121-128)
TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/TypingEventsSender_Mock.swift (2)
  • startTyping (34-39)
  • stopTyping (41-47)
StreamChatUITestsAppUITests/Pages/ChannelListPage.swift (1)
  • name (38-40)
StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift (3)
TestTools/StreamChatTestMockServer/Robots/BackendRobot.swift (5)
  • invalidateToken (65-68)
  • invalidateTokenDate (70-73)
  • invalidateTokenSignature (75-78)
  • revokeToken (60-63)
  • breakTokenGeneration (80-83)
StreamChatUITestsAppUITests/Robots/UserRobot.swift (1)
  • login (20-24)
StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift (1)
  • assertConnectionStatus (173-183)
StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift (3)
StreamChatUITestsAppUITests/Pages/ChannelListPage.swift (1)
  • lastMessage (46-48)
StreamChatUITestsAppUITests/Pages/MessageListPage.swift (4)
  • text (183-185)
  • text (191-193)
  • title (291-293)
  • threadReplyCountButton (171-173)
Sources/StreamChat/Models/ChatMessage.swift (1)
  • deliveryStatus (563-586)
🔇 Additional comments (61)
StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift (1)

9-9: LGTM: Property rename aligns with new mock server infrastructure.

The change from mockServerEnabled to useMockServer is consistent with the broader refactoring to use the new cross-platform mock server. Disabling the mock server for backend integration tests is appropriate.

fastlane/Fastfile (5)

22-22: LGTM! Port constant properly defined.

The mock server driver port is clearly defined and consistently used throughout the mock server lifecycle lanes.


29-29: LGTM! Exclusion list updated correctly.

The removal of sync_mock_server from the exclusion list is consistent with the migration away from Sinatra-based flows.


34-34: LGTM! Cleanup logic updated for new mock server.

The migration from stop_sinatra to stop_mock_server is correct and maintains proper cleanup after e2e mock tests.


375-381: LGTM! Cleanup with appropriate error handling.

The stop lane correctly sends a stop request to the driver and gracefully handles errors. Silent error swallowing is acceptable here since this is a best-effort cleanup operation.


586-586: LGTM! E2E mock tests updated to use new server.

The migration from start_sinatra to start_mock_server is correct and completes the transition to the driver-based mock server infrastructure.

StreamChatUITestsApp/ViewController.swift (1)

213-214: Verify the new mock server endpoint supports the updated JWT request format.

The JWT endpoint has been migrated from the previous format to /jwt/get?platform=ios. Since the mock server implementation is external to this repository (as part of PR #3895's cross-platform mock server), confirm that the external mock server implementation includes this endpoint and returns JWT tokens in the format expected by the client.

StreamChatUITestsAppUITests/Tests/SlowMode_Tests.swift (1)

131-131: LGTM! API parameter name updated correctly.

The parameter name change from count to channelsCount aligns with the BackendRobot API update shown in the relevant code snippets, making the intent more explicit.

TestTools/StreamChatTestMockServer/MockServer/LaunchArgument.swift (1)

37-37: LGTM! Improved Swift idiom.

Using key path syntax (\.rawValue) instead of a closure is more concise and idiomatic in modern Swift.

StreamChatUITestsAppUITests/Tests/PushNotification_Tests.swift (3)

22-78: LGTM! Well-structured test simplification.

The refactoring to use the checkHappyPath helper method reduces code duplication and improves test maintainability. Each test clearly expresses its intent through the helper's parameters.


143-171: LGTM! Well-designed helper method.

The checkHappyPath helper method is well-refactored with:

  • Clear, parameterized interface with sensible defaults
  • Proper handling of optional title parameter
  • Clean conditional logic for assertions

This design reduces duplication across tests while maintaining flexibility.


8-14: Add documentation explaining the iOS 17 minimum requirement.

The iOS 17 version check significantly reduces E2E test coverage (excluding iOS 14, 15, 16) but lacks explanation in the code. Unit tests for push notification functionality don't require iOS 17.

Please add a comment clarifying:

  • Is iOS 17+ required due to push notification API changes or testing framework limitations?
  • Are iOS 14-16 behaviors sufficiently covered by the unit tests (snoozePushNotifications, handleNotification, etc.)?
StreamChat.xcodeproj/project.pbxproj (1)

585-586: LGTM!

The project file changes correctly integrate the new DataTypes.swift and LaunchArgument.swift files into the MockServer group:

  • UUID references are consistent across PBXBuildFile, PBXFileReference, PBXGroup, and PBXSourcesBuildPhase sections.
  • Files are properly added to the StreamChatTestMockServer target's sources build phase.

Also applies to: 3593-3594, 6200-6201, 12353-12355

StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift (8)

6-6: LGTM on the import change.

The import switch to @testable import StreamChatCommonUI aligns with the mock server migration. This provides access to the necessary test infrastructure types.


38-56: LGTM on the new empty channel preview assertion.

Clean implementation following the existing pattern with proper file/line forwarding for debugging.


94-110: Type migration looks good.

The delivery status parameter now uses the fully-qualified StreamChatTestMockServer.MessageDeliveryStatus? type, consistent with the mock server refactor across the test suite.


248-277: Push notification assertion signature updated correctly.

The rename from text/sender to title/body better reflects the actual push notification structure. The assertion logic remains sound.


551-565: Consistent type migration for delivery status assertions.

All delivery status methods now use StreamChatTestMockServer.MessageDeliveryStatus?, maintaining consistency with the new mock server types.

Also applies to: 567-577, 879-888


711-718: Good refactor to parameterize the user name.

Removing the hardcoded user name and accepting it as a parameter improves test flexibility and reusability.


956-959: Improved thread reply count pluralization.

The ternary for singular/plural ("1 Thread Reply" vs "N Thread Replies") is a nice UX improvement for test assertions.


1059-1064: Verify error message matches mock server implementation.

The error message "Sorry, command \(invalidCommand) doesn't exist. Try posting your message without the starting /" is hardcoded in the test assertion, but the mock server implementation that generates this response is not accessible in the repository. Manual verification against the actual mock server or its documentation is required to confirm this string matches the backend response exactly.

StreamChatUITestsAppUITests/Tests/Performance/ChannelListScrollTime.swift (1)

9-13: LGTM on the property rename.

The change from mockServerEnabled to useMockServer aligns with the test infrastructure refactor. Setting it to false is correct for performance tests that need the real backend.

StreamChatUITestsAppUITests/Tests/Performance/MessageListScrollTime.swift (1)

10-14: LGTM on the API migration.

The generateChannels call correctly uses the new parameter labels (channelsCount:, attachments:) matching the updated BackendRobot signature shown in the relevant code snippets.

StreamChatUITestsAppUITests/Pages/ChannelListPage.swift (1)

58-67: LGTM on the type migration.

The statusCheckmark function now accepts StreamChatTestMockServer.MessageDeliveryStatus?, consistent with the parallel implementation in MessageListPage.swift. The logic remains unchanged.

StreamChatUITestsAppUITests/Tests/Attachments_Tests.swift (2)

7-7: Historical note preserved.

The comment about attachment tests freezing on iOS > 18 is useful context for future debugging.


50-63: LGTM on the new file upload test.

The test follows the established pattern from test_participantUploadsImage and test_participantUploadsVideo. It correctly uses AttachmentType.file from the new mock server types and leverages the existing assertFile helper.

TestTools/StreamChatTestMockServer/MockServer/DataTypes.swift (2)

7-15: LGTM on AttachmentType enum.

Clear and well-structured enum for test attachment types.


17-27: LGTM on ReactionType enum.

The lol = "haha" mapping is a nice touch to align with backend expectations while keeping a readable case name.

StreamChatUITestsAppUITests/Pages/MessageListPage.swift (1)

211-220: LGTM on the type migration.

The statusCheckmark function update is consistent with ChannelListPage.swift. Both use the fully-qualified StreamChatTestMockServer.MessageDeliveryStatus? type with identical logic.

StreamChatUITestsAppUITests/Tests/ChannelList_Tests.swift (4)

60-60: LGTM - parameter rename improves clarity.

The parameter rename from count: to channelsCount: makes the API more explicit and self-documenting.


186-186: LGTM - method rename aligns with new API.

The rename from replyToMessageInThread to sendMessageInThread is consistent with the mock server migration and maintains the same functionality.


237-237: LGTM - consistent API usage.

The generateChannels parameter update is consistent with the broader migration to the new mock server API.


261-286: Good test coverage for channel truncation without message.

The new test case properly validates the edge case where a channel is truncated without a system message, ensuring both the message list and channel preview reflect the empty state correctly.

StreamChatUITestsAppUITests/Tests/Ephemeral_Messages_Tests.swift (3)

8-8: LGTM - helpful historical context.

The NOTE comment documents a previously problematic behavior, providing useful context for future developers.


18-18: LGTM - uploadGiphy naming is more accurate.

The rename from sendGiphy to uploadGiphy better reflects the two-step process (upload then optionally send) and aligns with the new mock server API.

Also applies to: 34-34, 79-79, 98-98, 117-117, 136-136


111-111: LGTM - consistent parameter naming.

The parameter rename is consistent with the mock server API migration.

StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus_Tests.swift (5)

45-45: LGTM - improved API naming and signature.

The rename from delayServerResponse(byTimeInterval:) to delayNewMessages(by:) is more explicit about what's being delayed, and using integer seconds simplifies the API.

Also applies to: 515-515


87-87: LGTM - simplified API removes timing concerns from test code.

The rename from readMessageAfterDelay() to readMessage() simplifies the test API by moving delay handling to the mock server implementation.

Also applies to: 109-109, 137-137, 267-267, 338-338, 388-388, 561-561, 583-583, 608-608


291-291: LGTM - consistent method rename.

The rename from replyToMessageInThread to sendMessageInThread is consistent with the mock server migration across all test files.

Also applies to: 311-311, 335-335, 360-360, 385-385, 414-414, 442-442


143-143: LGTM - cleaner participant reference.

Using participantRobot.id directly eliminates the need for local variables and keeps the participant state centralized.

Also applies to: 394-394, 611-611


304-304: LGTM - consistent parameter naming.

The parameter rename is consistent with the mock server API migration.

StreamChatUITestsAppUITests/Tests/Message Delivery Status/MessageDeliveryStatus+ChannelList_Tests.swift (3)

24-24: LGTM - consistent with mock server migration.

The API changes in this file are consistent with the broader migration to the new mock server infrastructure.


97-97: LGTM - simplified read API.

The change from readMessageAfterDelay() to readMessage() is consistent with the mock server migration.

Also applies to: 222-222


166-166: LGTM - consistent thread message API.

The rename from replyToMessageInThread to sendMessageInThread is consistent with the mock server migration.

Also applies to: 194-194, 219-219, 250-250, 275-275

StreamChatUITestsAppUITests/Tests/Authentication_Tests.swift (3)

13-101: Excellent test coverage for token invalidation scenarios.

The six new test cases comprehensively cover token invalidation edge cases (generic, date, and signature invalidation) both before and after login. The tests follow the BDD pattern consistently and properly verify that the app successfully refreshes the token in each scenario.


107-107: LGTM - consistent API migration.

The change from server.revokeJwt() to backendRobot.revokeToken() aligns with the mock server migration.


181-181: LGTM - improved API naming.

The rename from server.breakJwt() to backendRobot.breakTokenGeneration() is more descriptive and aligns with the mock server migration.

StreamChatUITestsAppUITests/Tests/QuotedReply_Tests.swift (1)

1-802: LGTM!

The test file consistently migrates to the new mock server API surface:

  • generateChannels(count:)generateChannels(channelsCount:)
  • quoteMessage(..., toLastMessage:)quoteMessage(..., last:)
  • Thread-specific helpers renamed appropriately (quoteMessageInThread, quoteMessageWithAttachmentInThread, etc.)

All test assertions remain logically correct after the API changes.

StreamChatUITestsAppUITests/Tests/MessageList_Tests.swift (1)

1-1061: LGTM!

The test file correctly migrates to the new mock server APIs:

  • generateChannels(channelsCount:, messagesCount:, repliesCount:) signature
  • participantRobot.sendMessageInThread() replaces replyToMessageInThread()
  • participantRobot.startTyping()/stopTyping() for typing indicators
  • userRobot.mentionParticipant(participantRobot.id) with explicit userId
  • mockServer.forbiddenWord for moderation testing
  • uploadGiphy() replaces sendGiphy()

All test logic remains correct after the API changes.

StreamChatUITestsAppUITests/Robots/UserRobot.swift (5)

154-197: LGTM!

The reaction handling is correctly updated to use ReactionType enum instead of TestData.Reactions. The switch statement handles all expected cases with a sensible default fallback to .like.


223-237: LGTM!

The openThread method properly handles the new waitForThreadIcon parameter with conditional waiting behavior. The existence check and fallback to context menu is preserved.


282-305: LGTM!

sendMessageInThread correctly implements thread reply functionality with the alsoSendInChannel checkbox handling and delegates to the existing sendMessage implementation.


382-416: LGTM!

The rename from sendGiphy to uploadGiphy is consistently applied, and the replyWithGiphy and replyWithGiphyInThread methods correctly call the renamed method.


481-490: LGTM!

mentionParticipant now correctly accepts a userId parameter and constructs the mention text from it, enabling explicit control over which participant to mention.

TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift (2)

7-62: LGTM!

The ParticipantRobot class is cleanly structured with:

  • Clear public API surface (name, id, initializer)
  • Consistent endpoint-based interactions via mockServer.postRequest()
  • Fluent API pattern with @discardableResult returns

100-247: LGTM!

The threading, quoting, attachment, and reaction methods are well-organized with consistent patterns:

  • Thread operations use thread=true query param
  • Quote operations use quote_last=true/quote_first=true
  • Attachment operations use the AttachmentType.attachment property
  • Reaction operations use the ReactionType.reaction property
StreamChatUITestsAppUITests/Tests/Base TestCase/StreamTestCase.swift (3)

14-15: LGTM: Clean test setup flags.

The rename to mockServer and the new useMockServer flag provide clear control over test execution mode. The default of true aligns with the PR objective to use the new mock server by default.


34-34: LGTM: Proper mock server cleanup.

The teardown correctly stops the mock server after terminating the app, ensuring resources are released in the proper order.


22-28: The port "4566" is intentionally hardcoded as a shared port allocation driver, not as the mock server port itself. The StreamMockServer architecture dynamically allocates unique mock server ports per test (via the testName parameter), which already supports parallel test execution. No changes are needed—the current design properly handles concurrent test runs.

.github/workflows/cron-checks.yml (1)

83-83: Artifact path updated consistently across workflows.

The log path change matches the update in smoke-checks.yml and correctly reflects the migration to the new mock server infrastructure.

.github/workflows/smoke-checks.yml (1)

225-225: Artifact path correctly configured for mock server logs.

The path change from fastlane/sinatra_log.txt to fastlane/stream-chat-test-mock-server/logs/* is verified. The mock server's start_mock_server fastlane lane explicitly creates the logs directory via FileUtils.mkdir_p('logs') and writes the driver log to logs/driver.log, making the wildcard pattern appropriate for capturing all generated logs. The path is consistently applied across both smoke-checks.yml and cron-checks.yml workflows.

Comment on lines +354 to 373
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add health check after starting the mock server.

The mock server is started in the background without verifying it's ready to accept connections. This could cause race conditions where tests begin before the server is fully initialized, leading to flaky test failures.

🔧 Suggested improvement

Add a health check loop after starting the server:

   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 &")
+    
+    # Wait for server to be ready
+    max_attempts = 30
+    attempt = 0
+    server_ready = false
+    
+    until server_ready || attempt >= max_attempts
+      begin
+        Net::HTTP.get_response(URI("http://localhost:#{mock_server_driver_port}/health"))
+        server_ready = true
+        UI.success("Mock server is ready on port #{mock_server_driver_port}")
+      rescue StandardError
+        attempt += 1
+        sleep(1)
+      end
+    end
+    
+    UI.user_error!("Mock server failed to start within #{max_attempts} seconds") unless server_ready
   end

Note: This assumes the driver exposes a /health endpoint. Adjust the endpoint path based on your actual implementation.

Committable suggestion skipped: line range outside the PR's diff.

override func setUpWithError() throws {
mockServerEnabled = false
useMockServer = false
switchApiKey = "8br4watad788"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "8br4watad788" --type swift

Repository: GetStream/stream-chat-swift

Length of output: 369


🏁 Script executed:

# Check context around the DemoUsers.swift entry
rg -B 2 -A 2 "8br4watad788" demoapp --type swift

# Also search for comments about demo/test keys
rg -i "demo.*key|test.*key|apikey" DemoApp/Shared/DemoUsers.swift --type swift | head -20

Repository: GetStream/stream-chat-swift

Length of output: 1208


This is a demo/test API key, but replace with an obvious placeholder per guidelines.

Confirmed: "8br4watad788" is a dedicated demo key (labeled "UIKit default" in DemoApiKeys). However, the coding guidelines require using obvious placeholders like YOUR_STREAM_KEY in test and example code, even for demo credentials. Update to use a placeholder instead.

🤖 Prompt for AI Agents
In @StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift at line 10,
Replace the hard-coded demo API key assigned to switchApiKey ("8br4watad788")
with an obvious placeholder string (e.g., "YOUR_STREAM_KEY") so test/example
code does not contain real/demo credentials; update the assignment to set
switchApiKey to the placeholder and leave a comment if needed to indicate where
to obtain a real key for testing.

Comment on lines +34 to 63
@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)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Force-unwrap in default parameter value is unsafe.

The default value baseUrl: String = url! will crash at call-time if url is nil. Consider making it optional or using a guard inside the method.

🐛 Suggested fix
 @discardableResult
 public func postRequest(
-    baseUrl: String = url!,
+    baseUrl: String? = nil,
     endpoint: String,
     body: Data = Data(),
     async: Bool = false
 ) -> (body: String, statusCode: Int) {
+    guard let resolvedUrl = baseUrl ?? StreamMockServer.url else {
+        XCTFail("Mock server URL is not configured")
+        return ("", 0)
+    }
-    let url = URL(string: "\(baseUrl)/\(endpoint)")!
+    let url = URL(string: "\(resolvedUrl)/\(endpoint)")!
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@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)
}
@discardableResult
public func postRequest(
baseUrl: String? = nil,
endpoint: String,
body: Data = Data(),
async: Bool = false
) -> (body: String, statusCode: Int) {
guard let resolvedUrl = baseUrl ?? StreamMockServer.url else {
XCTFail("Mock server URL is not configured")
return ("", 0)
}
let url = URL(string: "\(resolvedUrl)/\(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()
}
task.resume()
semaphore?.wait()
return (output, statusCode)
}
🤖 Prompt for AI Agents
In @TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift around
lines 34 - 63, The postRequest function currently force-unwraps the shared
property url via the default parameter baseUrl: String = url!, which can crash
if url is nil; change the signature to accept an optional baseUrl (e.g.,
baseUrl: String? = nil) and inside postRequest resolve a non-optional base using
let base = baseUrl ?? url and guard that base is non-nil (or return an explicit
error/status), then build the request with that safe base; remove the
force-unwrap and ensure you handle the missing-base case deterministically
before creating the URL and request.

Comment on lines +65 to 91
@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)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same force-unwrap issue in getRequest default parameter.

Apply the same fix as postRequest to avoid crashes when url is nil.

🤖 Prompt for AI Agents
In @TestTools/StreamChatTestMockServer/MockServer/StreamMockServer.swift around
lines 65 - 91, getRequest currently force-unwraps url in its default parameter
(baseUrl: String = url!), which can crash if url is nil; mirror the postRequest
fix: make baseUrl optional (e.g., baseUrl: String? = nil), then inside
getRequest resolve a safe base string by using the provided baseUrl or falling
back to the instance url only after safely unwrapping it (or return an early
default result when neither is present), and use that safe base value to
construct the URL for the request.

Comment on lines 64 to 84
@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
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Force-unwrap on SIMULATOR_UDID environment variable.

Line 71 uses force-unwrap on SIMULATOR_UDID which will crash if run outside a simulator environment. Consider adding a guard with a meaningful error.

🐛 Suggested fix
 public func sendPushNotification(
     title: String?,
     body: String,
     bundleId: String,
     rest: String? = nil
 ) -> ParticipantRobot {
-    let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"]!
+    guard let udid = ProcessInfo.processInfo.environment["SIMULATOR_UDID"] else {
+        assertionFailure("SIMULATOR_UDID environment variable not found - must run on simulator")
+        return self
+    }
     var endpoint = "participant/push?" +
🤖 Prompt for AI Agents
In @TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift around
lines 64 - 84, The code force-unwraps
ProcessInfo.processInfo.environment["SIMULATOR_UDID"] inside
ParticipantRobot.sendPushNotification which will crash outside a simulator;
replace the forced unwrap with a guard let udid =
ProcessInfo.processInfo.environment["SIMULATOR_UDID"] else {
fatalError("SIMULATOR_UDID not set: sendPushNotification requires a simulator
UDID") } (or preconditionFailure/log + return self if you prefer non-fatal
behavior) before building the endpoint and calling mockServer.postRequest so the
failure is reported with a clear message instead of an unexpected crash.

Comment on lines 179 to 189
@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
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo in method names: pinMesage and unpinMesage.

The method names are missing an 's' — should be pinMessage and unpinMessage.

🐛 Proposed fix
 @discardableResult
-public func pinMesage() -> ParticipantRobot {
+public func pinMessage() -> ParticipantRobot {
     _ = mockServer.postRequest(endpoint: "participant/message?action=pin")
     return self
 }

 @discardableResult
-public func unpinMesage() -> ParticipantRobot {
+public func unpinMessage() -> ParticipantRobot {
     _ = mockServer.postRequest(endpoint: "participant/message?action=unpin")
     return self
 }
🤖 Prompt for AI Agents
In @TestTools/StreamChatTestMockServer/Robots/ParticipantRobot.swift around
lines 179 - 189, Two methods are misspelled: rename pinMesage to pinMessage and
unpinMesage to unpinMessage in ParticipantRobot so the API is clear; update the
method declarations (preserve @discardableResult, return type ParticipantRobot
and the existing mockServer.postRequest(endpoint: "...") calls) and then update
any callers/tests to use the new names; keep the endpoint strings
("participant/message?action=pin" and "...?action=unpin") unchanged.

@testableapple testableapple changed the title [CI][v5] Implement new cross-platform mock server (#3895) [CI] Implement new cross-platform mock server (#3895) Jan 9, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift (1)

942-960: Use the localization constant instead of hardcoded English strings.

Line 957 hardcodes the pluralization logic ("1 Thread Reply" vs "N Thread Replies"). While these strings currently match the English localization, the test should use the same localization method as the UI does:

Suggested fix
let expectedText = L10n.Message.Threads.count(replies)

This ensures the test stays in sync with the localization strings defined in Localizable.stringsdict and matches the approach used in ChatMessageContentView.swift line 710.

🤖 Fix all issues with AI agents
In @StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift:
- Around line 347-359: In assertFirstMessageIsVisible, guard that cells.count >
0 before computing let messageCell = messageCell(withIndex: cells.count - 1); if
count is 0 call XCTFail with a clear message (include file and line) and return
self to fail fast; this prevents passing -1 into messageCell(withIndex:) and
avoids undefined behavior when accessing cells.element(boundBy:).
🧹 Nitpick comments (1)
StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift (1)

249-277: Consider more specific assertions for push notification content.

The current implementation uses contains checks for both title and body. This could lead to false positives if there's overlap between the expected title and body strings (e.g., if title is a substring of body or vice versa).

♻️ Optional refinement for more precise validation

If push notification structure allows, consider verifying that title and body appear in distinct UI elements:

 let pushNotification = SpringBoard.notificationBanner.wait()
 XCTAssertTrue(
     pushNotification.exists,
     "Push notification should appear",
     file: file,
     line: line
 )
-
-let pushNotificationContent = pushNotification.text
-XCTAssertTrue(
-    pushNotificationContent.contains(body),
-    "\(pushNotificationContent) does not contain \(body)",
-    file: file,
-    line: line
-)
-XCTAssertTrue(
-    pushNotificationContent.contains(title),
-    "\(pushNotificationContent) does not contain \(title)",
-    file: file,
-    line: line
-)
+
+// If SpringBoard exposes separate title/body elements, use those instead
+let notificationTitle = pushNotification.staticTexts.matching(identifier: "NotificationTitle").firstMatch
+let notificationBody = pushNotification.staticTexts.matching(identifier: "NotificationBody").firstMatch
+XCTAssertEqual(notificationTitle.label, title, file: file, line: line)
+XCTAssertEqual(notificationBody.label, body, file: file, line: line)

Note: Only apply if SpringBoard notification banner exposes distinct elements for title and body.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e6ed5e0 and 0be1a4e.

📒 Files selected for processing (1)
  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
🧰 Additional context used
📓 Path-based instructions (1)
**/*.swift

📄 CodeRabbit inference engine (AGENTS.md)

**/*.swift: Write Swift code compatible with iOS deployment targets specified in Package.swift and podspec files; do not lower deployment targets without approval
Use SwiftLint with strict mode and respect .swiftlint.yml rules; justify and scope any exceptions rather than suppressing rules broadly
Run SwiftFormat for code formatting and respect repository-specific style conventions
Never commit API keys or customer data; use obvious placeholders (e.g., YOUR_STREAM_KEY) in example code
Follow the project's zero-warnings policy: fix new warnings and avoid introducing any

Files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Tests/**/*.swift : Add or extend tests in the matching module's Tests/ folder for changes to StreamChat or StreamChatUI code, covering core models, API surfaces, and view controller behaviors; use fakes/mocks from test helpers provided by the repo
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Maintain high test coverage when changing code in the Stream iOS Chat SDK
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChat/**/*.swift : Ensure public API changes in StreamChat and StreamChatUI include inline documentation and migration notes
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChatUI/**/*.swift : Ensure public API changes in StreamChatUI include inline documentation and migration notes
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Tests/**/*.swift : Add or extend tests in the matching module's Tests/ folder for changes to StreamChat or StreamChatUI code, covering core models, API surfaces, and view controller behaviors; use fakes/mocks from test helpers provided by the repo

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChatUI/**/*.swift : Ensure public API changes in StreamChatUI include inline documentation and migration notes

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to Sources/StreamChat/**/*.swift : Ensure public API changes in StreamChat and StreamChatUI include inline documentation and migration notes

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Maintain high test coverage when changing code in the Stream iOS Chat SDK

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Applies to CHANGELOG* : Update CHANGELOG entries for user-visible SDK changes in StreamChat and StreamChatUI

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: For UI changes in StreamChatUI, attach comparison screenshots (before/after) in pull requests where feasible

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Prioritize backwards compatibility and API stability when changing code in the Stream iOS Chat SDK

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Prefer additive changes and avoid breaking public APIs in StreamChat and StreamChatUI

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
📚 Learning: 2025-11-25T12:17:18.459Z
Learnt from: CR
Repo: GetStream/stream-chat-swift PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T12:17:18.459Z
Learning: Mirror existing patterns in the nearest module when making changes to StreamChat or StreamChatUI

Applied to files:

  • StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift
🔇 Additional comments (4)
StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift (4)

38-56: LGTM! New assertion follows existing patterns.

The new assertChannelPreviewIsEmpty method correctly verifies that the channel preview displays "No messages".


712-718: LGTM! Parameterization improves test flexibility.

Adding the userName parameter removes hardcoding and makes the assertion reusable across different test scenarios.


1053-1064: Hardcoded error message in test assertion lacks verification source.

Line 1060 contains a hardcoded error message string used for test assertion. The exact message definition in the app code could not be located to verify whether this test string matches the actual implementation.

Consider:

  1. Locating where the app defines this error message and verifying the test string matches exactly
  2. If the message comes from a backend response or mock server, document that dependency
  3. If maintaining this message in multiple places, consider extracting to a shared constant or documenting the expected format

96-96: LGTM! Delivery status type migration is consistent.

The migration from the old delivery status type to StreamChatTestMockServer.MessageDeliveryStatus? is applied consistently across all relevant methods. The type is properly defined as a public enum in StreamChatTestMockServer and re-exported via @_exported import in the test setup, making it available across all test files.

Also applies to: 553-553, 569-569, 881-881

Comment on lines +347 to +359

@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
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find the messageCell(withIndex:) method to understand its bounds checking
rg -n "func messageCell\(" StreamChatUITestsAppUITests/Robots/ -A 10 --type swift

Repository: GetStream/stream-chat-swift

Length of output: 1153


🏁 Script executed:

# Check for cells.count - 1 patterns in the codebase
rg -n 'cells\.count\s*-\s*1' StreamChatUITestsAppUITests/ --type swift -C 2

Repository: GetStream/stream-chat-swift

Length of output: 642


🏁 Script executed:

# Find the UserRobot+Asserts.swift file and check context around line 354
wc -l StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift

Repository: GetStream/stream-chat-swift

Length of output: 134


🏁 Script executed:

# Get the full context of the assertFirstMessageIsVisible method and surrounding code
sed -n '340,365p' StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift

Repository: GetStream/stream-chat-swift

Length of output: 1031


🏁 Script executed:

# Search for other assertions about first message to see if there are guards elsewhere
rg -n "assertFirstMessage|assertLastMessage|cells\.count" StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift --type swift | head -30

Repository: GetStream/stream-chat-swift

Length of output: 782


🏁 Script executed:

# Get the complete messageCell implementation to see how it accesses cells
sed -n '190,220p' StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift

Repository: GetStream/stream-chat-swift

Length of output: 1080


🏁 Script executed:

# Check if there are accessor implementations or actual cell access logic
rg -n "cells\[" StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift --type swift -B 2 -A 2

Repository: GetStream/stream-chat-swift

Length of output: 53


🏁 Script executed:

# Check test usage patterns - look for assertFirstMessageIsVisible calls
rg -n "assertFirstMessageIsVisible" StreamChatUITestsAppUITests/ --type swift -B 3 -A 3

Repository: GetStream/stream-chat-swift

Length of output: 2636


Add a guard check to prevent accessing negative cell indices.

Line 354 passes cells.count - 1 to messageCell(withIndex:) without validating that cells exist. When cells.count == 0, this results in index -1 being passed to cells.element(boundBy: -1), which causes unexpected behavior. Add an explicit guard to fail fast with a clear error message.

🛡️ Defensive check
 @discardableResult
 func assertFirstMessageIsVisible(
     _ text: String,
     file: StaticString = #filePath,
     line: UInt = #line
 ) -> Self {
+    guard cells.count > 0 else {
+        XCTFail("No message cells available", file: file, line: line)
+        return 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
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@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 assertFirstMessageIsVisible(
_ text: String,
file: StaticString = #filePath,
line: UInt = #line
) -> Self {
guard cells.count > 0 else {
XCTFail("No message cells available", file: file, line: line)
return 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
}
🤖 Prompt for AI Agents
In @StreamChatUITestsAppUITests/Robots/UserRobot+Asserts.swift around lines 347
- 359, In assertFirstMessageIsVisible, guard that cells.count > 0 before
computing let messageCell = messageCell(withIndex: cells.count - 1); if count is
0 call XCTFail with a clear message (include file and line) and return self to
fail fast; this prevents passing -1 into messageCell(withIndex:) and avoids
undefined behavior when accessing cells.element(boundBy:).

@testableapple testableapple changed the title [CI] Implement new cross-platform mock server (#3895) [CI] Implement new cross-platform mock server Jan 9, 2026
@github-actions
Copy link

github-actions bot commented Jan 9, 2026

1 Warning
⚠️ Big PR

Generated by 🚫 Danger

@Stream-SDK-Bot
Copy link
Collaborator

SDK Size

title develop branch diff status
StreamChat 8.63 MB 8.63 MB 0 KB 🟢
StreamChatUI 4.91 MB 4.91 MB 0 KB 🟢

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 9, 2026

@testableapple testableapple merged commit 8ba546b into develop Jan 9, 2026
14 checks passed
@testableapple testableapple deleted the ci/cross-platform-mock-server branch January 9, 2026 17:57
@coderabbitai coderabbitai bot mentioned this pull request Jan 14, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 CI/CD Any work related to CI/CD

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants