Skip to content

Conversation

@maratal
Copy link
Collaborator

@maratal maratal commented Jan 26, 2025

Closes #86 #88

Summary by CodeRabbit

  • New Features

    • Enhanced chat and reaction event handling with robust error detection and management, ensuring a smoother messaging experience.
  • Bug Fixes

    • Prevented propagation of irregular or malformed messages, resulting in more consistent and reliable chat displays.
  • Refactor

    • Streamlined asynchronous state management and API interactions for improved performance and responsiveness in real-time chat.

@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2025

Walkthrough

This pull request implements improved error handling and debugging support throughout the chat API. The production code now uses conditional compilation blocks in debug mode to manage malformed message events, including new properties and methods in both the messages and reactions modules. Test files have been reorganized—some tests have been removed, renamed, or enhanced to verify new behaviors. Updates to helper and mock classes introduce optional parameters and asynchronous state callbacks, altering the way state transitions and pagination responses are handled.

Changes

File(s) Change Summary
Sources/.../DefaultMessages.swift, Sources/.../DefaultRoomReactions.swift Added debug-only conditional blocks. In DefaultMessages, an extension marks ARTMessage as @retroactive @unchecked Sendable and introduces a do‑catch based error handling mechanism with new properties/methods for malformed message events; DefaultRoomReactions now supports subscribing and emitting malformed message events. Additional methods for updating and deleting messages were also added.
Tests/.../ChatAPITests.swift Removed a test function (getMessages_whenGetMessagesReturnsServerError_throwsARTError) that validated error handling when server errors occur during message retrieval.
Tests/.../DefaultMessagesTests.swift, Tests/.../DefaultRoomReactionsTests.swift Overhauled test suite: renamed test methods, added tests for validating message sending via REST, omitted metadata/headers in payload when unspecified, verified subscription point logic under various channel states, and added tests for handling malformed realtime events.
Tests/.../Helpers/Helpers.swift Updated createContributor to include an optional underlyingChannel parameter and added a static method waitForManager that asynchronously waits for a manager to handle state changes.
Tests/.../Mocks/MockHTTPPaginatedResponse.swift, Tests/.../Mocks/MockRealtimeChannel.swift, Tests/.../Mocks/MockRoomLifecycleContributorChannel.swift Removed the _isLast property and updated pagination logic in MockHTTPPaginatedResponse; enhanced MockRealtimeChannel with a new optional state property, state callbacks, and asynchronous attach/detach behavior; updated MockRoomLifecycleContributorChannel to include an optional underlying channel and invoke its state change callbacks.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant DM as DefaultMessages
    participant Subscriber as MalformedMessageSubscription
    Client->>DM: Receive incoming message
    DM->>DM: Process message in do-catch block
    alt Error Occurred (DEBUG)
        DM->>Subscriber: Emit malformed message event
    else Normal
        DM->>Client: Process message normally
    end
Loading
sequenceDiagram
    participant Test as Test
    participant MRC as MockRealtimeChannel
    participant Callback as State Callback
    Test->>MRC: Call attach()
    MRC->>MRC: Update channel state (optional _state set)
    MRC-->>Callback: Invoke attach callbacks asynchronously
Loading

Possibly related PRs

Poem

I'm a little rabbit, hopping with delight,
Debugging in code, through day and night.
Errors caught in do-catch, messages clear as dew,
Malformed events handled with flair and a few.
In fields of logic and tests, I dance and play—
Cheers to our changes, hip-hip-hooray! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@maratal maratal changed the base branch from main to 140-typing-tests January 26, 2025 23:09
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 15, 2025 22:41 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 16, 2025 23:53 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 17, 2025 00:05 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 21, 2025 15:59 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 21, 2025 23:14 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 22, 2025 16:37 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 23, 2025 15:30 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 23, 2025 15:38 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 23, 2025 15:54 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 23, 2025 17:09 Inactive
@maratal
Copy link
Collaborator Author

maratal commented Feb 23, 2025

I'm seeing failures for message tests whenChannelReentersATTACHEDWithResumedFalseThenSubscriptionPointResetsToAttachSerial and whenChannelUPDATEReceivedWithResumedFalseThenSubscriptionPointResetsToAttachSerial which are testing the same code that previously was reported here

Locally failure rate is 14-18% and on CI it feels like more than 50%. The chain of events leading to failures looks the same as for when those tests passed:

Screenshot 2025-02-23 at 17 57 31

So before diving into it I would like to assure that the code under these tests is valid, and I'm marking this PR as ready for review now to start a discussion. FYI @umair-ably @lawrence-forooghian

UPD: Those fail 90% of the time if executed together with the other tests.

UPD: Tests don't fail if parallel execution is turned off (it's still very fast - the duration of the longest (integration) test takes almost all time). I've tried to edit BuildTool, but it doesn't take my parameter. Couldn't you please look into it? @lawrence-forooghian FYI @umair-ably

@maratal maratal marked this pull request as ready for review February 23, 2025 17:44
@maratal maratal requested a review from umair-ably February 23, 2025 17:44
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: 0

🧹 Nitpick comments (7)
Tests/AblyChatTests/DefaultMessagesTests.swift (2)

6-24: Validate returned message fields in “clientMaySendMessageViaRESTChatAPI”.

Testing only the text field might not catch potential serialization errors in other fields (e.g., serial or metadata). Consider asserting on additional returned attributes (if relevant) to strengthen this test.


322-356: Additional field checks recommended in “subscriptionCanBeRegisteredToReceiveIncomingMessages”.

Currently, only headers and metadata are checked. For completeness, consider verifying text or other fields.

Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (3)

122-133: Asynchronous attach callbacks.
Using @MainActor is acceptable in tests. If the code might be used outside tests, consider more robust error handling instead of fatalError.


135-146: Asynchronous detach callbacks.
Similar to attach callbacks, consider capturing errors more gracefully rather than calling fatalError.


161-163: Spawned Task inside attach.
Consider using structured concurrency (e.g., making attach an async function) rather than spawning a top-level Task if attach is repeatedly called. This prevents concurrency mishaps.

Tests/AblyChatTests/Mocks/MockRoomLifecycleContributorChannel.swift (1)

131-133: Asynchronous state change emission.
Creating a new Task from within the actor is safe, but you might prefer using await in the actor scope. Ensure you truly need concurrency here.

Tests/AblyChatTests/DefaultRoomPresenceTests.swift (1)

84-84: Consider a more robust solution for timing-sensitive tests.

The current changes double the delay to prevent race conditions where attach failure happens before the lifecycle manager starts waiting. While this fix works, it makes the tests more brittle as they depend on specific timing values.

Consider implementing a more robust solution:

  1. Extract the delay multiplier to a constant to make it easier to adjust if needed.
  2. Investigate if SignallableChannelOperation can be made to work with the test framework's async/await syntax.
  3. Consider implementing a custom expectation mechanism that doesn't rely on timing.
+ // At the top of the file
+ private enum Constants {
+     static let attachFailureDelayMultiplier = 2
+ }

- let contributor = RoomLifecycleHelper.createContributor(feature: .presence, attachBehavior: .completeAndChangeState(.failure(attachError), newState: .failed, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay * 2))
+ let contributor = RoomLifecycleHelper.createContributor(feature: .presence, attachBehavior: .completeAndChangeState(.failure(attachError), newState: .failed, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay * Constants.attachFailureDelayMultiplier))

Also applies to: 204-204, 382-382

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e11ea0 and ea960b3.

📒 Files selected for processing (9)
  • Sources/AblyChat/DefaultMessages.swift (4 hunks)
  • Tests/AblyChatTests/ChatAPITests.swift (0 hunks)
  • Tests/AblyChatTests/DefaultMessagesTests.swift (4 hunks)
  • Tests/AblyChatTests/DefaultRoomPresenceTests.swift (3 hunks)
  • Tests/AblyChatTests/DefaultRoomTypingTests.swift (2 hunks)
  • Tests/AblyChatTests/Helpers/Helpers.swift (2 hunks)
  • Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift (4 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (9 hunks)
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleContributorChannel.swift (5 hunks)
💤 Files with no reviewable changes (1)
  • Tests/AblyChatTests/ChatAPITests.swift
✅ Files skipped from review due to trivial changes (1)
  • Tests/AblyChatTests/DefaultRoomTypingTests.swift
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Xcode, tvOS (Xcode 16)
🔇 Additional comments (44)
Tests/AblyChatTests/DefaultMessagesTests.swift (11)

28-47: Looks good.

The test thoroughly verifies the absence of headers and metadata when not specified. This coverage is sufficient.


51-64: All set.

The test properly checks for thrown errors, aligning well with the code’s intended behavior.


68-86: Proper subscription point for attached channel.

This test accurately ensures fromSerial is set to the channel serial once attached. No issues found.


90-118: Correct subscription point for non-attached channel.

The test properly waits for attach and verifies attachSerial usage. Implementation seems correct.


122-162: Reattached channel behavior validated.

This covers the resumed=false scenario thoroughly, resetting subscription points to attachSerial.


166-213: UPDATE event handling appears correct.

Resets subscription point to attachSerial on resumed=false. This behavior aligns with the specification.


220-251: Adequate coverage for history query options.

The forced “backwards” direction is confirmed, and fromSerial is verified. Test coverage looks complete.


255-272: Error handling on previous messages is validated.

Throws the expected server error, matching the specification.


277-296: History query options for getMessages are verified.

Pagination checks are correct and consistent with your mock responses.


300-315: Server error path verified for getMessages.

The test ensures correct error throwing, aligning with the expected behavior.


361-383: Handling of malformed realtime events.

Test confirms such events are not emitted to the normal subscription and instead go to the malformed subscription. No issues found.

Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift (6)

13-14: Constructor parameter update is consistent.

Removing an explicit isLast parameter in favor of hasNext simplifies the logic. This remains clear and maintainable.


43-43: Inverted isLast logic is correct.

override var isLast: Bool { !_hasNext } properly mirrors the hasNext flag.


117-118: Pagination setup for partial results.

hasNext: true helps test multi-page scenarios. This aligns with typical paginated flows.


128-131: Next page items updated.

No issues in modifying these fields for test data.


139-142: Corrected message content.

Updating serial and text for next-page items is straightforward; logic is unchanged.


148-149: hasNext: false finalizes pagination.

Ensures the next page is accurately flagged as the last page.

Sources/AblyChat/DefaultMessages.swift (4)

9-11: Marking ARTMessage as @unchecked Sendable.

This approach is valid for debugging and concurrency, but be wary that forced sendability can hide potential thread-safety concerns.


61-117: Potentially unhandled error throw in subscription callback.

Rethrowing inside Task might not surface in normal channel usage. Confirm whether these thrown errors need additional handling or logging beyond the debug log.


135-145: Debug-only malformed messages subscription.

Implementation correctly isolates malformed message events under #if DEBUG. This seems safe.


192-193: Update event listener added.

Aligns with spec for resetting the subscription point on update with resumed=false.

Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (14)

9-9: Storing the optional channel state.
No issues found. This is a valid approach to store an optional channel state for test usage.


20-20: Introducing a typealias for clarity.
This typealias improves readability by giving a clear name to the callback type.


22-23: Nonisolated(unsafe) usage might allow unsynchronized access.
Consider clarifying concurrency usage or adding synchronization if multiple tasks modify these callbacks concurrently.


37-37: Additional initializer parameter.
Including an optional state parameter is useful. Ensure references to state are consistent throughout the codebase.


41-41: Adding messageJSONToEmitOnSubscribe.
This new parameter seamlessly supports JSON-based message emission. Looks good.


45-45: Assignment of _state.
Straightforward assignment. No issues found.


49-49: Assignment of messageJSONToEmitOnSubscribe.
No immediate concerns. A clear way to store JSON messages for subscription.


83-87: Possible oversimplification in fallback state.
When _state is nil, the code sets the channel to .attached only if attachResult is .success; otherwise .failed. Consider whether intermediate states like .detaching or .suspended might be missed.


116-120: Performing state change callbacks.
Invoking both stateCallback and stateCallbacks[...] for the same event is fine, but ensure it doesn't cause duplicated effects if both are set.


152-152: Overloaded attach function.
Attaching without a callback is convenient. Ensure any side effects or concurrency issues are addressed when calling attach().


190-190: New JSON parameter.
Duplicated from the initializer; referencing it here as well ensures clarity.


206-221: Parsing JSON-based message data.
This approach is valid. Confirm that defaulting action to .create on unknown raw values won't mask potential issues.


250-253: on(event:) callback registration.
Storing callbacks by event is a standard approach. Looks good.


255-257: on() callback registration.
Similarly, registering a single callback is concise.

Tests/AblyChatTests/Helpers/Helpers.swift (3)

67-67: Added underlyingChannel parameter.
Useful for injecting a mock realtime channel for specialized testing. Nicely done.


75-75: Initializer assignment.
Assigning the underlyingChannel to the newly introduced property. Straightforward.


85-92: New waitForManager method.
This async helper method is a neat solution for ensuring manager side effects are processed before continuing. Be mindful of potential race conditions if multiple state changes happen concurrently.

Tests/AblyChatTests/Mocks/MockRoomLifecycleContributorChannel.swift (5)

8-8: New optional underlyingChannel property.
Allows bridging the contributor channel with an actual mock realtime channel. Good for advanced testing scenarios.


18-18: New constructor parameter underlyingChannel.
Ensures that we can inject a mock channel when needed.


25-25: Assigning self.underlyingChannel.
Straightforward assignment. No issues found.


75-75: Calling attach on underlyingChannel.
Verify that the underlying channel’s attach logic is thread-safe, especially since this method is an async function in an actor.


86-86: Calling detach on underlyingChannel.
Same concurrency considerations as attach. Confirm no data races or re-entrancy issues.

Tests/AblyChatTests/DefaultRoomPresenceTests.swift (1)

1-487: Comprehensive test coverage with well-structured test cases.

The test suite thoroughly covers presence-related functionality including basic operations, error handling, state transitions, event subscriptions, and discontinuity handling. Each test is well-documented with spec references.

@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 23, 2025 22:27 Inactive
@maratal maratal changed the title Messages tests Messages and room reactions tests Feb 23, 2025
@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat February 23, 2025 22:30 Inactive
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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently, please check "Code review limits" under "Moderation" settings.

Actionable comments posted: 3

🔭 Outside diff range comments (1)
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (1)

199-227: ⚠️ Potential issue

Fix potential crash in timestamp parsing.

The timestamp parsing uses force unwrap which could crash if the timestamp string is invalid.

Apply this diff to safely handle timestamp parsing:

-            if let ts = json["timestamp"] as? String {
-                message.timestamp = Date(timeIntervalSince1970: TimeInterval(ts)!)
+            if let ts = json["timestamp"] as? String,
+               let interval = TimeInterval(ts) {
+                message.timestamp = Date(timeIntervalSince1970: interval)
             }
🧹 Nitpick comments (4)
Tests/AblyChatTests/DefaultRoomReactionsTests.swift (1)

51-66: Add trailing commas in multi-line collections.

Add trailing commas after the last item in multi-line collections to maintain consistency with Swift style guidelines.

Apply this diff to fix the trailing commas:

                 "metadata": [
-                    "foo": "bar"
+                    "foo": "bar",
                 ],
                 "extras": [
                     "headers": [
-                        "baz": "qux"
+                        "baz": "qux",
                     ]
                 ]
🧰 Tools
🪛 SwiftLint (0.57.0)

[Error] 58-58: Multi-line collection literals should have trailing commas

(trailing_comma)


[Error] 64-64: Multi-line collection literals should have trailing commas

(trailing_comma)


[Error] 65-65: Multi-line collection literals should have trailing commas

(trailing_comma)

Sources/AblyChat/DefaultRoomReactions.swift (1)

105-105: Remove trailing whitespace.

Remove the trailing whitespace to maintain code cleanliness.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Error] 105-105: Lines should not have trailing whitespace

(trailing_whitespace)

Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (1)

135-146: Fix error message in detach callback handling.

The error message incorrectly references "attachResult" instead of "detachResult".

Apply this diff to fix the error message:

         guard let detachResult else {
-            fatalError("attachResult must be set before attach is called")
+            fatalError("detachResult must be set before detach is called")
         }
Tests/AblyChatTests/DefaultMessagesTests.swift (1)

5-404: Consider general improvements to test suite reliability.

To prevent similar issues in other test cases, consider implementing these improvements across the test suite:

  1. Create a base test class or helper methods to standardize state verification.
  2. Replace fixed delays with proper synchronization mechanisms.
  3. Add timeout handling for all async operations.
  4. Add retry mechanisms for flaky assertions.

Example helper methods to consider adding:

extension DefaultMessagesTests {
    func waitForState(_ state: ARTRealtimeChannelState, timeout: TimeInterval = 5.0) async throws {
        let expectation = AsyncExpectation()
        // Add state change listener and fulfill expectation
        try await expectation.wait(timeout: .seconds(timeout))
    }
    
    func retryAssertion(
        timeout: TimeInterval = 5.0,
        interval: TimeInterval = 0.1,
        assertion: () async throws -> Void
    ) async throws {
        let deadline = Date().addingTimeInterval(timeout)
        var lastError: Error?
        
        while Date() < deadline {
            do {
                try await assertion()
                return
            } catch {
                lastError = error
                try await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000))
            }
        }
        
        throw lastError ?? NSError(domain: "Assertion failed", code: -1)
    }
}
🛑 Comments failed to post (3)
Tests/AblyChatTests/DefaultMessagesTests.swift (2)

166-213: ⚠️ Potential issue

Improve UPDATE event handling and state verification.

The test is failing intermittently due to timing issues and lack of proper state verification. Consider these improvements:

  1. Add explicit state verification after the UPDATE event.
  2. Remove fixed delays and use proper synchronization.
  3. Add verification that the UPDATE event was actually processed.
 @Test
 func whenChannelUPDATEReceivedWithResumedFalseThenSubscriptionPointResetsToAttachSerial() async throws {
     // Given
     let attachSerial = "attach123"
     let channelSerial = "channel456"
     let channel = MockRealtimeChannel(properties: ARTChannelProperties(attachSerial: attachSerial, channelSerial: channelSerial), state: .attached, attachResult: .success, detachResult: .success)
-    let contributor = RoomLifecycleHelper.createContributor(feature: .messages, underlyingChannel: channel, attachBehavior: .completeAndChangeState(.success, newState: .attached, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay * 2), detachBehavior: .completeAndChangeState(.success, newState: .detached, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay * 2))
+    let contributor = RoomLifecycleHelper.createContributor(feature: .messages, underlyingChannel: channel, attachBehavior: .completeAndChangeState(.success, newState: .attached), detachBehavior: .completeAndChangeState(.success, newState: .detached))
     let lifecycleManager = await RoomLifecycleManager.createManager(contributors: [contributor])
     
     // ... setup code ...
     
     // Wait for room to become ATTACHED
     try await lifecycleManager.performAttachOperation(testsOnly_forcingOperationID: UUID())
+    // Verify initial state
+    let roomStatusSubscription = await lifecycleManager.onRoomStatusChange(bufferingPolicy: .unbounded)
+    _ = try #require(await roomStatusSubscription.attachedElements().first { _ in true })
     
     // ... test code ...
     
     // When: This contributor emits an UPDATE event with `resumed` flag set to false
+    let updateEventProcessed = AsyncExpectation()
     let contributorStateChange = ARTChannelStateChange(
         current: .attached,
         previous: .attached,
         event: .update,
         reason: ARTErrorInfo(domain: "SomeDomain", code: 123),
         resumed: false
     )
     
     await RoomLifecycleHelper.waitForManager(lifecycleManager, toHandleContributorStateChange: contributorStateChange) {
         await contributor.channel.emitStateChange(contributorStateChange)
+        await updateEventProcessed.fulfill()
     }
+    // Verify UPDATE event was processed
+    try await updateEventProcessed.wait(timeout: .seconds(5))
     
     // ... assertions ...
 }
📝 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.

func whenChannelUPDATEReceivedWithResumedFalseThenSubscriptionPointResetsToAttachSerial() async throws {
    // Given
    let attachSerial = "attach123"
    let channelSerial = "channel456"
    let channel = MockRealtimeChannel(
        properties: ARTChannelProperties(attachSerial: attachSerial, channelSerial: channelSerial),
        state: .attached,
        attachResult: .success,
        detachResult: .success
    )
    let contributor = RoomLifecycleHelper.createContributor(
        feature: .messages,
        underlyingChannel: channel,
        attachBehavior: .completeAndChangeState(.success, newState: .attached),
        detachBehavior: .completeAndChangeState(.success, newState: .detached)
    )
    let lifecycleManager = await RoomLifecycleManager.createManager(contributors: [contributor])
    
    let realtime = MockRealtime.create {
        (MockHTTPPaginatedResponse.successSendMessageWithNoItems, nil)
    }
    let chatAPI = ChatAPI(realtime: realtime)
    let featureChannel = DefaultFeatureChannel(channel: channel, contributor: contributor, roomLifecycleManager: lifecycleManager)
    let defaultMessages = await DefaultMessages(featureChannel: featureChannel, chatAPI: chatAPI, roomID: "basketball", clientID: "clientId", logger: TestLogger())
    
    // Wait for room to become ATTACHED
    try await lifecycleManager.performAttachOperation(testsOnly_forcingOperationID: UUID())
    // Verify initial state
    let roomStatusSubscription = await lifecycleManager.onRoomStatusChange(bufferingPolicy: .unbounded)
    _ = try #require(await roomStatusSubscription.attachedElements().first { _ in true })
    
    // When: subscription is added
    let subscription = try await defaultMessages.subscribe()
    
    // When: history get is called
    _ = try await subscription.getPreviousMessages(params: .init())
    
    // Then: subscription point is the channelSerial
    let requestParams1 = try #require(realtime.requestArguments.first?.params)
    #expect(requestParams1["fromSerial"] == channelSerial)
    
    // When: This contributor emits an UPDATE event with `resumed` flag set to false
    let updateEventProcessed = AsyncExpectation()
    let contributorStateChange = ARTChannelStateChange(
        current: .attached, // arbitrary
        previous: .attached, // arbitrary
        event: .update,
        reason: ARTErrorInfo(domain: "SomeDomain", code: 123), // arbitrary
        resumed: false
    )
    
    await RoomLifecycleHelper.waitForManager(lifecycleManager, toHandleContributorStateChange: contributorStateChange) {
        await contributor.channel.emitStateChange(contributorStateChange)
        await updateEventProcessed.fulfill()
    }
    // Verify UPDATE event was processed
    try await updateEventProcessed.wait(timeout: .seconds(5))
    
    // When: history get is called
    _ = try await subscription.getPreviousMessages(params: .init())
    
    // Then: The subscription point of any subscribers must be reset to the attachSerial
    let requestParams2 = try #require(realtime.requestArguments.last?.params)
    #expect(requestParams2["fromSerial"] == attachSerial)
}

122-162: ⚠️ Potential issue

Address potential race conditions in the test.

The test is failing intermittently due to timing-sensitive operations. Consider these improvements to make the test more reliable:

  1. Add explicit state verification after each lifecycle operation.
  2. Use dynamic delays or proper synchronization mechanisms instead of fixed delays.
  3. Add retry mechanism for assertions that might be affected by timing.
 @Test
 func whenChannelReentersATTACHEDWithResumedFalseThenSubscriptionPointResetsToAttachSerial() async throws {
     // Given
     let attachSerial = "attach123"
     let channelSerial = "channel456"
     let channel = MockRealtimeChannel(properties: ARTChannelProperties(attachSerial: attachSerial, channelSerial: channelSerial), state: .attached, attachResult: .success, detachResult: .success)
-    let contributor = RoomLifecycleHelper.createContributor(feature: .messages, underlyingChannel: channel, attachBehavior: .completeAndChangeState(.success, newState: .attached, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay * 2), detachBehavior: .completeAndChangeState(.success, newState: .detached, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay * 2))
+    let contributor = RoomLifecycleHelper.createContributor(feature: .messages, underlyingChannel: channel, attachBehavior: .completeAndChangeState(.success, newState: .attached), detachBehavior: .completeAndChangeState(.success, newState: .detached))
     let lifecycleManager = await RoomLifecycleHelper.createManager(contributors: [contributor])
     
     // ... setup code ...
     
     // Wait for room to become ATTACHED
     try await lifecycleManager.performAttachOperation(testsOnly_forcingOperationID: UUID())
+    // Verify channel state
+    let roomStatusSubscription = await lifecycleManager.onRoomStatusChange(bufferingPolicy: .unbounded)
+    _ = try #require(await roomStatusSubscription.attachedElements().first { _ in true })
     
     // ... test code ...
     
     // Wait for room to become DETACHED
     try await lifecycleManager.performDetachOperation(testsOnly_forcingOperationID: UUID())
+    // Verify channel state
+    _ = try #require(await roomStatusSubscription.detachedElements().first { _ in true })
     
     // And then to become ATTACHED again
     try await lifecycleManager.performAttachOperation(testsOnly_forcingOperationID: UUID())
+    // Verify channel state
+    _ = try #require(await roomStatusSubscription.attachedElements().first { _ in true })
     
     // ... assertions ...
 }
📝 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.

    func whenChannelReentersATTACHEDWithResumedFalseThenSubscriptionPointResetsToAttachSerial() async throws {
        // Given
        let attachSerial = "attach123"
        let channelSerial = "channel456"
        let channel = MockRealtimeChannel(
            properties: ARTChannelProperties(attachSerial: attachSerial, channelSerial: channelSerial),
            state: .attached,
            attachResult: .success,
            detachResult: .success
        )
-    let contributor = RoomLifecycleHelper.createContributor(feature: .messages, underlyingChannel: channel, attachBehavior: .completeAndChangeState(.success, newState: .attached, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay * 2), detachBehavior: .completeAndChangeState(.success, newState: .detached, delayInMilliseconds: RoomLifecycleHelper.fakeNetworkDelay * 2))
+    let contributor = RoomLifecycleHelper.createContributor(feature: .messages, underlyingChannel: channel, attachBehavior: .completeAndChangeState(.success, newState: .attached), detachBehavior: .completeAndChangeState(.success, newState: .detached))
        let lifecycleManager = await RoomLifecycleHelper.createManager(contributors: [contributor])

        let realtime = MockRealtime.create {
            (MockHTTPPaginatedResponse.successSendMessageWithNoItems, nil)
        }
        let chatAPI = ChatAPI(realtime: realtime)
        let featureChannel = DefaultFeatureChannel(channel: channel, contributor: contributor, roomLifecycleManager: lifecycleManager)
        let defaultMessages = await DefaultMessages(featureChannel: featureChannel, chatAPI: chatAPI, roomID: "basketball", clientID: "clientId", logger: TestLogger())

        // Wait for room to become ATTACHED
        try await lifecycleManager.performAttachOperation(testsOnly_forcingOperationID: UUID())
+    // Verify channel state after attach
+    let roomStatusSubscription = await lifecycleManager.onRoomStatusChange(bufferingPolicy: .unbounded)
+    _ = try #require(await roomStatusSubscription.attachedElements().first { _ in true })

        // When: subscription is added
        let subscription = try await defaultMessages.subscribe()

        // When: history get is called
        _ = try await subscription.getPreviousMessages(params: .init())

        // Then: subscription point is the channelSerial
        let requestParams1 = try #require(realtime.requestArguments.first?.params)
        #expect(requestParams1["fromSerial"] == channelSerial)

        // Wait for room to become DETACHED
        try await lifecycleManager.performDetachOperation(testsOnly_forcingOperationID: UUID())
+    // Verify channel state after detach
+    _ = try #require(await roomStatusSubscription.detachedElements().first { _ in true })

        // And then to become ATTACHED again
        try await lifecycleManager.performAttachOperation(testsOnly_forcingOperationID: UUID())
+    // Verify channel state after re-attach
+    _ = try #require(await roomStatusSubscription.attachedElements().first { _ in true })

        // When: history get is called
        _ = try await subscription.getPreviousMessages(params: .init())

        // Then: The subscription point of any subscribers must be reset to the attachSerial
        let requestParams2 = try #require(realtime.requestArguments.last?.params)
        #expect(requestParams2["fromSerial"] == attachSerial)
    }
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (1)

200-217: ⚠️ Potential issue

Handle timestamp conversion safely.

The force unwrapping of the timestamp conversion could lead to runtime crashes if the timestamp string is invalid.

Apply this diff to handle the timestamp conversion safely:

-                message.timestamp = Date(timeIntervalSince1970: TimeInterval(ts)!)
+                if let timeInterval = TimeInterval(ts) {
+                    message.timestamp = Date(timeIntervalSince1970: timeInterval)
+                }
📝 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.

        if let json = messageJSONToEmitOnSubscribe {
            let message = ARTMessage(name: nil, data: json["data"] ?? "")
            if let action = json["action"] as? UInt {
                message.action = ARTMessageAction(rawValue: action) ?? .create
            }
            if let serial = json["serial"] as? String {
                message.serial = serial
            }
            if let clientId = json["clientId"] as? String {
                message.clientId = clientId
            }
            if let extras = json["extras"] as? ARTJsonCompatible {
                message.extras = extras
            }
            if let ts = json["timestamp"] as? String {
-                message.timestamp = Date(timeIntervalSince1970: TimeInterval(ts)!)
+                if let timeInterval = TimeInterval(ts) {
+                    message.timestamp = Date(timeIntervalSince1970: timeInterval)
+                }
            }
            callback(message)

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: 0

🧹 Nitpick comments (1)
Tests/AblyChatTests/DefaultRoomReactionsTests.swift (1)

79-80: Track untestable specification.

The comment indicates that specification CHA-ER4c is currently untestable. This should be tracked to ensure it gets addressed in the future.

Would you like me to create an issue to track the untestable specification CHA-ER4c?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2188cc and 3fafdf5.

📒 Files selected for processing (2)
  • Sources/AblyChat/DefaultRoomReactions.swift (2 hunks)
  • Tests/AblyChatTests/DefaultRoomReactionsTests.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/AblyChat/DefaultRoomReactions.swift
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Xcode, tvOS (Xcode 16)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 16)
  • GitHub Check: Xcode, iOS (Xcode 16)
  • GitHub Check: Xcode, macOS (Xcode 16)
🔇 Additional comments (2)
Tests/AblyChatTests/DefaultRoomReactionsTests.swift (2)

46-77: LGTM! Test improvements enhance clarity and validation.

The renamed test method and enhanced validation logic provide better clarity and more thorough testing. The detailed JSON structure for simulating room reaction messages is well-structured and comprehensive.


81-96: LGTM! Well-structured test for malformed message handling.

The test effectively validates that malformed messages are properly handled and don't propagate to regular subscribers. The test structure follows the AAA pattern and provides clear validation.

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: 4

🧹 Nitpick comments (25)
CHANGELOG.md (1)

3-19: LGTM, but consider addressing formatting inconsistencies

The changelog updates correctly document the new features (updating and deleting messages) and breaking changes (renaming ClientOptions to ChatClientOptions).

Consider addressing the following formatting issues:

  • Line 14: Heading level should increment by one (h3 instead of h4)
  • Lines 16, 18, 24, 26: Bare URLs should be formatted as proper markdown links
  • Line 22: Duplicate heading content (same as line 5)
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

14-14: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


16-16: Bare URL used
null

(MD034, no-bare-urls)


18-18: Bare URL used
null

(MD034, no-bare-urls)

Example/AblyChatExample/MessageViews/PresenceMessageView.swift (1)

25-28: Consider optional unwrapping with nil coalescing for safer code.

The optional unwrapping with force unwrapping (status!) could be replaced with nil coalescing for a more robust implementation.

- let presenceMessage = status != nil ? "\(clientPresenceChangeMessage) with status: \(status!)" : clientPresenceChangeMessage
+ let presenceMessage = status.map { "\(clientPresenceChangeMessage) with status: \($0)" } ?? clientPresenceChangeMessage
Example/AblyChatExample/MessageViews/MessageView.swift (1)

27-45: Consider extracting the confirmation dialog to improve readability

The confirmation dialog logic could be extracted into a separate view or view modifier to make the main view more concise and focused solely on presentation.

Sources/AblyChat/Version.swift (1)

3-9: Update the TODO comment

The TODO comment about copying chat-js implementation is no longer relevant as you've now implemented a custom solution with the dictionary approach.

-// TODO: Just copied chat-js implementation for now to send up agent info. https://github.com/ably-labs/ably-chat-swift/issues/76
+// Agent information to identify this library when communicating with Ably
Tests/AblyChatTests/IntegrationTests.swift (1)

50-50: Consider implications of enabling trace logging.

Introducing ChatClientOptions(logHandler: ChatLogger(label: loggingLabel), logLevel: .trace) might add significant overhead in production environments due to verbose logging. If this is only for debugging, ensure it’s disabled in release builds.

Sources/AblyChat/Messages.swift (2)

85-85: Using an existential for channel is flexible but can have a performance trade-off.

Changing var channel: RealtimeChannelProtocol to var channel: any RealtimeChannelProtocol widens the type, which is useful for dynamic dispatch. Keep in mind this introduces a slight runtime overhead compared to a concrete type. This is likely fine if you need that flexibility.


141-189: New parameter structs enhance clarity of message operations.

The UpdateMessageParams and DeleteMessageParams structures encapsulate operation-specific data, reducing confusion over method signatures. Consider adding test coverage for edge cases (e.g., empty metadata) if not already covered.

Sources/AblyChat/DefaultMessages.swift (1)

319-330: Safely converting ARTMessageOperation to MessageOperation.

This extension enforces a non-nil clientId to avoid hidden crashes. Throwing an error on nil clientId is reasonable for data integrity. Confirm through tests that any edge cases (e.g., partial fields) are handled gracefully.

Example/AblyChatExample/ContentView.swift (7)

19-20: Consider customizing ChatClientOptions for the mock environment.
Providing mock-specific configurations (e.g., logging level, or dummy credentials) can enhance test fidelity.

-            return MockChatClient(
-                realtime: MockRealtime(),
-                clientOptions: ChatClientOptions()
-            )
+            let mockOptions = ChatClientOptions()
+            // e.g., mockOptions.logLevel = .verbose
+            return MockChatClient(
+                realtime: MockRealtime(),
+                clientOptions: mockOptions
+            )

80-87: Gracefully handle whitespace-only inputs.
Currently, the logic switches to a reaction if newMessage is empty. However, whitespace-only inputs also appear empty to users but are not strictly "". You might trim whitespace to provide a consistent experience.

- if newMessage.isEmpty {
+ if newMessage.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
     ...
 }

103-129: Reverse-chronological insertion may confuse users.
New .create messages are inserted at index 0, while older messages are appended. This ensures new content shows up at the top, but you might confirm that this is the desired chat ordering, as many chat apps place new messages at the bottom.


135-149: Throttle typing event triggers.
Calling startTyping() or stopTyping() on every character change may overload the server. A short debounce or throttling interval can reduce excess network traffic.


165-172: Add accessibility label to the cancel-edit button.
Providing a label or text ensures that screen readers can identify this control properly.

 Button("", systemImage: "xmark.circle.fill") {
+    Text("Cancel edit")
     editingItemID = nil
     newMessage = ""
 }

303-311: Confirm that user presence updates are shown in the intended order.
Presence events are prepended to the list. If real-time presence updates should appear chronologically, inserting at position 0 might reverse that.


417-421: Consider error handling for failed typing updates.
If startTyping() or stopTyping() fails, the current code quietly returns. Logging or indicating an error might help diagnose issues with typing signals.

Sources/AblyChat/Dependencies.swift (1)

12-13: Elaborate on concurrency safety.
The protocol inherits Sendable, so document any concurrency guarantees or restrictions for RealtimeClientProtocol.

Sources/AblyChat/ChatAPI.swift (3)

64-78: Use consistent naming for version fields.
You are repurposing response.serial as the version in the new Message. Consider naming consistency across the server and client to reduce confusion.


132-170: Consider using HTTP DELETE method for deletions.
You're using a POST request to an endpoint named .../delete. This is valid if your API mandates it, but using DELETE might be more semantically consistent.


187-189: Add the same error handling comment to updateMessage and deleteMessage.
The comment explicitly mentions “send call”. Making it generic for update/delete calls clarifies that same error path is used for all operations.

- // If an error is returned from the REST API, its ErrorInfo representation shall be thrown as the result of the send call.
+ // If an error is returned from the REST API, its ErrorInfo representation shall be thrown as the result of the send, update, or delete calls.
Sources/AblyChat/Message.swift (1)

85-101: New message version fields
Adding version, timestamp, and operation helps track modifications to a message. Consider clarifying in doc comments how this new timestamp differs from createdAt.

Example/AblyChatExample/Mocks/MockClients.swift (2)

138-145: Sending messages
Setting version and timestamp to current values is fine for mock usage. Consider generating more realistic unique versions if needed.


165-181: Ignoring params in delete
params might carry important details for deletion logic, but it’s not reflected in the resulting Message. Consider whether you need to add or log it in operation for completeness.

Example/AblyChatExample/Mocks/MockRealtime.swift (1)

251-345: Good addition of the RealtimePresence implementation.

The new RealtimePresence class implements the necessary protocol methods, most of which throw "Not implemented" errors as placeholder stubs. This is appropriate for a mock class that's under development.

Note that the history method on line 340 is implemented as a no-op (empty method) rather than throwing an error. Consider documenting why this method is implemented differently than the others or make it consistent with the other methods.

Tests/AblyChatTests/DefaultMessagesTests.swift (2)

53-54: Consider using a more specific error!

While the test is correct, using a more specific error domain and code that matches actual API errors would make the test more realistic and valuable.

-        let sendError = ARTErrorInfo(domain: "SomeDomain", code: 123)
+        let sendError = ARTErrorInfo(domain: "io.ably.error", code: 40000) // Using Ably's error domain and a realistic error code

359-361: Fix the incorrect spec reference in the comment!

The comment indicates that the spec reference should be CHA-M4k, not CHA-M5k.

-    // Wrong name, should be CHA-M4k
-    // @spec CHA-M5k
+    // @spec CHA-M4k
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fafdf5 and aa85fba.

📒 Files selected for processing (43)
  • AblyChat-Package.xctestplan (1 hunks)
  • AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved (1 hunks)
  • CHANGELOG.md (1 hunks)
  • Example/AblyChatExample/ContentView.swift (10 hunks)
  • Example/AblyChatExample/MessageViews/DeletedMessageView.swift (1 hunks)
  • Example/AblyChatExample/MessageViews/MenuButtonView.swift (1 hunks)
  • Example/AblyChatExample/MessageViews/MessageView.swift (1 hunks)
  • Example/AblyChatExample/MessageViews/PresenceMessageView.swift (1 hunks)
  • Example/AblyChatExample/Mocks/Misc.swift (1 hunks)
  • Example/AblyChatExample/Mocks/MockClients.swift (9 hunks)
  • Example/AblyChatExample/Mocks/MockRealtime.swift (3 hunks)
  • Package.resolved (1 hunks)
  • Package.swift (1 hunks)
  • README.md (1 hunks)
  • Sources/AblyChat/AblyCocoaExtensions/Ably+Dependencies.swift (1 hunks)
  • Sources/AblyChat/ChatAPI.swift (3 hunks)
  • Sources/AblyChat/ChatClient.swift (4 hunks)
  • Sources/AblyChat/DefaultMessages.swift (6 hunks)
  • Sources/AblyChat/Dependencies.swift (1 hunks)
  • Sources/AblyChat/Events.swift (1 hunks)
  • Sources/AblyChat/Message.swift (4 hunks)
  • Sources/AblyChat/Messages.swift (2 hunks)
  • Sources/AblyChat/Occupancy.swift (1 hunks)
  • Sources/AblyChat/Room.swift (2 hunks)
  • Sources/AblyChat/RoomFeature.swift (2 hunks)
  • Sources/AblyChat/RoomReactions.swift (1 hunks)
  • Sources/AblyChat/Rooms.swift (3 hunks)
  • Sources/AblyChat/Typing.swift (1 hunks)
  • Sources/AblyChat/Version.swift (1 hunks)
  • Tests/AblyChatTests/ChatAPITests.swift (8 hunks)
  • Tests/AblyChatTests/DefaultChatClientTests.swift (1 hunks)
  • Tests/AblyChatTests/DefaultMessagesTests.swift (4 hunks)
  • Tests/AblyChatTests/DefaultRoomOccupancyTests.swift (3 hunks)
  • Tests/AblyChatTests/DefaultRoomTests.swift (11 hunks)
  • Tests/AblyChatTests/DefaultRoomTypingTests.swift (3 hunks)
  • Tests/AblyChatTests/DefaultRoomsTests.swift (10 hunks)
  • Tests/AblyChatTests/IntegrationTests.swift (2 hunks)
  • Tests/AblyChatTests/MessageSubscriptionTests.swift (1 hunks)
  • Tests/AblyChatTests/MessageTests.swift (4 hunks)
  • Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (1 hunks)
  • Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift (5 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtime.swift (3 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (7 hunks)
✅ Files skipped from review due to trivial changes (4)
  • README.md
  • AblyChat-Package.xctestplan
  • Tests/AblyChatTests/DefaultRoomTests.swift
  • Tests/AblyChatTests/DefaultRoomsTests.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • Tests/AblyChatTests/DefaultRoomTypingTests.swift
  • Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift
🧰 Additional context used
🪛 LanguageTool
CHANGELOG.md

[uncategorized] ~24-~24: Although a hyphen is possible, it is not necessary in a compound modifier in which the first word is an adverb that ends in ‘ly’.
Context: ...ed This release reverts the pinning of ably-cocoa that was introduced in version 0.1.1 (h...

(HYPHENATED_LY_ADVERB_ADJECTIVE)

🪛 markdownlint-cli2 (0.17.2)
CHANGELOG.md

14-14: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


16-16: Bare URL used
null

(MD034, no-bare-urls)


18-18: Bare URL used
null

(MD034, no-bare-urls)


22-22: Multiple headings with the same content
null

(MD024, no-duplicate-heading)


24-24: Bare URL used
null

(MD034, no-bare-urls)


26-26: Bare URL used
null

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: Example app, tvOS (Xcode 16)
  • GitHub Check: Example app, iOS (Xcode 16)
  • GitHub Check: Example app, macOS (Xcode 16)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 16)
  • GitHub Check: Xcode, tvOS (Xcode 16)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 16)
  • GitHub Check: Xcode, iOS (Xcode 16)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 16)
  • GitHub Check: Xcode, macOS (Xcode 16)
  • GitHub Check: SPM (Xcode 16)
🔇 Additional comments (104)
Package.swift (1)

23-23: Updated dependency specification for ably-cocoa.

The dependency has been updated from using an exact version (1.2.36) to a range that allows any version from 1.2.40 and above. This change provides more flexibility for future updates while maintaining a minimum compatible version.

Package.resolved (1)

2-2: Package.resolved updated to match new ably-cocoa dependency.

The Package.resolved file has been correctly updated to reflect the dependency change to ably-cocoa version 1.2.40. The originHash has been updated accordingly.

Also applies to: 9-10

AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved (1)

2-2: Workspace Package.resolved updated to match new ably-cocoa dependency.

The workspace Package.resolved file has been correctly updated to reflect the dependency change to ably-cocoa version 1.2.40. The originHash has been updated accordingly.

Also applies to: 9-10

Sources/AblyChat/Occupancy.swift (1)

37-37: Protocol update to match Swift's explicit handling of existential types

The change from RealtimeChannelProtocol to any RealtimeChannelProtocol follows Swift's evolution toward more explicit handling of existential types, making the code more future-proof.

Sources/AblyChat/Typing.swift (1)

57-57: Protocol update to match Swift's explicit handling of existential types

The change from RealtimeChannelProtocol to any RealtimeChannelProtocol follows Swift's evolution toward more explicit handling of existential types, making the code more future-proof.

Sources/AblyChat/RoomReactions.swift (1)

25-25: Protocol update to match Swift's explicit handling of existential types

The change from RealtimeChannelProtocol to any RealtimeChannelProtocol follows Swift's evolution toward more explicit handling of existential types, making the code more future-proof.

Example/AblyChatExample/MessageViews/DeletedMessageView.swift (2)

3-23: Clean implementation of DeletedMessageView.

The view structure is well-designed with appropriate visual hierarchy for displaying deleted messages. Good use of SwiftUI styling for differentiating the client ID and the deletion message.


19-21: Good platform-specific customization.

Appropriate use of conditional compilation to hide the list row separator on non-tvOS platforms, ensuring consistent appearance across different Apple platforms.

Example/AblyChatExample/MessageViews/MenuButtonView.swift (2)

3-20: Well-structured MenuButtonView implementation.

Clean implementation of a reusable menu component with appropriate actions for editing and deleting messages. Good use of SF Symbols for visual consistency.


13-15: Appropriate role assignment for destructive action.

The delete button is correctly configured with the .destructive role, providing users with a visual cue about the nature of this action.

Sources/AblyChat/Events.swift (3)

11-12: Appropriate extension of MessageAction enum.

The addition of update and delete cases aligns with the PR objective of enhancing message handling capabilities.


18-21: Comprehensive handling of Realtime actions.

The fromRealtimeAction method has been properly updated to handle the new message action types, ensuring consistency between the application's enum and the underlying Ably library's actions.


23-24: Well-maintained ignored actions list.

The list of actions to ignore has been correctly updated to remove the now-handled actions, leaving only truly ignored cases.

Example/AblyChatExample/Mocks/Misc.swift (1)

21-24: Updated Message initialization with new required properties.

The mock implementation has been properly updated to include the new properties required by the Message model: version, timestamp, and operation.

Tests/AblyChatTests/ChatAPITests.swift (3)

11-13: Updated MockRealtime instantiation.

The code has been updated to use the new MockRealtime initialization pattern, which appears to be a consistent change across the test suite.


50-52: Added version and timestamp properties to Message instantiation.

These new properties (version and timestamp) have been added to align with an update to the Message model structure. The version is set to match the message serial, maintaining data consistency.


148-150: Added version and timestamp properties to multiple Message objects.

The expected test message objects have been properly updated to include the new required properties. The implementation correctly maps the timestamp to match the creation date when available, and sets it to nil when the creation date is nil.

Also applies to: 160-162

Tests/AblyChatTests/DefaultRoomOccupancyTests.swift (2)

10-19: Updated MockRealtime instantiation with closure.

The code has been updated to use the new MockRealtime initialization pattern with a closure that returns the test response data. This change is consistent with updates in other test files.


38-38: Simplified MockRealtime instantiation.

The MockRealtime initialization has been simplified to use the default constructor since these tests don't require custom HTTP responses. This makes the code cleaner while maintaining test functionality.

Also applies to: 60-60

Tests/AblyChatTests/MessageSubscriptionTests.swift (1)

29-29: Updated Message instantiation with new required properties.

The Message initialization has been updated to include the new version and timestamp properties, maintaining compatibility with the updated Message struct. This change ensures consistent use of the Message model across the test suite.

Example/AblyChatExample/MessageViews/PresenceMessageView.swift (1)

4-34: New view for displaying presence messages.

The implementation looks clean and follows SwiftUI best practices. The generatePresenceMessage() function effectively constructs a human-readable message based on presence data.

Example/AblyChatExample/MessageViews/MessageView.swift (2)

4-51: Well-structured SwiftUI view with good separation of concerns

This is a clean implementation of a message view that properly handles both sender and receiver message presentations. The view appropriately manages state for editing and deletion confirmation.


53-56: Good use of a dedicated model struct for view data

Creating a separate MessageListItem struct to wrap the message with presentation metadata is a good practice that separates view concerns from data model concerns.

Tests/AblyChatTests/MessageTests.swift (3)

13-15: Consistent addition of new Message properties in tests

The new headers, version, and timestamp properties have been added to all test fixtures, maintaining test consistency with updated model properties.


39-41: Good test coverage for invalid scenario

Setting the version to "invalid" for the invalidMessage test fixture helps ensure the system handles malformed data appropriately.


85-87: Maintain consistency in test fixtures

All message instances now properly include the new properties required by the Message struct, ensuring tests remain valid with the updated model.

Tests/AblyChatTests/Mocks/MockFeatureChannel.swift (3)

5-5: Updated to use explicit existential type syntax

The change to any RealtimeChannelProtocol reflects modern Swift practices for working with protocol types.


10-10: Consistent use of explicit existential type in initializer

The initializer parameter has been properly updated to match the property declaration, maintaining consistency throughout the class.


4-32:

✅ Verification successful

Verify consistent usage of existential types across codebase

Ensure that all other implementations using RealtimeChannelProtocol have been similarly updated to use the any keyword for consistency.


🏁 Script executed:

#!/bin/bash
# Check for any remaining instances of RealtimeChannelProtocol without 'any'
rg "RealtimeChannelProtocol" --type swift -g "!*.generated.swift" | grep -v "any RealtimeChannelProtocol" | grep -v "protocol RealtimeChannelProtocol"

Length of output: 1833


Existential types usage verified.
After reviewing the code, the MockFeatureChannel file correctly declares its property as any RealtimeChannelProtocol. The grep results confirm that other references to RealtimeChannelProtocol—such as in class conformances, type constraints, and comments—are used correctly without the any keyword. This usage distinction is intentional and aligns with Swift’s language rules regarding existentials versus protocol conformances.

Sources/AblyChat/Version.swift (3)

7-7: Version bump from 0.1.1 to 0.2.0

The version increment follows semantic versioning principles, indicating significant new functionality without breaking changes.


9-9: Improved structure for agent information

Changing from a string-based approach to a dictionary makes the agent information more structured and extensible for future additions.


3-10:

❓ Verification inconclusive

Verify usage of the new agents dictionary throughout the codebase

Since the previous defaultChannelParams variable has been removed, ensure all code that used it has been updated to use the new agents dictionary.


🏁 Script executed:

#!/bin/bash
# Check for any remaining references to defaultChannelParams
rg "defaultChannelParams" --type swift

# Check how the new agents dictionary is used
rg "agents\[" --type swift

Length of output: 67


Agents Dictionary Migration Verification

  • Automated searches did not find any occurrences of the old defaultChannelParams in the Swift codebase.
  • The new agents dictionary declared in Sources/AblyChat/Version.swift appears to be in place.
  • However, since the search commands returned no output, please manually verify that no indirect usages of defaultChannelParams exist and that all references now correctly use the new agents dictionary.
Sources/AblyChat/RoomFeature.swift (2)

49-49: Updated property to use 'any' keyword for protocol type.

The code has been updated to use the modern Swift syntax for existential types, adding the any keyword before RealtimeChannelProtocol. This change makes the protocol usage more explicit without changing the functionality.


65-65: Consistent protocol syntax update.

Updating the implementation to match the protocol declaration with the any keyword ensures consistency across the codebase.

Sources/AblyChat/Room.swift (2)

303-303: Added 'any' keyword for existential type.

This change follows the same pattern as seen in RoomFeature.swift, using the any keyword to explicitly mark the protocol type as existential.


318-318: Changed options initialization to use direct Ably types.

Two changes here:

  1. Changed from var to let - this is good as it promotes immutability
  2. Changed from RealtimeChannelOptions to ARTRealtimeChannelOptions - using the direct Ably type instead of a wrapper

Both changes improve the code quality.

Sources/AblyChat/Rooms.swift (3)

50-50: Updated return type from 'ClientOptions' to 'ChatClientOptions'.

This type name change makes the purpose more explicit and specific to chat functionality, improving code readability.


63-63: Updated property type for consistency.

Property type has been updated from ClientOptions to ChatClientOptions to match the protocol definition.


118-118: Updated constructor parameter type.

The parameter type change ensures consistency with the property declaration and protocol changes.

Sources/AblyChat/AblyCocoaExtensions/Ably+Dependencies.swift (5)

5-6: Added protocol conformance for wrapper class.

New extension added to make ARTWrapperSDKProxyRealtime conform to RealtimeClientProtocol, ensuring consistent interfaces across both direct and proxy classes.


8-8: Added protocol conformance for channels wrapper class.

Similar to the previous change, this extends protocol conformance to the wrapper class.


11-12: Added protocol conformance for channel wrapper.

This ensures that the wrapper implementation for realtime channels can be used interchangeably with the direct implementation.


13-14: Added protocol conformance for presence classes.

Both the direct ARTRealtimePresence and its wrapper now conform to RealtimePresenceProtocol, completing the protocol conformance pattern used throughout the file.


3-4:

✅ Verification successful

Changed ARTRealtime protocol conformance.

The protocol conformance has been updated from RealtimeClientProtocol to SuppliedRealtimeClientProtocol. This appears to be part of a broader architectural change in the protocol hierarchy.


🏁 Script executed:

#!/bin/bash
# Check definition of the new protocol to ensure ARTRealtime still satisfies all required functionality
rg -A 10 "protocol SuppliedRealtimeClientProtocol" --type swift

Length of output: 1004


Protocol conformance update verified

The updated conforming declaration in Sources/AblyChat/AblyCocoaExtensions/Ably+Dependencies.swift now uses SuppliedRealtimeClientProtocol, which has been confirmed by the search. The protocol definition in Sources/AblyChat/Dependencies.swift clearly shows that SuppliedRealtimeClientProtocol extends RealtimeClientProtocol and requires the createWrapperSDKProxy(with:) method. Assuming that ARTRealtime either implements or inherits the necessary functionality (as the build currently passes), this change appears valid and in-line with the broader architectural update.

Tests/AblyChatTests/IntegrationTests.swift (1)

162-218: Be mindful of potential race conditions in async message flow.

These added test steps for editing and deleting messages ensure correct client-server round-trip validation. However, relying on immediate subscription events in concurrent environments can be a source of flakiness. Consider adding small timeouts or retry logic if you notice intermittent CI failures, and ensure that the subscription receives events when system load is high.

Sources/AblyChat/Messages.swift (2)

43-43: Doc comment for send method looks good.

The note clarifies that the returned message uses .create action. No further issues identified.


49-79: New 'update' and 'delete' doc comments are clear.

Documentation for update and delete thoroughly explains parameter usage. The highlight on returning a message with .update or .delete action is very helpful.

Sources/AblyChat/DefaultMessages.swift (3)

9-11: Debug-only concurrency marking.

Marking ARTMessage as @unchecked Sendable in debug builds can be useful for testing. However, be aware that it bypasses some thread-safety checks, so guard carefully against concurrency bugs.


62-125: Structured error handling for malformed messages.

The do-catch flow for parsing incoming realtime messages is an improvement over silent guards. Emitting malformed events in debug mode is helpful for diagnosing issues. Ensure that production builds do not inadvertently expose these malformed events or logs to end users.


208-208: RESET logic on channel UPDATE with resumed=false is appropriate.

Resetting the subscription point for all listeners aligns with the requirement to handle missed messages when the channel transitions without resume. This prevents message gaps. Continue monitoring potential timing or concurrency issues in high-latency environments.

Example/AblyChatExample/ContentView.swift (2)

55-70: Ensure ID uniqueness for presence events.
Using timestamp.description as an ID may result in collisions if multiple events share the same timestamp. Consider a combination of timestamp plus a random UUID or an event-specific ID to avoid potential collisions.


246-280: Validate message ordering for previously fetched messages vs. newly created ones.
Older messages are appended, while newly created messages are prepended, potentially mixing chronological order. Ensure it matches the intended user experience.

Sources/AblyChat/Dependencies.swift (2)

6-10: Specify whether createWrapperSDKProxy requires error handling.
If createWrapperSDKProxy can fail for certain inputs, consider handling or throwing an error. Otherwise, confirming it always succeeds clarifies usage.


29-33: Double-check presence concurrency logic.
You’ve added an associated type Presence to RealtimeChannelProtocol. Ensure presence operations remain thread-safe, especially if multiple tasks modify presence concurrently.

Sources/AblyChat/ChatAPI.swift (2)

28-40: Confirm required JSON keys from the backend.
MessageOperationResponse assumes version and timestamp keys exist. This is acceptable, but ensure your server always returns them, or handle missing fields gracefully.


80-130: Handle partial updates carefully.
When updating a message, unspecified fields are removed. Confirm that client logic or UI accounts for the possible removal of optional fields (e.g., headers).

Tests/AblyChatTests/DefaultChatClientTests.swift (5)

8-11: Looks good
The updated initialization with MockRealtime(createWrapperSDKProxyReturnValue: .init()) is correct and aligns well with the intent to test default client options behavior when none are provided.


14-14: No concerns
Defining let defaultOptions = ChatClientOptions() to validate the fallback logic is consistent with the rest of the codebase.


18-27: New test method is correct
Verifies that client.realtime correctly references the passed-in instance rather than a proxy.


32-38: No issues found
The assertion confirming that the proxy is created with the expected agents ensures proper agent injection.


44-54: Rooms property test
These assertions confirm that rooms points to the wrapper SDK proxy client. Implementation looks consistent.

Sources/AblyChat/Message.swift (5)

13-17: Definition for OperationMetadata
Introduces a clear alias for operation-related metadata, aligning well with the rest of the message model.


102-114: Expanded initializer
Properly sets the new fields. This aligns with how versioning and operations are being handled in other parts of the code.


116-139: Introduction of copy method
Allows for selectively updating a subset of fields while retaining the rest. Straightforward and readable.


142-151: MessageOperation struct
Defines a simple structure for describing who performed an operation, optional descriptions, and related metadata.


156-175: JSON decoding for new properties
Parsing version, timestamp, and operation from the JSON object appears correct. Verify how missing or invalid values for these fields are handled in edge cases.

Example/AblyChatExample/Mocks/MockClients.swift (11)

6-6: Switched to ChatClientOptions
Enhances consistency across the mock and main code.


10-10: Updated init method
Falls back to a default ChatClientOptions() if none is provided. Logical and clean.


18-18: clientID fallback
Using "AblyTest" as a fallback in mocks is reasonable for testing scenarios.


23-23: Added chat client options property
Matches the other code changes for moving from ClientOptions to ChatClientOptions.


39-39: Initialization
Storing the provided clientOptions ensures each MockRooms instance aligns with the real environment’s setup.


91-91: Switched channel to an existential
Leveraging any RealtimeChannelProtocol affords greater flexibility in mocking.


111-115: Mock message creation
Populating headers, version, timestamp, and operation aligns with the updated Message structure for consistency.


191-191: Channel existential
Switching to any RealtimeChannelProtocol is consistent with your approach elsewhere.


211-211: Random interval range changed
Expanding to (0.3 ... 0.6) looks fine, just ensures slightly longer intervals in subscription mock generation.


238-238: Channel existential
Reinforces consistent usage of existential channels across mocks.


401-401: Channel existential
Allows diverse channel conformances for occupancy handling without losing type safety.

Example/AblyChatExample/Mocks/MockRealtime.swift (2)

12-14: Good improvement on the clientId implementation.

Returning a static string instead of throwing a fatal error is a better approach for a mock implementation as it provides a predictable value for testing without crashing.


108-108: Appropriate implementation of the presence property.

Replacing the computed property that previously raised a fatal error with a concrete instance of RealtimePresence is a good change for testing as it provides an actual implementation to work with.

Sources/AblyChat/ChatClient.swift (5)

38-38: Improved type naming with ChatClientOptions.

Changing from ClientOptions to ChatClientOptions is a good improvement for clarity, making it explicit that these options are specifically for the chat functionality.


47-48: Appropriate use of nonisolated for frequently accessed properties.

Marking realtime and clientOptions as nonisolated allows them to be accessed from outside the actor without awaiting, which is suitable for properties that are frequently accessed and don't change after initialization.


63-68: Enhanced initialization with wrapper SDK proxy support.

The updated initializer now properly uses the SuppliedRealtimeClientProtocol to create a wrapper SDK proxy with the agents configuration. This change allows for more flexibility in how the realtime client is created and configured.


75-80: Better error handling for clientID.

The improved implementation provides a clear error message when the clientId is missing, which will help developers identify and fix the issue more quickly.


86-86: Consistent renaming to ChatClientOptions.

The struct has been renamed from ClientOptions to ChatClientOptions along with its usage in the equality comparison method, maintaining consistency throughout the codebase.

Also applies to: 107-107

Tests/AblyChatTests/Mocks/MockRealtime.swift (5)

6-6: Updated protocol conformance for wrapper SDK support.

The class now conforms to SuppliedRealtimeClientProtocol instead of RealtimeClientProtocol, which is necessary to support the wrapper SDK proxy functionality.


10-10: Added properties for wrapper SDK proxy testing.

The addition of createWrapperSDKProxyReturnValue and _createWrapperSDKProxyOptionsArgument properties enables testing of the wrapper SDK proxy functionality by allowing tests to control the returned value and inspect the passed options.

Also applies to: 16-16


29-36: Updated initializer to support wrapper SDK proxy testing.

The initializer now accepts a createWrapperSDKProxyReturnValue parameter, allowing tests to specify what value should be returned by the createWrapperSDKProxy method.


81-91: Well-implemented createWrapperSDKProxy method.

This method properly implements the requirement from SuppliedRealtimeClientProtocol, with good error handling when the return value is not set. It also stores the options argument for later inspection.


93-99: Thread-safe access to options argument.

The computed property provides thread-safe access to the _createWrapperSDKProxyOptionsArgument property by using a mutex lock, which is a good practice for properties that might be accessed from multiple threads.

Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (8)

9-9: Enhanced state management capabilities.

The addition of the _state property and updated state computed property allows for more flexible control over the channel state during tests, which is valuable for testing different state scenarios.

Also applies to: 47-48, 86-90


13-13: Improved type specificity for presence property.

Using some RealtimePresenceProtocol instead of the less specific type provides better type information and is a good Swift practice.


20-24: Good addition of callback storage for state changes.

The new typealias and callback properties provide a structured way to store and manage callbacks for channel state changes, which is essential for testing event handling.


32-33: Enhanced MessageToEmit struct with operation and version.

Adding operation and version properties to the MessageToEmit struct allows for more detailed message configuration, which is helpful for comprehensive testing.


119-148: Well-structured state change callback methods.

The new methods for handling state change callbacks are well-organized and handle the different scenarios (attach/detach success/failure) appropriately. The use of @MainActor ensures that UI updates are performed on the main thread.


163-166: Proper asynchronous execution of state callbacks.

Using Task to asynchronously execute the state callbacks after the attach/detach operations is a good approach that better simulates the real-world behavior of these operations.

Also applies to: 185-188


191-192: Added support for JSON-based message data.

The new messageJSONToEmitOnSubscribe property and corresponding implementation in the subscribe method provide a more flexible way to define test messages using JSON data.

Also applies to: 202-229


257-265: Improved event listener implementation.

The updated on methods now properly store callbacks in the appropriate collection, allowing them to be invoked later when state changes occur, which is crucial for testing event handling.

Tests/AblyChatTests/DefaultMessagesTests.swift (7)

10-24: Test implementation looks good!

This new test correctly verifies that clients can send messages via the REST Chat API. The test structure follows the Arrange-Act-Assert pattern, properly mocks dependencies, and has an appropriate assertion.


26-47: Clear test for payload structure verification!

This test effectively verifies that metadata and headers are properly omitted from REST payloads when not specified by the user.


88-118: Test implementation looks good!

This new test properly verifies the behavior of subscription points when the underlying realtime channel is not in the ATTACHED state. The test setup correctly simulates the ATTACHING state and the assertions validate the expected behavior.


215-251: Test implementation is thorough and clear!

This test effectively verifies that subscription.getPreviousMessages:

  1. Uses the correct subscription point
  2. Forces direction to "backwards" regardless of input
  3. Properly handles pagination

Good use of multiple assertions to verify different aspects of the behavior.


362-384: Well-structured test for malformed event handling!

The test correctly verifies that malformed events are not passed to regular subscribers but are emitted to the malformed message subscribers. The test setup with a malformed JSON message is particularly effective.


317-319: Consider adding tests for the untestable specs if possible!

The comments indicate that CHA-M4b and CHA-M4d are currently untestable. Consider if there are alternative approaches to verify this functionality, such as:

  1. Testing memory management with a weak reference
  2. Using reflection or debug-only features for verification
  3. Restructuring the code to make these aspects testable

120-162:

❓ Verification inconclusive

Investigate potential race condition in failing test!

This test is reported as failing 14-18% locally and >50% in CI according to PR comments. The test logic is sound, but the intermittent failures suggest a potential race condition in the test or implementation.

The failure could be due to timing issues between the channel state transitions and checking the subscription point. Consider:

  1. Adding explicit awaits or delays between state transitions
  2. Adding logging to track the exact sequence of events
  3. Adding more deterministic synchronization points

🏁 Script executed:

#!/bin/bash
# Look for any potential race conditions or timing issues in DefaultMessages implementation
rg -A 10 -B 10 "resetSubscriptionPoint|updateSubscriptionPoint" Sources/

Length of output: 74


Action Required: Investigate and Mitigate Potential Race Conditions in Subscription State Updates

While intermittent failures—with a 14–18% local and >50% CI failure rate—indicate a timing sensitivity between channel state transitions and the subscription point check, our search for functions explicitly named like resetSubscriptionPoint or updateSubscriptionPoint in the Sources directory returned no clues. This lack of explicit evidence makes it hard to pinpoint the root cause automatically, so please verify that the asynchronous operations around attach/detach transitions are reliably synchronized.

Consider the following improvements:

  • Introduce explicit awaits or brief delays between state transitions to ensure the channel state is fully updated.
  • Add detailed logging to trace the sequence of events and verify that the subscription point is updated as expected.
  • Enhance synchronization mechanisms (or introduce additional deterministic checkpoints) to avoid potential race conditions.

@github-actions github-actions bot temporarily deployed to staging/pull/219/AblyChat March 2, 2025 16:37 Inactive
@maratal maratal force-pushed the 140-typing-tests branch from 3e11ea0 to cce8978 Compare March 4, 2025 16:11
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: 0

🧹 Nitpick comments (1)
Tests/AblyChatTests/DefaultMessagesTests.swift (1)

359-384: Test for malformed event handling.

This test ensures that malformed realtime events are not propagated to subscribers but are captured by the specialized subscription method. There appears to be a discrepancy in the spec reference (@SPEC CHA-M5k vs comment that mentions it should be CHA-M4k).

There's a minor inconsistency in the test annotation. The comment indicates the spec should be CHA-M4k, but the actual annotation is @SPEC CHA-M5k. Consider updating the annotation to match the correct specification reference.

-// Wrong name, should be CHA-M4k
-// @spec CHA-M5k
+// @spec CHA-M4k
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa85fba and 5bf2afd.

📒 Files selected for processing (9)
  • Sources/AblyChat/DefaultMessages.swift (4 hunks)
  • Sources/AblyChat/DefaultRoomReactions.swift (2 hunks)
  • Tests/AblyChatTests/ChatAPITests.swift (0 hunks)
  • Tests/AblyChatTests/DefaultMessagesTests.swift (4 hunks)
  • Tests/AblyChatTests/DefaultRoomReactionsTests.swift (1 hunks)
  • Tests/AblyChatTests/Helpers/Helpers.swift (2 hunks)
  • Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift (4 hunks)
  • Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (8 hunks)
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleContributorChannel.swift (5 hunks)
💤 Files with no reviewable changes (1)
  • Tests/AblyChatTests/ChatAPITests.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • Tests/AblyChatTests/Mocks/MockRoomLifecycleContributorChannel.swift
  • Tests/AblyChatTests/Helpers/Helpers.swift
⏰ Context from checks skipped due to timeout of 90000ms (12)
  • GitHub Check: Example app, tvOS (Xcode 16)
  • GitHub Check: Example app, iOS (Xcode 16)
  • GitHub Check: Example app, macOS (Xcode 16)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 16)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 16)
  • GitHub Check: Xcode, tvOS (Xcode 16)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 16)
  • GitHub Check: Xcode, iOS (Xcode 16)
  • GitHub Check: SPM (Xcode 16)
  • GitHub Check: SPM, release configuration (Xcode 16)
  • GitHub Check: Xcode, macOS (Xcode 16)
  • GitHub Check: check-documentation
🔇 Additional comments (31)
Sources/AblyChat/DefaultRoomReactions.swift (2)

81-85: Good implementation of debug-only error handling.

This code correctly emits malformed messages to test subscriptions in DEBUG mode only, which is a good approach for diagnostic purposes without affecting production behavior.


106-116: Clear testing API for malformed messages.

The implementation provides a well-designed testing API that follows Swift conventions:

  1. Debug-only code is properly isolated with #if DEBUG
  2. Clear documentation comments explaining the purpose of each component
  3. The method name clearly indicates it's for testing only

This matches the similar implementation in DefaultMessages.swift, maintaining consistency across the codebase.

Tests/AblyChatTests/DefaultRoomReactionsTests.swift (4)

48-48: Improved test naming for clarity.

The new method name subscriptionCanBeRegisteredToReceiveReactionEvents more precisely describes the test purpose compared to the previous subscribe_returnsSubscription, making the test intention clearer.


50-67: More comprehensive test fixture setup.

The detailed JSON structure for the mock message provides better test coverage by including all relevant fields (name, clientId, data, timestamp, extras) that would be present in a real message.


74-77: More thorough assertion of reaction properties.

The test now verifies the actual reaction content (type is ":like:") rather than just checking that a subscription exists, providing stronger validation that the message parsing works correctly.


79-96: Good implementation of malformed message test.

This test effectively validates the new malformed message handling functionality:

  1. Tests spec point CHA-ER4d
  2. Creates a malformed message scenario
  3. Verifies that malformed messages are properly captured for debugging while not disrupting regular subscriptions

This complements the changes in DefaultRoomReactions.swift by ensuring the debug-only functionality works as expected.

Sources/AblyChat/DefaultMessages.swift (6)

9-11: Appropriate use of Sendable conformance for testing.

The @retroactive @unchecked Sendable extension for ARTMessage is correctly isolated to DEBUG builds only. This approach allows testing code to work with ARTMessage in concurrent contexts without compiler warnings, while maintaining stricter type safety in production builds.


61-124: Improved error handling with structured catch blocks.

The refactoring from direct guard statement throws to a do-catch block significantly improves the code structure:

  1. The change maintains the same validation logic while providing better error handling
  2. The debug-only malformed message emission is cleanly integrated
  3. Error logging is properly implemented at the appropriate level

This approach is consistent with the changes in DefaultRoomReactions.


143-153: Consistent implementation of debug testing helpers.

The malformed message subscription implementation matches the one in DefaultRoomReactions, maintaining consistency across similar components in the codebase. This is a good practice for ensuring a unified approach to testing and debugging.


164-166: New API method for updating messages.

The addition of the update method enhances the Messages API functionality, allowing for message updates. The implementation follows the same pattern as other API methods in the class.


168-170: New API method for deleting messages.

The addition of the delete method completes the CRUD operations for messages. The implementation is consistent with other API methods in the class.


209-209: Updated specification reference.

The comment reference has been updated from CHA-M4d to CHA-M5d, ensuring accurate cross-referencing to the specification document.

Tests/AblyChatTests/Mocks/MockHTTPPaginatedResponse.swift (4)

13-13: Simplified constructor parameter list.

Removing the redundant isLast parameter simplifies the API by eliminating a potentially confusing parameter that could lead to contradictory states (if hasNext and isLast were set inconsistently).


43-44: More maintainable property derivation.

The isLast property now directly derives its value from !_hasNext, which:

  1. Eliminates the need for a separate _isLast property
  2. Ensures consistency between hasNext and isLast properties
  3. Reduces the chance of bugs due to inconsistent property values

This is a more maintainable approach following the DRY principle.


120-122: Enhanced pagination test configuration.

Setting hasNext: true on the successGetMessagesWithItems instance properly configures this mock to represent a non-final page in a paginated sequence, making it more suitable for testing pagination functionality.


127-155: More complete mock message structure.

The updated mock messages now include all required fields:

  1. Addition of clientId, action, createdAt, version
  2. Updated serial values to ensure proper sequence
  3. More descriptive text fields ("next hello" vs "hello")
  4. Correct configuration of hasNext: false for the last page

These changes make the mocks more accurately reflect real API responses and support testing the pagination behavior.

Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (8)

10-11: Added private state property provides better mock flexibility.

The addition of the _state property enables explicit control over the channel state in test scenarios, which is a good enhancement for testing various channel states.


21-25: Improved callback mechanism for state change events.

Adding the type alias and callback properties creates a more flexible system for handling state transitions in tests, allowing verification of state change behaviors.


40-45: Enhanced initializer with better state control and message handling options.

The updated initializer provides more flexibility by:

  1. Making state optional with a default of nil (instead of the previous .suspended)
  2. Adding the messageJSONToEmitOnSubscribe parameter for more granular control over emitted messages

These changes will make testing different scenarios easier and more precise.


87-91: Improved state computation logic.

The state property now first checks for an explicitly set state before falling back to the attachment-based logic. This provides more control and clarity in test scenarios.


119-149: Well-structured state change callback methods.

The implementation of these methods provides a clean, organized approach to handling state change notifications:

  • performStateChangeCallbacks: Centralized method for all callbacks
  • performStateAttachCallbacks: Specialized for attach operations
  • performStateDetachCallbacks: Specialized for detach operations

This structure improves testability of state transitions.


164-167: Asynchronous state change notifications.

Using Task to perform state change callbacks asynchronously better simulates real-world behavior where state changes occur after the operation completes. This is particularly important for testing race conditions that might exist in the actual implementation.

Also applies to: 186-189


192-192: Enhanced message subscription with JSON support.

The addition of messageJSONToEmitOnSubscribe and the corresponding handling in the subscribe method allows for more flexible and realistic message creation in tests. This is particularly valuable for testing edge cases with different message formats.

Also applies to: 203-220


258-266: Updated callback registration methods.

The on methods now store callbacks in the appropriate properties, completing the implementation of the callback mechanism. This will help ensure that state change events are properly propagated in tests.

Tests/AblyChatTests/DefaultMessagesTests.swift (7)

10-24: Improved test case for REST API message sending.

The renamed test clientMaySendMessageViaRESTChatAPI now properly focuses on verifying successful message sending via the REST API rather than error conditions. This better aligns with the CHA-M3f and CHA-M3a specifications noted in the comments.


26-47: Clear test for payload field omission.

The renamed test whenMetadataAndHeadersAreNotSpecifiedByUserTheyAreOmittedFromRESTPayload now explicitly verifies that metadata and headers are omitted from the REST payload when not specified by the user, which is an important validation of the API behavior.


88-118: Good test for subscription point behavior with not-attached channels.

This new test verifies the important behavior that the subscription point is set to the attachSerial when the underlying channel is not in the ATTACHED state, covering a distinct edge case in the protocol.


120-162: Tests for subscription point reset on channel reattachment.

This test verifies the critical behavior of resetting the subscription point when a channel reenters the ATTACHED state with resumed=false. This is exactly the kind of test needed to catch regression issues in state management.

Based on past review comments, this test was reported as failing intermittently (14-18% locally, >50% in CI). Since this PR seems to modify the test infrastructure with better async state handling, we should verify that it now passes consistently.

#!/bin/bash
# Check if this test has been fixed by looking for any recent changes that might address the issue
rg -A 5 "whenChannelReentersATTACHEDWithResumedFalseThenSubscriptionPointResetsToAttachSerial" --type swift Tests/ Sources/
rg -A 10 "performStateChangeCallbacks" --type swift Tests/ Sources/

164-213: Tests for subscription point reset on UPDATE event.

Similar to the previous test, this verifies the reset of subscription points when an UPDATE event with resumed=false is received. This test is essential for validating the channel's recovery mechanism in response to server-initiated updates.

This test was also reported as failing intermittently in previous review comments. The updated MockRealtimeChannel should address this with its improved state handling, but we should verify that the fixes are effective.

#!/bin/bash
# Check if this test's behavior has been fixed or improved in recent changes
rg -A 5 "whenChannelUPDATEReceivedWithResumedFalseThenSubscriptionPointResetsToAttachSerial" --type swift Tests/ Sources/
rg -A 10 "resumed.*false" --type swift Sources/

215-251: Comprehensive test for history query options.

This test thoroughly validates that the subscription's getPreviousMessages method properly handles standard history query options, with the exception of direction which has special behavior. The assertions for pagination also verify that the returned results work as expected.


317-321: Clear documentation of untestable specifications.

The comments clearly explain which specifications cannot be tested (CHA-M4b and CHA-M4d) and why, which is valuable information for future developers. This kind of documentation helps maintain awareness of potential coverage gaps.

Base automatically changed from 140-typing-tests to main March 7, 2025 18:55
@maratal maratal merged commit a9dbd86 into main Mar 10, 2025
17 checks passed
@maratal maratal deleted the 86-messages-tests branch March 10, 2025 13:46
@coderabbitai coderabbitai bot mentioned this pull request Jul 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Add more tests for Messages section of the spec

3 participants