Skip to content

Commit

Permalink
Implement CHA-RL1g2
Browse files Browse the repository at this point in the history
Resolves #53.
  • Loading branch information
lawrence-forooghian committed Oct 8, 2024
1 parent e8ad145 commit c9afd8f
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 3 deletions.
27 changes: 24 additions & 3 deletions Sources/AblyChat/RoomLifecycleManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ internal actor RoomLifecycleManager<Contributor: RoomLifecycleContributor> {
private struct ContributorAnnotations {
private var storage: [Contributor.ID: ContributorAnnotation]

init(contributors: [Contributor]) {
init(contributors: [Contributor], pendingDiscontinuityEvents: [Contributor.ID: [ARTErrorInfo]]) {
storage = contributors.reduce(into: [:]) { result, contributor in
result[contributor.id] = .init()
result[contributor.id] = .init(pendingDiscontinuityEvents: pendingDiscontinuityEvents[contributor.id] ?? [])
}
}

Expand Down Expand Up @@ -100,6 +100,7 @@ internal actor RoomLifecycleManager<Contributor: RoomLifecycleContributor> {
await self.init(
current: nil,
hasOperationInProgress: nil,
pendingDiscontinuityEvents: [:],
contributors: contributors,
logger: logger,
clock: clock
Expand All @@ -110,13 +111,15 @@ internal actor RoomLifecycleManager<Contributor: RoomLifecycleContributor> {
internal init(
testsOnly_current current: RoomLifecycle? = nil,
testsOnly_hasOperationInProgress hasOperationInProgress: Bool? = nil,
testsOnly_pendingDiscontinuityEvents pendingDiscontinuityEvents: [Contributor.ID: [ARTErrorInfo]]? = nil,
contributors: [Contributor],
logger: InternalLogger,
clock: SimpleClock
) async {
await self.init(
current: current,
hasOperationInProgress: hasOperationInProgress,
pendingDiscontinuityEvents: pendingDiscontinuityEvents,
contributors: contributors,
logger: logger,
clock: clock
Expand All @@ -127,14 +130,15 @@ internal actor RoomLifecycleManager<Contributor: RoomLifecycleContributor> {
private init(
current: RoomLifecycle?,
hasOperationInProgress: Bool?,
pendingDiscontinuityEvents: [Contributor.ID: [ARTErrorInfo]]?,
contributors: [Contributor],
logger: InternalLogger,
clock: SimpleClock
) async {
self.current = current ?? .initialized
self.hasOperationInProgress = hasOperationInProgress ?? false
self.contributors = contributors
contributorAnnotations = .init(contributors: contributors)
contributorAnnotations = .init(contributors: contributors, pendingDiscontinuityEvents: pendingDiscontinuityEvents ?? [:])
self.logger = logger
self.clock = clock

Expand Down Expand Up @@ -362,6 +366,23 @@ internal actor RoomLifecycleManager<Contributor: RoomLifecycleContributor> {
// CHA-RL1g1
changeStatus(to: .attached)
// CHA-RL1g2
await emitPendingDiscontinuityEvents()
}
/// Implements CHA-RL1g2’s emitting of pending discontinuity events.
private func emitPendingDiscontinuityEvents() async {
// Emit all pending discontinuity events
logger.log(message: "Emitting pending discontinuity events", level: .info)
for contributor in contributors {
for pendingDiscontinuityEvent in contributorAnnotations[contributor].pendingDiscontinuityEvents {
logger.log(message: "Emitting pending discontinuity event \(pendingDiscontinuityEvent) to contributor \(contributor)", level: .info)
await contributor.emitDiscontinuity(pendingDiscontinuityEvent)
}
}

contributorAnnotations.clearPendingDiscontinuityEvents()
}

/// Implements CHA-RL1h5’s "detach all channels that are not in the FAILED state".
Expand Down
36 changes: 36 additions & 0 deletions Tests/AblyChatTests/RoomLifecycleManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@ struct RoomLifecycleManagerTests {
private func createManager(
forTestingWhatHappensWhenCurrentlyIn current: RoomLifecycle? = nil,
forTestingWhatHappensWhenHasOperationInProgress hasOperationInProgress: Bool? = nil,
forTestingWhatHappensWhenHasPendingDiscontinuityEvents pendingDiscontinuityEvents: [MockRoomLifecycleContributor.ID: [ARTErrorInfo]]? = nil,
contributors: [MockRoomLifecycleContributor] = [],
clock: SimpleClock = MockSimpleClock()
) async -> RoomLifecycleManager<MockRoomLifecycleContributor> {
await .init(
testsOnly_current: current,
testsOnly_hasOperationInProgress: hasOperationInProgress,
testsOnly_pendingDiscontinuityEvents: pendingDiscontinuityEvents,
contributors: contributors,
logger: TestLogger(),
clock: clock
Expand Down Expand Up @@ -175,6 +177,40 @@ struct RoomLifecycleManagerTests {
try #require(await manager.current == .attached)
}

// @spec CHA-RL1g2
@Test
func attach_uponSuccess_emitsPendingDiscontinuityEvents() async throws {
// Given: A RoomLifecycleManager, all of whose contributors’ calls to `attach` succeed
let contributors = (1 ... 3).map { _ in createContributor(attachBehavior: .complete(.success)) }
let pendingDiscontinuityEvents: [MockRoomLifecycleContributor.ID: [ARTErrorInfo]] = [
contributors[1].id: [.init(domain: "SomeDomain", code: 123) /* arbitrary */ ],
contributors[2].id: [.init(domain: "SomeDomain", code: 456) /* arbitrary */ ],
]
let manager = await createManager(
forTestingWhatHappensWhenHasPendingDiscontinuityEvents: pendingDiscontinuityEvents,
contributors: contributors
)

// When: `performAttachOperation()` is called on the lifecycle manager
try await manager.performAttachOperation()

// Then: It:
// - emits all pending discontinuities to its contributors
// - clears all pending discontinuity events (TODO: I assume this is the intended behaviour, but confirm; have asked in https://github.com/ably/specification/pull/200/files#r1781917231)
for contributor in contributors {
let expectedPendingDiscontinuityEvents = pendingDiscontinuityEvents[contributor.id] ?? []
let emitDiscontinuityArguments = await contributor.emitDiscontinuityArguments
try #require(emitDiscontinuityArguments.count == expectedPendingDiscontinuityEvents.count)
for (emitDiscontinuityArgument, expectedArgument) in zip(emitDiscontinuityArguments, expectedPendingDiscontinuityEvents) {
#expect(emitDiscontinuityArgument === expectedArgument)
}
}

for contributor in contributors {
#expect(await manager.testsOnly_pendingDiscontinuityEvents(for: contributor).isEmpty)
}
}

// @spec CHA-RL1h2
// @specOneOf(1/2) CHA-RL1h1 - tests that an error gets thrown when channel attach fails due to entering SUSPENDED (TODO: but I don’t yet fully understand the meaning of CHA-RL1h1; outstanding question https://github.com/ably/specification/pull/200/files#r1765476610)
// @specPartial CHA-RL1h3 - Have tested the failure of the operation and the error that’s thrown. Have not yet implemented the "enter the recovery loop" (TODO: https://github.com/ably-labs/ably-chat-swift/issues/50)
Expand Down

0 comments on commit c9afd8f

Please sign in to comment.