Integration: residence invite accept/decline APIs + wire notification actions

Adds acceptResidenceInvite / declineResidenceInvite to ResidenceApi
(POST /api/residences/{id}/invite/{accept|decline}) and exposes them via
APILayer. On accept success, myResidences is force-refreshed so the
newly-joined residence appears without a manual pull.

Wires NotificationActionReceiver's ACCEPT_INVITE / DECLINE_INVITE
handlers to the new APILayer calls, replacing the log-only TODOs left
behind by P4 Stream O. Notifications are now cleared only on API
success so a failed accept stays actionable.

Tests:
 - ResidenceApiInviteTest covers correct HTTP method/path + error surfacing.
 - NotificationActionReceiverTest invite cases updated to assert the new
   APILayer calls (were previously asserting the log-only path).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 13:36:59 -05:00
parent 10b57aabaa
commit 485f70dfa1
5 changed files with 227 additions and 28 deletions

View File

@@ -243,12 +243,14 @@ class NotificationActionReceiverTest {
scope.cancel()
}
// ---------- 5. ACCEPT_INVITE: clears notification (TODO: API call) ----------
// ---------- 5. ACCEPT_INVITE: calls APILayer + clears notification ----------
@Test
fun acceptInvite_withResidenceId_cancelsNotification() = runTest {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Unconfined)
coEvery { APILayer.acceptResidenceInvite(any()) } returns ApiResult.Success(Unit)
val notifId = 9005
postDummyNotification(notifId)
@@ -259,10 +261,7 @@ class NotificationActionReceiverTest {
}
receiverFor(scope).onReceive(context, intent)
// API method does not yet exist — see TODO in receiver. Expectation is
// that the notification is still cleared (best-effort UX) and we did
// NOT crash. APILayer.createTaskCompletion should NOT have been called.
coVerify(exactly = 0) { APILayer.createTaskCompletion(any()) }
coVerify(exactly = 1) { APILayer.acceptResidenceInvite(101) }
assertFalse(
"invite notification should be cleared on accept",
notificationManager.activeNotifications.any { it.id == notifId }
@@ -343,6 +342,8 @@ class NotificationActionReceiverTest {
fun declineInvite_withResidenceId_cancelsNotification() = runTest {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Unconfined)
coEvery { APILayer.declineResidenceInvite(any()) } returns ApiResult.Success(Unit)
val notifId = 9009
postDummyNotification(notifId)
@@ -353,6 +354,7 @@ class NotificationActionReceiverTest {
}
receiverFor(scope).onReceive(context, intent)
coVerify(exactly = 1) { APILayer.declineResidenceInvite(77) }
assertFalse(
"invite notification should be cleared on decline",
notificationManager.activeNotifications.any { it.id == notifId }