fix(notifications): always serialize streams so clearing the last subscription saves
#25
@@ -60,8 +60,15 @@ data class NotificationStreamsDto(
|
|||||||
* `GET/PUT /auth/me/notifications/subscriptions` — the user's opted-in stream ids.
|
* `GET/PUT /auth/me/notifications/subscriptions` — the user's opted-in stream ids.
|
||||||
* PUT replaces the full set; unknown ids are dropped server-side and the stored set
|
* PUT replaces the full set; unknown ids are dropped server-side and the stored set
|
||||||
* echoed back.
|
* echoed back.
|
||||||
|
*
|
||||||
|
* [streams] intentionally has NO default: this DTO doubles as the PUT body, and the
|
||||||
|
* backend validator requires the `streams` field (`body('streams').isArray()`).
|
||||||
|
* kotlinx omits a property equal to its default (encodeDefaults=false), so a default
|
||||||
|
* of `emptyList()` would drop the field when the user clears their LAST subscription,
|
||||||
|
* sending `{}` → 400 "Validation failed" (the "can't turn off the last one" bug). With
|
||||||
|
* no default the empty list always serializes as `{"streams":[]}`. Do not re-add a default.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class NotificationSubscriptionsDto(
|
data class NotificationSubscriptionsDto(
|
||||||
val streams: List<String> = emptyList(),
|
val streams: List<String>,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.runicgateway.app.data.api.dto
|
package com.runicgateway.app.data.api.dto
|
||||||
|
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
@@ -58,6 +59,15 @@ class NotificationsDtoTest {
|
|||||||
assertEquals(listOf("news.post", "champ.start"), dto.streams)
|
assertEquals(listOf("news.post", "champ.start"), dto.streams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun emptySubscriptionsStillSerializeStreamsField() {
|
||||||
|
// Regression: clearing the LAST subscription sends an empty set. The backend
|
||||||
|
// validator requires `streams`, so it must be present as `[]`, not omitted.
|
||||||
|
// Uses the production Json config (no encodeDefaults) to prove the field is
|
||||||
|
// always emitted because the DTO field has no default.
|
||||||
|
val body = json.encodeToString(NotificationSubscriptionsDto(emptyList()))
|
||||||
|
assertEquals("""{"streams":[]}""", body)
|
||||||
|
}
|
||||||
|
|
||||||
@Test fun settingsPushBlockDecodes() {
|
@Test fun settingsPushBlockDecodes() {
|
||||||
val dto = json.decodeFromString<SettingsDto>(
|
val dto = json.decodeFromString<SettingsDto>(
|
||||||
"""{"site_title":"Shard","brand":{"name":"Shard"},"push":{"ntfyUrl":"https://ntfy.shard.tld"}}""",
|
"""{"site_title":"Shard","brand":{"name":"Shard"},"push":{"ntfyUrl":"https://ntfy.shard.tld"}}""",
|
||||||
|
|||||||
Reference in New Issue
Block a user