import json import tempfile import unittest from pathlib import Path from app.bettergi_daily import ( DAILY_MODE_COMMISSION, DAILY_MODE_DOMAIN, DAILY_MODE_LEY_LINE, DAILY_MODE_NONE, DailyAutomationError, DomainAliasResolver, apply_json_updates, parse_daily_request, prepare_commission_current_party_update, prepare_daily_run, prepare_edit_party_update, prepare_switch_party_update, resolve_party_members, ) class BetterGIDailyTestCase(unittest.TestCase): def setUp(self): self.temp_dir = tempfile.TemporaryDirectory() self.root = Path(self.temp_dir.name) self.alias_path = self.root / "domain_aliases.json" self._write_json( self.alias_path, { "_说明": "test", "铭记之谷": ["风本", "少女套"], }, ) self._write_json( self.root / "User" / "OneDragon" / "默认配置.json", { "Name": "默认配置", "TaskEnabledList": { "mail-template-id": True, "serenitea-template-id": True, "craft-template-id": True, "domain-template-id": False, "ley-line-template-id": False, "daily-reward-template-id": True, }, "TaskOrder": [ "mail-template-id", "serenitea-template-id", "craft-template-id", "domain-template-id", "ley-line-template-id", "daily-reward-template-id", ], "TaskDefinitions": { "mail-template-id": "领取邮件", "serenitea-template-id": "领取尘歌壶奖励", "craft-template-id": "合成树脂", "domain-template-id": "自动秘境", "ley-line-template-id": "自动地脉花", "daily-reward-template-id": "领取每日奖励", }, "CraftingBenchCountry": "枫丹", "AdventurersGuildCountry": "璃月", "PartyName": "战斗队", "DailyRewardPartyName": "好感队", "SecretTreasureObjects": ["须臾树脂"], "WeeklyDomainEnabled": True, "DomainName": "旧秘境", "CompletionAction": "关机", }, ) self._write_json( self.root / "User" / "config.json", { "marker": "preserved", "autoFightConfig": {"strategyName": "策略A"}, "autoDomainConfig": { "specifyResinUse": True, "other": 1, }, "autoLeyLineOutcropConfig": { "isGoToSynthesizer": True, "team": "战斗队", "friendshipTeam": "", "fightConfig": {"strategyName": "策略A"}, }, }, ) strategy = self.root / "User" / "AutoFight" / "策略A.txt" strategy.parent.mkdir(parents=True, exist_ok=True) strategy.write_text("战斗策略", encoding="utf-8") self._write_json( self.root / "User" / "JsScript" / "AutoDomain" / "settings.json", [ { "name": "domainName", "type": "select", "options": ["铭记之谷", "仲夏庭园"], } ], ) self._write_json( self.root / "User" / "ScriptGroup" / "每日委托.json", {"name": "每日委托", "projects": [{"name": "委托", "status": "Enabled"}]}, ) self._write_json( self.root / "User" / "ScriptGroup" / "切换队伍.json", { "name": "切换队伍", "projects": [ { "folderName": "AcceleratedEditionSwitchParty", "status": "Enabled", "jsScriptSettingsObject": {"partyName": "旧队伍", "debug": True}, } ], }, ) self._write_json( self.root / "User" / "ScriptGroup" / "修改队员.json", { "name": "修改队员", "projects": [ { "folderName": "AutoSwitchRoles", "status": "Enabled", "jsScriptSettingsObject": {"position1": ""}, } ], }, ) options = [ "水-单手剑-芙宁娜", "冰-长枪-爱可菲", "草-法器-纳西妲", "雷-长枪-雷电将军", "岩-长枪-钟离", ] self._write_json( self.root / "User" / "JsScript" / "AutoSwitchRoles" / "settings.json", [ {"name": f"position{index}", "type": "select", "options": ["", *options]} for index in range(1, 5) ], ) def tearDown(self): self.temp_dir.cleanup() @staticmethod def _write_json(path: Path, data): path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") def _prepare( self, argument: str, *, craft_before: bool = True, commission_use_current_party: bool = False, ): return prepare_daily_run( self.root, self.alias_path, argument, template_name="默认配置", managed_name="直播系统自动每日", ley_line_craft_resin_before=craft_before, commission_use_current_party=commission_use_current_party, ) def test_parse_daily_modes(self): self.assertEqual(parse_daily_request("").mode, DAILY_MODE_NONE) domain = parse_daily_request("秘境 风本") self.assertEqual((domain.mode, domain.domain_name), (DAILY_MODE_DOMAIN, "风本")) ley_line = parse_daily_request("地脉 经验 蒙德") self.assertEqual( (ley_line.mode, ley_line.ley_line_type, ley_line.ley_line_country), (DAILY_MODE_LEY_LINE, "启示之花", "蒙德"), ) self.assertEqual(parse_daily_request("委托").mode, DAILY_MODE_COMMISSION) def test_parse_rejects_missing_or_unknown_arguments(self): with self.assertRaisesRegex(DailyAutomationError, "需要指定秘境"): parse_daily_request("秘境") with self.assertRaisesRegex(DailyAutomationError, "地脉模式格式"): parse_daily_request("地脉 经验") with self.assertRaisesRegex(DailyAutomationError, "不支持的地脉国家"): parse_daily_request("地脉 摩拉 天空岛") with self.assertRaisesRegex(DailyAutomationError, "仅支持"): parse_daily_request("探索") def test_domain_alias_resolves_canonical_and_common_names(self): resolver = DomainAliasResolver(self.alias_path, self.root) self.assertEqual(resolver.resolve("风本"), "铭记之谷") self.assertEqual(resolver.resolve("少女套"), "铭记之谷") self.assertEqual(resolver.resolve("仲夏庭园"), "仲夏庭园") def test_domain_alias_collision_is_rejected(self): self._write_json( self.alias_path, { "铭记之谷": ["风本"], "仲夏庭园": ["风 本"], }, ) with self.assertRaisesRegex(DailyAutomationError, "同时指向"): DomainAliasResolver(self.alias_path, self.root).resolve("风本") def test_domain_run_generates_exact_flow_and_corrects_bgi_config(self): prepared = self._prepare("秘境 风本") self.assertEqual(prepared.request.domain_name, "铭记之谷") self.assertEqual(prepared.task_name, "自动每日(秘境:铭记之谷)") updates = {update.path.name: update for update in prepared.updates} corrected = updates["config.json"].data self.assertFalse(corrected["autoDomainConfig"]["specifyResinUse"]) self.assertEqual(corrected["autoDomainConfig"]["other"], 1) self.assertEqual(corrected["marker"], "preserved") managed = updates["直播系统自动每日.json"].data names = [managed["TaskDefinitions"][task_id] for task_id in managed["TaskOrder"]] self.assertEqual( names, ["领取邮件", "合成树脂", "自动秘境", "领取尘歌壶奖励", "领取每日奖励"], ) self.assertEqual( managed["TaskOrder"], [ "mail-template-id", "craft-template-id", "domain-template-id", "serenitea-template-id", "daily-reward-template-id", ], ) self.assertFalse(managed["WeeklyDomainEnabled"]) self.assertEqual(managed["DomainName"], "铭记之谷") self.assertEqual(managed["CompletionAction"], "无") self.assertEqual(managed["PartyName"], "战斗队") def test_no_mode_skips_only_the_other_task(self): prepared = self._prepare("") managed = prepared.updates[-1].data names = [managed["TaskDefinitions"][task_id] for task_id in managed["TaskOrder"]] self.assertEqual( names, ["领取邮件", "合成树脂", "领取尘歌壶奖励", "领取每日奖励"], ) def test_ley_line_run_sets_all_days_and_respects_craft_toggle(self): prepared = self._prepare("地脉 摩拉 枫丹", craft_before=False) updates = {update.path.name: update for update in prepared.updates} corrected = updates["config.json"].data self.assertFalse(corrected["autoLeyLineOutcropConfig"]["isGoToSynthesizer"]) managed = updates["直播系统自动每日.json"].data names = [managed["TaskDefinitions"][task_id] for task_id in managed["TaskOrder"]] self.assertEqual( names, ["领取邮件", "自动地脉花", "领取尘歌壶奖励", "领取每日奖励"], ) self.assertTrue(managed["LeyLineResinExhaustionMode"]) self.assertFalse(managed["LeyLineOpenModeCountMin"]) for day in ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"): self.assertTrue(managed[f"LeyLineRun{day}"]) self.assertEqual(managed[f"LeyLine{day}Type"], "藏金之花") self.assertEqual(managed[f"LeyLine{day}Country"], "枫丹") def test_commission_is_added_as_a_configuration_group_task(self): prepared = self._prepare("委托") managed = prepared.updates[-1].data names = [managed["TaskDefinitions"][task_id] for task_id in managed["TaskOrder"]] self.assertEqual( names, ["领取邮件", "合成树脂", "每日委托", "领取尘歌壶奖励", "领取每日奖励"], ) commission_id = next( task_id for task_id, name in managed["TaskDefinitions"].items() if name == "每日委托" ) self.assertNotIn( commission_id, { "mail-template-id", "craft-template-id", "serenitea-template-id", "daily-reward-template-id", }, ) def test_commission_requires_nonempty_group(self): (self.root / "User" / "ScriptGroup" / "每日委托.json").unlink() with self.assertRaisesRegex(DailyAutomationError, "每日委托配置组"): self._prepare("委托") def test_auto_commission_nova_requires_complete_first_run_config(self): self._write_json( self.root / "User" / "ScriptGroup" / "每日委托.json", { "name": "每日委托", "projects": [ { "folderName": "AutoCommissionNova", "status": "Enabled", } ], }, ) with self.assertRaisesRegex(DailyAutomationError, "缺少 Data/user-config.json"): self._prepare("委托") user_config_path = ( self.root / "User" / "JsScript" / "AutoCommissionNova" / "Data" / "user-config.json" ) self._write_json( user_config_path, { "party": { "global": { "battleTeamName": "战斗队", "elementTeamName": "", "battleStrategy": "策略A", } } }, ) with self.assertRaisesRegex(DailyAutomationError, "元素采集队伍"): self._prepare("委托") self._write_json( user_config_path, { "party": { "global": { "battleTeamName": "战斗队", "elementTeamName": "采集队", "battleStrategy": "策略A", } } }, ) self.assertEqual(self._prepare("委托").request.mode, DAILY_MODE_COMMISSION) def test_auto_commission_nova_can_inject_current_party_for_battle_and_collection(self): self._write_json( self.root / "User" / "ScriptGroup" / "每日委托.json", { "name": "每日委托", "projects": [ { "folderName": "AutoCommissionNova", "status": "Enabled", } ], }, ) user_config_path = ( self.root / "User" / "JsScript" / "AutoCommissionNova" / "Data" / "user-config.json" ) self._write_json( user_config_path, { "marker": "preserved", "party": { "global": { "battleTeamName": "", "elementTeamName": "", "battleStrategy": "策略A", } }, }, ) prepared = self._prepare("委托", commission_use_current_party=True) self.assertTrue(prepared.requires_current_party) update = prepare_commission_current_party_update(self.root, "当前队伍") self.assertEqual( update.data["party"]["global"]["battleTeamName"], "当前队伍", ) self.assertEqual( update.data["party"]["global"]["elementTeamName"], "当前队伍", ) self.assertEqual(update.data["marker"], "preserved") def test_switch_party_updates_only_target_setting(self): update = prepare_switch_party_update(self.root, "深渊队") project = update.data["projects"][0] self.assertEqual(project["jsScriptSettingsObject"]["partyName"], "深渊队") self.assertTrue(project["jsScriptSettingsObject"]["debug"]) def test_edit_party_accepts_separators_and_contiguous_names(self): separated, display = resolve_party_members( self.root, "芙宁娜 爱可菲 纳西妲 雷电将军", ) self.assertEqual(display, ["芙宁娜", "爱可菲", "纳西妲", "雷电将军"]) contiguous, contiguous_display = resolve_party_members( self.root, "芙宁娜爱可菲纳西妲雷电将军", ) self.assertEqual(contiguous, separated) self.assertEqual(contiguous_display, display) update, names = prepare_edit_party_update( self.root, "芙宁娜、爱可菲、纳西妲、雷电将军", ) settings = update.data["projects"][0]["jsScriptSettingsObject"] self.assertEqual(names, ("芙宁娜", "爱可菲", "纳西妲", "雷电将军")) self.assertEqual(settings["position4"], "雷-长枪-雷电将军") def test_edit_party_supports_auto_switch_roles_67_character_data(self): self._write_json( self.root / "User" / "JsScript" / "AutoSwitchRoles" / "settings.json", [ {"name": f"position{index}", "type": "input-text", "default": ""} for index in range(1, 5) ], ) self._write_json( self.root / "User" / "JsScript" / "AutoSwitchRoles" / "combat_avatar.json", [ {"name": "神里绫华", "alias": ["绫华"]}, {"name": "申鹤", "alias": []}, {"name": "枫原万叶", "alias": ["万叶"]}, {"name": "珊瑚宫心海", "alias": ["心海"]}, ], ) update, names = prepare_edit_party_update( self.root, "绫华 申鹤 万叶 心海", ) settings = update.data["projects"][0]["jsScriptSettingsObject"] self.assertEqual(names, ("神里绫华", "申鹤", "枫原万叶", "珊瑚宫心海")) self.assertEqual( [settings[f"position{index}"] for index in range(1, 5)], ["神里绫华", "申鹤", "枫原万叶", "珊瑚宫心海"], ) resolved, display = resolve_party_members( self.root, "神里绫华申鹤枫原万叶珊瑚宫心海", ) self.assertEqual(resolved, list(display)) def test_edit_party_rejects_ambiguous_combat_avatar_alias(self): self._write_json( self.root / "User" / "JsScript" / "AutoSwitchRoles" / "settings.json", [ {"name": f"position{index}", "type": "input-text", "default": ""} for index in range(1, 5) ], ) self._write_json( self.root / "User" / "JsScript" / "AutoSwitchRoles" / "combat_avatar.json", [ {"name": "角色甲", "alias": ["同名"]}, {"name": "角色乙", "alias": ["同名"]}, {"name": "角色丙", "alias": []}, {"name": "角色丁", "alias": []}, {"name": "角色戊", "alias": []}, ], ) with self.assertRaisesRegex(DailyAutomationError, "未知或有歧义"): resolve_party_members(self.root, "同名 角色丙 角色丁 角色戊") def test_edit_party_requires_four_distinct_known_members(self): with self.assertRaisesRegex(DailyAutomationError, "必须是4人"): resolve_party_members(self.root, "芙宁娜 爱可菲 纳西妲") with self.assertRaisesRegex(DailyAutomationError, "不能重复"): resolve_party_members(self.root, "芙宁娜 芙宁娜 纳西妲 雷电将军") with self.assertRaisesRegex(DailyAutomationError, "未知"): resolve_party_members(self.root, "芙宁娜 爱可菲 纳西妲 不存在") def test_apply_json_updates_writes_managed_files(self): prepared = self._prepare("秘境 风本") apply_json_updates(prepared.updates) bgi_config = json.loads((self.root / "User" / "config.json").read_text(encoding="utf-8")) managed = json.loads( (self.root / "User" / "OneDragon" / "直播系统自动每日.json").read_text(encoding="utf-8") ) self.assertFalse(bgi_config["autoDomainConfig"]["specifyResinUse"]) self.assertEqual(managed["DomainName"], "铭记之谷") if __name__ == "__main__": unittest.main()