feat(enums): 接入 /meta/enums 权威枚举表,删掉客户端硬编码映射
客户端此前自带 platform / mood / mode 三张硬编码标签表,会与服务端漂移—— 实测已经漂了:客户端把 `web` 显示成「Web」,权威表里是「网页」。 - data:MetaApi(GET /api/v1/meta/enums)+ MetaDto + MetaRepository, 经 ApiClientFactory 创建以复用既有 token / 401 处理 - domain/enums/EnumCatalog.kt:标签查找器,**不含任何硬编码兜底表**; 不在表内的取值一律原样返回(api-client §1.1 的未知值回落红线); platformLabelOrNull 供 deviceId 前缀这类「认识/不认识要走不同回落」的调用方用; 空 id 或空 label 一律不入表(否则角标会显示空白,或查空串凭空借到别人的标签) - core/prefs/EnumPreferences.kt:last-good 快照,baseUrl+username 指纹闸门, 换服务器/账号即作废,登出清空 - EnumCatalogViewModel + LocalEnumCatalog:在 CyreneRoot 注入(与 LocalAppPrefs 同套路), 聊天角标与管理页共读同一份 - 删除 platformLabels / personaModeLabels / moodLabels;MessageSource 与 AdminFormat 改为查表,并保留各自原有的原样回落 为什么不留硬编码兜底表:没有登录就看不到任何消息,而权威表在登录前后即会拉取, 所以 last-good 快照几乎总是存在;留着硬编码表等于把「漂移」重新引进来,而且是无声的—— 旧标签和新鲜标签长得一模一样。 不转换的:trace kind/status、memory scope、thinking.mode、WS session_busy 的 mode 集合—— 它们都不在 /meta/enums 内,且原本就已按原样回落。 测试:115 全绿(88 → +27:MetaDtoRealPayload 8 / EnumCatalog 7 / EnumPrefs 3 / MessageSource 8,AdminFormatTest 改写并 +1),其中 /meta/enums 与登录响应一样 用真实抓取的响应体做夹具。
This commit is contained in:
1 parent
9f054aad28
commit
09a3222b7b
17 files changed
+693
-48
No files matched your search
@@ -6,6 +6,9 @@ import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import top.yeij.cyrene.data.api.dto.EnumItemDto
|
||||
import top.yeij.cyrene.data.api.dto.EnumTableDto
|
||||
import top.yeij.cyrene.domain.enums.EnumCatalog
|
||||
import top.yeij.cyrene.domain.parse.ContentPart
|
||||
|
||||
/**
|
||||
@@ -18,6 +21,16 @@ class AdminFormatTest {
|
||||
|
||||
private val timestamp = Regex("""\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}""")
|
||||
|
||||
/** `/meta/enums` 的权威表由调用方注入:这里只登记各表的一部分,用来验证「查表」而非「本地表」。 */
|
||||
private val enumCatalog = EnumCatalog.of(
|
||||
EnumTableDto(
|
||||
platforms = listOf(EnumItemDto("web", "网页")),
|
||||
moods = listOf(EnumItemDto("calm", "平静")),
|
||||
modes = listOf(EnumItemDto("chat", "对话")),
|
||||
unknown = "display-raw",
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `offset and zulu spellings of the same instant agree`() {
|
||||
assertEquals(
|
||||
@@ -91,12 +104,23 @@ class AdminFormatTest {
|
||||
assertEquals("quantum_hop", traceKindLabel("quantum_hop"))
|
||||
assertEquals("成功", traceStatusLabel("ok"))
|
||||
assertEquals("degraded", traceStatusLabel("degraded"))
|
||||
assertEquals("对话", personaModeLabel("chat"))
|
||||
assertEquals("telepathy", personaModeLabel("telepathy"))
|
||||
assertEquals("对话", personaModeLabel("chat", enumCatalog))
|
||||
assertEquals("telepathy", personaModeLabel("telepathy", enumCatalog))
|
||||
assertEquals("人格", memoryScopeLabel("persona"))
|
||||
assertEquals("shared", memoryScopeLabel("shared"))
|
||||
assertEquals("平静", moodLabel("calm"))
|
||||
assertEquals("euphoric", moodLabel("euphoric"))
|
||||
assertEquals("平静", moodLabel("calm", enumCatalog))
|
||||
assertEquals("euphoric", moodLabel("euphoric", enumCatalog))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mode and mood are looked up in the injected table, not in a client-side copy`() {
|
||||
// 注入表里只有 chat / calm:同为真实取值的 code / happy 没有表项,
|
||||
// 因此必须原样显示——本地硬编码表会翻成「代码」「开心」,这条断言就是那处漂移的钉子。
|
||||
assertEquals("code", personaModeLabel("code", enumCatalog))
|
||||
assertEquals("happy", moodLabel("happy", enumCatalog))
|
||||
// 没有表(未登录 / 尚无快照)时同样原样显示,不回落任何客户端硬编码。
|
||||
assertEquals("chat", personaModeLabel("chat", EnumCatalog.Empty))
|
||||
assertEquals("calm", moodLabel("calm", EnumCatalog.Empty))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package top.yeij.cyrene.domain.enums
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import top.yeij.cyrene.data.api.dto.EnumItemDto
|
||||
import top.yeij.cyrene.data.api.dto.EnumTableDto
|
||||
|
||||
/**
|
||||
* 标签查找的未知值回落(api-client §1.1 红线):表里有就翻译,表里没有就**原样返回**——
|
||||
* 不丢弃、不报错、不替换占位符、不猜测。
|
||||
*/
|
||||
class EnumCatalogTest {
|
||||
|
||||
private val catalog = EnumCatalog.of(
|
||||
EnumTableDto(
|
||||
platforms = listOf(EnumItemDto("web", "网页"), EnumItemDto("telegram", "Telegram")),
|
||||
moods = listOf(EnumItemDto("calm", "平静")),
|
||||
modes = listOf(EnumItemDto("chat", "对话")),
|
||||
unknown = "display-raw",
|
||||
),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `known ids resolve to the authoritative labels`() {
|
||||
assertEquals("网页", catalog.platformLabel("web"))
|
||||
assertEquals("Telegram", catalog.platformLabel("telegram"))
|
||||
assertEquals("平静", catalog.moodLabel("calm"))
|
||||
assertEquals("对话", catalog.modeLabel("chat"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unknown ids are returned verbatim, never dropped or replaced`() {
|
||||
assertEquals("mastodon", catalog.platformLabel("mastodon"))
|
||||
assertEquals("euphoric", catalog.moodLabel("euphoric"))
|
||||
assertEquals("telepathy", catalog.modeLabel("telepathy"))
|
||||
// 判定只比对 id 且大小写敏感:`WEB` 不是表里的 `web`,不得归一化后当命中。
|
||||
assertEquals("WEB", catalog.platformLabel("WEB"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a blank value is not looked up and stays blank`() {
|
||||
assertEquals("", catalog.platformLabel(""))
|
||||
assertEquals("", catalog.moodLabel(""))
|
||||
assertEquals("", catalog.modeLabel(""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty catalog degrades everything to raw ids`() {
|
||||
assertEquals("web", EnumCatalog.Empty.platformLabel("web"))
|
||||
assertEquals("calm", EnumCatalog.Empty.moodLabel("calm"))
|
||||
assertEquals("chat", EnumCatalog.Empty.modeLabel("chat"))
|
||||
assertEquals("web", EnumCatalog.of(null).platformLabel("web"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tables not present in a partial payload fall back to raw ids`() {
|
||||
val platformsOnly = EnumCatalog.of(EnumTableDto(platforms = listOf(EnumItemDto("web", "网页"))))
|
||||
|
||||
assertEquals("网页", platformsOnly.platformLabel("web"))
|
||||
assertEquals("calm", platformsOnly.moodLabel("calm"))
|
||||
assertEquals("chat", platformsOnly.modeLabel("chat"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `items with a blank id or blank label are not registered`() {
|
||||
val table = EnumCatalog.of(
|
||||
EnumTableDto(
|
||||
platforms = listOf(
|
||||
EnumItemDto("", "空 id"),
|
||||
EnumItemDto("web", ""),
|
||||
EnumItemDto("kook", "KOOK"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// 空 id 不得让空查询命中;空 label 不得把角标显示成空白。
|
||||
assertEquals("", table.platformLabel(""))
|
||||
assertEquals("web", table.platformLabel("web"))
|
||||
assertEquals("KOOK", table.platformLabel("kook"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `platformLabelOrNull separates known from unknown without substituting`() {
|
||||
assertEquals("网页", catalog.platformLabelOrNull("web"))
|
||||
assertNull(catalog.platformLabelOrNull("mastodon"))
|
||||
assertNull(EnumCatalog.Empty.platformLabelOrNull("web"))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user