Skip to content

Conversation

@maratal
Copy link
Collaborator

@maratal maratal commented Oct 27, 2025

Remove transient disconnect handling from Connection.
Add tests for Connection.

Closes #393

Summary by CodeRabbit

  • Bug Fixes

    • Simplified connection handling by removing transient timer-based disconnect logic and streamlining cleanup and listener removal.
  • Tests

    • Added comprehensive tests for connection status mapping, status/error exposure, listener notifications/unsubscription, and simulated state transitions.
  • Documentation

    • Minor wording clarifications in connection status descriptions.

@coderabbitai
Copy link

coderabbitai bot commented Oct 27, 2025

Warning

Rate limit exceeded

@maratal has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 6 minutes and 56 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 76b0632 and 5311848.

📒 Files selected for processing (4)
  • Sources/AblyChat/Connection.swift (2 hunks)
  • Sources/AblyChat/DefaultConnection.swift (2 hunks)
  • Tests/AblyChatTests/DefaultConnectionTests.swift (1 hunks)
  • Tests/AblyChatTests/Mocks/MockConnection.swift (1 hunks)

Walkthrough

This PR removes transient-disconnect timer logic from DefaultConnection and simplifies onStatusChange to emit events directly. It adds comprehensive connection state tests and enhances mock connection helpers to allow state mutation and event emission for testing.

Changes

Cohort / File(s) Summary
Timer Logic Removal
Sources/AblyChat/DefaultConnection.swift
Removed TimerManager usage and all transient-disconnect timer checks/management from onStatusChange. Reworked guard handling for self and removed timer cancellation from cleanup; now status changes are emitted directly.
Connection State Tests
Tests/AblyChatTests/DefaultConnectionTests.swift
Added tests covering mapping of realtime connection states to public statuses (CHA-CS1), ChatClient status/error exposure and retrieval (CHA-CS2/CHA-CS3), and listener subscription/unsubscription behavior including event contents (CHA-CS4/CHA-CS5).
Mock Connection Enhancement
Tests/AblyChatTests/Mocks/MockConnection.swift
Made state and errorReason mutable; implemented on(_:) and off(_:) listener registration with private storage; added emit(...) helper to update state/error and notify listeners to simulate realtime events.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant App
    participant DefaultConnection
    participant Listener

    Note over DefaultConnection: Old Flow (with transient timer)
    rect rgb(230,240,250)
    DefaultConnection->>DefaultConnection: onStatusChange detected
    DefaultConnection->>DefaultConnection: evaluate/start/cancel transient timer
    DefaultConnection->>Listener: emit statusChange (after timer logic)
    end

    Note over DefaultConnection: New Flow (direct emit)
    rect rgb(230,250,230)
    DefaultConnection->>DefaultConnection: onStatusChange detected
    DefaultConnection->>Listener: emit statusChange immediately
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay extra attention to:
    • Sources/AblyChat/DefaultConnection.swift — ensure removal of timer logic doesn't omit necessary cleanup paths or change lifecycle semantics.
    • Tests/AblyChatTests/DefaultConnectionTests.swift — confirm tests correctly exercise mapping and listener semantics.
    • Tests/AblyChatTests/Mocks/MockConnection.swift — verify listener registration/unregistration and emit behavior (threading/ordering).

Poem

🐰 Timers folded, paths now clean and bright,

I hop and whisper status through the night.
Mocks awake to mimic every state,
Tests clap paws to validate their fate.
Hooray — connections sprint without delay! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "ECO-5592 Remove transient disconnect handling from Connection" is directly related to the main change in the changeset. According to the summary, the primary modification is the removal of TimerManager usage and transient-disconnect timer logic from the DefaultConnection component's onStatusChange method. The title is concise, specific, and clearly communicates the primary change. A teammate reviewing the commit history would immediately understand the intent of this PR.
Linked Issues Check ✅ Passed The pull request satisfies the requirements from both linked issues (#393 and ECO-5592). The primary objective to remove transient disconnect handling from the Connection implementation is met through the removal of TimerManager usage and timer-related logic from DefaultConnection.swift. Additionally, the PR adds comprehensive test coverage for the Connection component as required, including tests for connection state mapping (CHA-CS1), status and error exposure (CHA-CS2/CHA-CS3), and listener observation behavior (CHA-CS4/CHA-CS5). The test suite uses enhanced mock implementations that enable proper state simulation and event emission. All coding-related requirements from the linked issues are addressed.
Out of Scope Changes Check ✅ Passed All changes in the pull request are directly related to the stated objectives. The modifications to DefaultConnection.swift remove the transient disconnect handling as required. The new test file DefaultConnectionTests.swift provides test coverage for the Connection component as explicitly requested in the linked issues. The enhancements to MockConnection.swift, including mutable state properties, listener tracking, and the emit helper method, are necessary supporting changes to enable the test coverage being introduced. No unrelated or out-of-scope modifications are present in this changeset.

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

❤️ Share

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

@github-actions github-actions bot temporarily deployed to staging/pull/464/AblyChat October 27, 2025 01:57 Inactive
@maratal maratal force-pushed the 393-remove-transient-disconnect branch from 3dc9e07 to cf9e95a Compare October 28, 2025 01:45
@maratal maratal marked this pull request as ready for review October 28, 2025 01:46
@github-actions github-actions bot temporarily deployed to staging/pull/464/AblyChat October 28, 2025 01:47 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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
Tests/AblyChatTests/Mocks/MockConnection.swift (1)

5-24: Consider marking mock with @mainactor for consistency.

The mock now has mutable state (state, errorReason, listeners) and methods that use @MainActor callbacks. Per coding guidelines, stateful objects should be marked with @MainActor. While this may be more flexible for test code, marking the mock with @MainActor would ensure thread-safety and align with the guidelines.

As per coding guidelines.

Apply this diff to add @MainActor isolation:

 import Ably
 @testable import AblyChat
 
+@MainActor
 final class MockConnection: InternalConnectionProtocol {
     var state: ARTRealtimeConnectionState
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8ed5e8b and cf9e95a.

📒 Files selected for processing (3)
  • Sources/AblyChat/DefaultConnection.swift (2 hunks)
  • Tests/AblyChatTests/DefaultConnectionTests.swift (1 hunks)
  • Tests/AblyChatTests/Mocks/MockConnection.swift (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: Use protocol-based design; expose SDK functionality via protocols and prefer associated types with opaque return types (some Protocol) instead of existentials (any Protocol)
Isolate all mutable state to the main actor; mark stateful objects with @mainactor
Public API must use typed throws with ErrorInfo; use InternalError internally and convert at the public API boundary
For public structs emitted by the API, provide an explicit public memberwise initializer
When using AsyncSequence operators in @mainactor contexts, mark operator closures as @sendable
Task, CheckedContinuation, and AsyncThrowingStream do not support typed errors; use Result and call .get() to surface typed errors
Do not use Dictionary.mapValues for typed throws; use ablyChat_mapValuesWithTypedThrow instead
When the compiler struggles with typed throws, explicitly declare the error type on do blocks (e.g., do throws(InternalError))
Specify error types in closures using the throws(ErrorType) syntax (e.g., try items.map { jsonValue throws(InternalError) in ... })
Mark any test-only APIs with testsOnly_ prefix and wrap them in #if DEBUG

Files:

  • Tests/AblyChatTests/DefaultConnectionTests.swift
  • Sources/AblyChat/DefaultConnection.swift
  • Tests/AblyChatTests/Mocks/MockConnection.swift
Tests/AblyChatTests/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Tests/AblyChatTests/**/*.swift: Use test attribution tags to reference spec coverage: @SPEC, @specOneOf(m/n), @specPartial, @specUntested, @specNotApplicable
For Swift Testing #expect(throws:) with typed errors, move typed-throw code into a separate non-typed-throw function until Xcode 16.3+

Files:

  • Tests/AblyChatTests/DefaultConnectionTests.swift
  • Tests/AblyChatTests/Mocks/MockConnection.swift
Tests/AblyChatTests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place tests under Tests/AblyChatTests/ as the root for test targets

Files:

  • Tests/AblyChatTests/DefaultConnectionTests.swift
  • Tests/AblyChatTests/Mocks/MockConnection.swift
Tests/AblyChatTests/Mocks/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Put mock implementations under Tests/AblyChatTests/Mocks/

Files:

  • Tests/AblyChatTests/Mocks/MockConnection.swift
🧬 Code graph analysis (2)
Tests/AblyChatTests/DefaultConnectionTests.swift (2)
Sources/AblyChat/DefaultConnection.swift (1)
  • onStatusChange (22-53)
Tests/AblyChatTests/Mocks/MockConnection.swift (2)
  • emit (27-50)
  • off (22-24)
Tests/AblyChatTests/Mocks/MockConnection.swift (2)
Sources/AblyChat/AblyCocoaExtensions/InternalAblyCocoaTypes.swift (5)
  • on (261-265)
  • on (473-477)
  • on (479-483)
  • off (267-269)
  • off (509-511)
Tests/AblyChatTests/Mocks/MockRealtimeChannel.swift (3)
  • on (166-169)
  • on (171-177)
  • off (197-199)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Example app, tvOS (Xcode 26.0)
  • GitHub Check: Example app, macOS (Xcode 26.0)
  • GitHub Check: Xcode, tvOS (Xcode 26.0)
  • GitHub Check: Xcode, iOS (Xcode 26.0)
  • GitHub Check: Example app, iOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, macOS (Xcode 26.0)
  • GitHub Check: Xcode, macOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, iOS (Xcode 26.0)
  • GitHub Check: Xcode, release configuration, tvOS (Xcode 26.0)
  • GitHub Check: SPM, release configuration (Xcode 26.0)
  • GitHub Check: SPM (Xcode 26.0)
  • GitHub Check: lint
🔇 Additional comments (6)
Sources/AblyChat/DefaultConnection.swift (2)

26-28: Guard pattern is valid but serves only as a lifecycle check.

The change from guard let self to guard self != nil is correct since self is not used after the guard. This pattern ensures the callback stops executing if the DefaultConnection is deallocated, which is a reasonable lifecycle management approach.


47-52: Verify listener cleanup lifecycle is intentional.

The subscription cleanup also uses weak self, which means if DefaultConnection is deallocated before the user calls subscription.off(), the listener will remain registered on the underlying realtime connection (though the closure will return early due to the guard check at line 26). This could lead to listener accumulation on the realtime connection object.

An alternative pattern would be to track registered listeners and clean them up in a deinit method. However, the current pattern is simpler and places lifecycle management responsibility on the caller.

Please confirm this lifecycle pattern is intentional and that callers are expected to manage subscription cleanup explicitly.

Tests/AblyChatTests/DefaultConnectionTests.swift (3)

9-98: LGTM! Comprehensive status mapping coverage.

The CHA-CS1 tests thoroughly cover all connection status mappings (initialized, connecting, connected, disconnected, suspended, failed) with proper @SPEC attribution. The test structure is clear with Given/When/Then comments, and the use of mocks is appropriate.


105-142: LGTM! Status and error exposure properly tested.

The CHA-CS2/CHA-CS3 tests verify that the connection properly exposes its current status and error. Both scenarios (with and without error) are covered, and the tests correctly validate error properties (code, message, statusCode).


153-195: LGTM! Comprehensive listener lifecycle test.

This test thoroughly validates the complete listener behavior including registration, event notification with correct current/previous status and error, and unsubscription. The use of MockConnection.emit() to simulate state changes is appropriate, and the test properly verifies that unsubscription stops further event delivery.

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

27-50: LGTM! Well-designed test helper.

The emit() helper properly simulates connection state changes by:

  • Capturing the previous state before transitioning
  • Updating current state and error as needed
  • Constructing a proper ConnectionStateChange object
  • Notifying all registered listeners

The retryIn ?? 0 behavior is consistent with the known limitation documented in the TODO comment at line 39-40 of DefaultConnection.swift.

@lawrence-forooghian
Copy link
Collaborator

@AndyTWF is going to make some changes to connection status soon (reintroducing CLOSING and CLOSED); let's wait til he's done that and then make these changes on top of that

struct DefaultConnectionTests {
// MARK: - CHA-CS1: Connection Status Values

// @spec CHA-CS1a
Copy link
Collaborator

Choose a reason for hiding this comment

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

These spec references are generating warnings about non-testable spec points in the spec coverage report. Isn't CHA-CS3 the relevant spec point here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, I've missed that the whole CHA-CS1 is non-testable. Removed.

@github-actions github-actions bot temporarily deployed to staging/pull/464/AblyChat October 28, 2025 13:42 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.

Actionable comments posted: 0

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

90-91: Consider checking statusCode for consistency.

The error validation here only checks code and message, but the test at lines 46-48 also validates statusCode. Since the connectionError includes statusCode: 500 (line 69), consider adding that assertion for consistency:

 #expect(statusChange.error?.code == connectionError.code)
 #expect(statusChange.error?.message == connectionError.message)
+#expect(statusChange.error?.statusCode == connectionError.statusCode)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cf9e95a and 76b0632.

📒 Files selected for processing (1)
  • Tests/AblyChatTests/DefaultConnectionTests.swift (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: Use protocol-based design; expose SDK functionality via protocols and prefer associated types with opaque return types (some Protocol) instead of existentials (any Protocol)
Isolate all mutable state to the main actor; mark stateful objects with @mainactor
Public API must use typed throws with ErrorInfo; use InternalError internally and convert at the public API boundary
For public structs emitted by the API, provide an explicit public memberwise initializer
When using AsyncSequence operators in @mainactor contexts, mark operator closures as @sendable
Task, CheckedContinuation, and AsyncThrowingStream do not support typed errors; use Result and call .get() to surface typed errors
Do not use Dictionary.mapValues for typed throws; use ablyChat_mapValuesWithTypedThrow instead
When the compiler struggles with typed throws, explicitly declare the error type on do blocks (e.g., do throws(InternalError))
Specify error types in closures using the throws(ErrorType) syntax (e.g., try items.map { jsonValue throws(InternalError) in ... })
Mark any test-only APIs with testsOnly_ prefix and wrap them in #if DEBUG

Files:

  • Tests/AblyChatTests/DefaultConnectionTests.swift
Tests/AblyChatTests/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Tests/AblyChatTests/**/*.swift: Use test attribution tags to reference spec coverage: @SPEC, @specOneOf(m/n), @specPartial, @specUntested, @specNotApplicable
For Swift Testing #expect(throws:) with typed errors, move typed-throw code into a separate non-typed-throw function until Xcode 16.3+

Files:

  • Tests/AblyChatTests/DefaultConnectionTests.swift
Tests/AblyChatTests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place tests under Tests/AblyChatTests/ as the root for test targets

Files:

  • Tests/AblyChatTests/DefaultConnectionTests.swift
🧬 Code graph analysis (1)
Tests/AblyChatTests/DefaultConnectionTests.swift (2)
Sources/AblyChat/DefaultConnection.swift (1)
  • onStatusChange (22-53)
Tests/AblyChatTests/Mocks/MockConnection.swift (2)
  • emit (27-50)
  • off (22-24)
🔇 Additional comments (3)
Tests/AblyChatTests/DefaultConnectionTests.swift (3)

5-27: LGTM! Well-structured initialization test.

The test correctly verifies the initial connection status and error state. Using a real ARTRealtime instance (instead of mocks) is appropriate here since you're testing the integration of the initial state without any state transitions.


29-49: LGTM! Thorough error exposure test.

The test correctly verifies that connection errors are properly exposed through the API. The individual field assertions (code, message, statusCode) provide clear validation.


51-102: Excellent comprehensive listener test!

This test thoroughly covers the connection status observation lifecycle:

  • Registration and event delivery (CHA-CS4a/b/c/d)
  • Proper state transitions with error propagation
  • Unsubscription behavior (CHA-CS4e)
  • Alignment with underlying realtime library (CHA-CS5c)

The use of Given/When/Then comments enhances readability.

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.

Remove transient disconnect handling from Connection

3 participants