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:
@@ -0,0 +1,127 @@
|
||||
package com.tt.honeyDue.network
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.mock.MockEngine
|
||||
import io.ktor.client.engine.mock.respond
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.headersOf
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* Verifies the residence-invite accept/decline endpoints hit the correct
|
||||
* paths. Wired from [com.tt.honeyDue.notifications.NotificationActionReceiver]
|
||||
* when the user taps the Accept/Decline button on a `residence_invite`
|
||||
* push notification (iOS parity with `NotificationCategories.swift`).
|
||||
*/
|
||||
class ResidenceApiInviteTest {
|
||||
|
||||
private fun mockClient(onRequest: (String) -> Unit): HttpClient =
|
||||
HttpClient(MockEngine) {
|
||||
engine {
|
||||
addHandler { req ->
|
||||
onRequest(req.url.encodedPath)
|
||||
respond(
|
||||
content = "",
|
||||
status = HttpStatusCode.OK,
|
||||
headers = headersOf(HttpHeaders.ContentType, "text/plain")
|
||||
)
|
||||
}
|
||||
}
|
||||
install(ContentNegotiation) {
|
||||
json(Json { ignoreUnknownKeys = true })
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun acceptResidenceInvite_hits_correct_endpoint() = runTest {
|
||||
var method: HttpMethod? = null
|
||||
var path: String? = null
|
||||
val client = HttpClient(MockEngine) {
|
||||
engine {
|
||||
addHandler { req ->
|
||||
method = req.method
|
||||
path = req.url.encodedPath
|
||||
respond(
|
||||
content = "",
|
||||
status = HttpStatusCode.OK,
|
||||
headers = headersOf(HttpHeaders.ContentType, "text/plain")
|
||||
)
|
||||
}
|
||||
}
|
||||
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
|
||||
}
|
||||
val api = ResidenceApi(client)
|
||||
|
||||
val result = api.acceptResidenceInvite("test-token", residenceId = 42)
|
||||
|
||||
assertTrue(result is ApiResult.Success, "expected Success, got $result")
|
||||
assertEquals(HttpMethod.Post, method)
|
||||
assertTrue(
|
||||
path?.endsWith("/residences/42/invite/accept/") == true,
|
||||
"unexpected path: $path"
|
||||
)
|
||||
client.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun declineResidenceInvite_hits_correct_endpoint() = runTest {
|
||||
var method: HttpMethod? = null
|
||||
var path: String? = null
|
||||
val client = HttpClient(MockEngine) {
|
||||
engine {
|
||||
addHandler { req ->
|
||||
method = req.method
|
||||
path = req.url.encodedPath
|
||||
respond(
|
||||
content = "",
|
||||
status = HttpStatusCode.OK,
|
||||
headers = headersOf(HttpHeaders.ContentType, "text/plain")
|
||||
)
|
||||
}
|
||||
}
|
||||
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
|
||||
}
|
||||
val api = ResidenceApi(client)
|
||||
|
||||
val result = api.declineResidenceInvite("test-token", residenceId = 99)
|
||||
|
||||
assertTrue(result is ApiResult.Success, "expected Success, got $result")
|
||||
assertEquals(HttpMethod.Post, method)
|
||||
assertTrue(
|
||||
path?.endsWith("/residences/99/invite/decline/") == true,
|
||||
"unexpected path: $path"
|
||||
)
|
||||
client.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun acceptResidenceInvite_surfaces_server_error() = runTest {
|
||||
val client = HttpClient(MockEngine) {
|
||||
engine {
|
||||
addHandler {
|
||||
respond(
|
||||
content = """{"detail":"Invite not found"}""",
|
||||
status = HttpStatusCode.NotFound,
|
||||
headers = headersOf(HttpHeaders.ContentType, "application/json")
|
||||
)
|
||||
}
|
||||
}
|
||||
install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
|
||||
}
|
||||
val api = ResidenceApi(client)
|
||||
|
||||
val result = api.acceptResidenceInvite("test-token", residenceId = 1)
|
||||
|
||||
assertTrue(result is ApiResult.Error, "expected Error, got $result")
|
||||
assertEquals(404, result.code)
|
||||
client.close()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user