package com.local.ktv; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Test; /** * 歌曲源列表 / 媒体地址允许清单 / 推送默认目标 / 旧默认值迁移的纯 JVM 单测。 * * 覆盖需求点名的几组: * ② 源列表解析(`sourceHosts` 为空时回落默认;**官方源优先、自建源兜底**); * ④ 推送默认目标(`pushServerUrl` 为空时从列表里找自建源,找不到才关闭); * ③ 1.6.1 旧默认顺序(自建源优先)落盘值的一次性迁移。 */ public class SourceHostsTest { // ── ② 源列表解析 ── /** 空(旧配置没有这个键 / 用户清空)→ 回落默认:**官方源第一、自建源兜底**。 */ @Test public void emptySourceListFallsBackToDefaultWithOfficialFirst() { assertEquals(SourceHosts.DEFAULT, SourceHosts.parse(null)); assertEquals(SourceHosts.DEFAULT, SourceHosts.parse("")); assertEquals(SourceHosts.DEFAULT, SourceHosts.parse(" \n \n")); assertEquals(SourceHosts.DEFAULT, SourceHosts.parseList(null)); assertEquals(SourceHosts.DEFAULT, SourceHosts.parseList(Collections.emptyList())); assertEquals("http://zk.ktv1.top:6677", SourceHosts.DEFAULT.get(0)); assertEquals("https://mdmuse.kasugano.cn", SourceHosts.DEFAULT.get(1)); assertTrue("默认列表第一项必须是官方源", SourceHosts.isOfficialSource(SourceHosts.DEFAULT.get(0))); assertFalse("默认列表第二项是自建源兜底", SourceHosts.isOfficialSource(SourceHosts.DEFAULT.get(1))); assertTrue(SourceHosts.isSelfHost(SourceHosts.DEFAULT.get(1))); } /** 设置页"一行一个":去空白/尾斜杠、按顺序去重、忽略非 http(s) 的错行。 */ @Test public void parsesMultilineTextInOrder() { List hosts = SourceHosts.parse( " https://mdmuse.kasugano.cn/ \n" + "\n" + "http://192.168.100.232:6677 \n" + "zk.ktv1.top:6677\n" // 没写协议 → 忽略 + "https://mdmuse.kasugano.cn\n"); // 重复 → 去重 assertEquals(Arrays.asList( "https://mdmuse.kasugano.cn", "http://192.168.100.232:6677"), hosts); // 逗号/分号也当分隔符(便于整条粘贴) assertEquals(2, SourceHosts.parse("https://a.example.com, http://b.example.com").size()); assertEquals(2, SourceHosts.parse("https://a.example.com;http://b.example.com").size()); } /** 官方源优先:官方源放第一 + 自建源兜底,两份实现(JS/原生)都读这个顺序。 */ @Test public void officialFirstThenSelfHostFallback() { List hosts = SourceHosts.parseList(Arrays.asList( "http://zk.ktv1.top:6677", "https://mdmuse.kasugano.cn")); assertTrue(SourceHosts.isOfficialSource(hosts.get(0))); assertFalse(SourceHosts.isOfficialSource(hosts.get(1))); assertTrue(SourceHosts.isSelfHost(hosts.get(1))); // 主机名判定与 scheme/端口无关(内网直连也是自建源) assertTrue(SourceHosts.isSelfHost("http://mdmuse.kasugano.cn:6677")); assertTrue(SourceHosts.isSelfHostName("192.168.100.232")); assertFalse(SourceHosts.isSelfHost(null)); assertFalse(SourceHosts.isSelfHost("")); // 官方源判定:非自建源的非空主机名;空/畸形一律 false("来源不明"绝不上报) assertTrue(SourceHosts.isOfficialSource("http://zk.ktv1.top:6677")); assertTrue(SourceHosts.isOfficialSource("zk.ktv1.top")); assertFalse(SourceHosts.isOfficialSource("")); assertFalse(SourceHosts.isOfficialSource(null)); assertFalse(SourceHosts.isOfficialSource("http://192.168.100.232:6677")); // 内网直连 = 自建源 } // ── ③ 旧默认顺序迁移 ── /** 落盘值恰好等于 1.6.1 的旧默认(自建源优先)→ 迁移成"官方源优先"的新默认。 */ @Test public void legacySelfHostFirstOrderIsMigratedToNewDefault() { List legacy = new ArrayList(SourceHosts.LEGACY_DEFAULT); assertEquals("旧默认必须与 1.6.1 落盘顺序一致", Arrays.asList("https://mdmuse.kasugano.cn", "http://zk.ktv1.top:6677"), legacy); assertEquals(SourceHosts.DEFAULT, SourceHosts.migrateLegacyDefault(legacy)); } /** 用户自定义的列表**一律不动**(哪怕顺序看起来像旧的)、空 → 默认。 */ @Test public void customOrderIsNeverRewritten() { List custom = Arrays.asList("http://192.168.100.232:6677", "http://zk.ktv1.top:6677"); assertEquals(custom, SourceHosts.migrateLegacyDefault(custom)); List selfOnly = Collections.singletonList("https://mdmuse.kasugano.cn"); assertEquals(selfOnly, SourceHosts.migrateLegacyDefault(selfOnly)); assertEquals(SourceHosts.DEFAULT, SourceHosts.migrateLegacyDefault(null)); } // ── 允许清单解析 ── @Test public void allowedMediaHostsDefaultAndNormalization() { assertEquals(SourceHosts.DEFAULT_ALLOWED_MEDIA_HOSTS, SourceHosts.parseAllowed(null)); assertEquals(SourceHosts.DEFAULT_ALLOWED_MEDIA_HOSTS, SourceHosts.parseAllowed(" ")); assertTrue(SourceHosts.DEFAULT_ALLOWED_MEDIA_HOSTS.contains("md.kasugano.cn")); assertTrue(SourceHosts.DEFAULT_ALLOWED_MEDIA_HOSTS.contains("192.168.100.232")); // 整条粘贴 / 大写 / 端口 都要能归一化成主机名 assertEquals(Arrays.asList("md.kasugano.cn", "192.168.100.232"), SourceHosts.parseAllowed("https://MD.Kasugano.cn:4433/media\n192.168.100.232:6677")); assertEquals("md.kasugano.cn", SourceHosts.normalizeAllowedHost(" md.kasugano.cn ")); assertNull(SourceHosts.normalizeAllowedHost(" ")); } // ── ④ 推送默认目标 ── /** 留空 + 列表里有自建源 → 用它(**不要求它排第一**,新默认顺序里官方源才是第一)。 */ @Test public void pushTargetAutoUsesSelfHostAnywhereInList() { assertEquals("https://mdmuse.kasugano.cn", PushDownload.effectiveServerUrl("", SourceHosts.DEFAULT)); assertEquals("http://192.168.100.232:6677", PushDownload.effectiveServerUrl(null, Collections.singletonList("http://192.168.100.232:6677"))); // 空配置也照样能拼出端点 assertEquals("https://mdmuse.kasugano.cn/push_download", PushDownload.endpoint(PushDownload.effectiveServerUrl("", SourceHosts.DEFAULT))); // 自建源不在第一位(新默认就是这样)也要能找到 List officialFirst = Arrays.asList( "http://zk.ktv1.top:6677", "https://mdmuse.kasugano.cn"); assertEquals("https://mdmuse.kasugano.cn", PushDownload.effectiveServerUrl("", officialFirst)); } @Test public void pushTargetRespectsExplicitConfigAndOff() { // 显式覆盖优先(现有用户配过地址的完全不受影响) assertEquals("http://192.168.1.10:6677", PushDownload.effectiveServerUrl(" http://192.168.1.10:6677 ", SourceHosts.DEFAULT)); assertEquals("http://192.168.1.10:6677/push_download", PushDownload.endpoint(PushDownload.effectiveServerUrl("http://192.168.1.10:6677", SourceHosts.DEFAULT))); // 显式关闭:留空已被"自动"占用,所以用 off/none/关闭 明确关掉 for (String off : new String[]{"off", "OFF", "none", "no", "0", "false", "关闭", "禁用"}) { assertEquals("显式关闭必须为空: " + off, "", PushDownload.effectiveServerUrl(off, SourceHosts.DEFAULT)); assertNull("关闭时不能拼出端点", PushDownload.endpoint( PushDownload.effectiveServerUrl(off, SourceHosts.DEFAULT))); } } @Test public void pushTargetStaysOffWhenNoSelfHostSource() { List officialOnly = new ArrayList(); officialOnly.add("http://zk.ktv1.top:6677"); assertEquals("列表里没有自建源 → 不自动开推送", "", PushDownload.effectiveServerUrl("", officialOnly)); assertNull(PushDownload.endpoint(PushDownload.effectiveServerUrl("", officialOnly))); assertEquals("没有任何源 → 关闭", "", PushDownload.effectiveServerUrl("", Collections.emptyList())); // 配置非法(漏写协议)仍按关闭处理(沿用旧口径) assertNull(PushDownload.endpoint(PushDownload.effectiveServerUrl("192.168.1.10:6677", SourceHosts.DEFAULT))); } }