"""Offline Chinese onboarding dialog regression; no Chrome or network access.""" import json import os import tempfile import unittest from pathlib import Path from unittest.mock import patch os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QApplication, QDialog, QLineEdit, QMessageBox, ) from account_store import DEFAULT_RULE from accounts_app import Backend, Window from get_current_user import parse_user_response class OnboardingUiTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.app = QApplication.instance() or QApplication([]) def setUp(self): self.temp = tempfile.TemporaryDirectory() self.backend = Backend(Path(self.temp.name)) self.window = Window(self.backend) def tearDown(self): self.window.allow_close = True self.window.close() self.temp.cleanup() def test_both_roles_only_ask_for_name(self): def fill(dialog): inputs = dialog.findChildren(QLineEdit) self.assertEqual(len(inputs), 1, "UID must not be a form input") inputs[0].setText("测试账号名称") return QDialog.DialogCode.Accepted for role in ("main", "worker"): with ( patch.object(QDialog, "exec", fill), patch.object(self.window, "send") as send, ): self.window.add(role) send.assert_called_once_with( "add", {"role": role, "name": "测试账号名称", "owner": None} ) def test_profile_and_zero_statistics_are_displayed(self): account = { "id": "a" * 32, "name": "业务名称", "role": "main", "owner": None, "uid": None, "nickname": "", "avatar": "", "profile": "{}", "rule": json.dumps(DEFAULT_RULE), "login": "待登录", "identity": "等待首次登录", "state": "等待登录", } snapshot = { "accounts": [account], "tasks": [], "counts": {}, "selected": [], "active": [], "browser": {}, "browser_overrides": {}, } self.window.refresh(snapshot) card = self.window.card_table.cards[account["id"]] self.assertIn("待登录自动获取", card.meta_label.text()) self.assertIn("等待登录", card.status_label.text()) account.update( uid="301", nickname="平台昵称", profile=json.dumps( { "follower_count": 0, "following_count": 12, "total_favorited": 123, "aweme_count": 4, } ), ) self.window.refresh(snapshot) card = self.window.card_table.cards[account["id"]] self.assertIn("平台昵称", card.meta_label.text()) for text in ("粉丝 0", "关注 12", "获赞 123", "作品 4"): self.assertIn(text, self.window.profile_summary.text()) def test_account_tree_expands_and_collapses_workers(self): main = { "id": "m" * 32, "name": "主账号", "role": "main", "owner": None, "uid": "100", "nickname": "主号名称", "avatar": "", "profile": "{}", "rule": json.dumps(DEFAULT_RULE), "login": "已登录", "identity": "已绑定", "state": "未启动", } worker = dict(main) worker.update( id="w" * 32, name="小号别名", role="worker", owner=main["id"], uid="200", nickname="小号名称", ) snapshot = { "accounts": [main, worker], "tasks": [], "counts": {}, "selected": [], "active": [], "browser": {}, "browser_overrides": {}, } self.window.refresh(snapshot) node = self.window.card_table.nodes[main["id"]] self.assertTrue(node.expanded) node.card.toggle_button.click() self.assertFalse(node.expanded) node.card.toggle_button.click() self.assertTrue(node.expanded) self.assertEqual( self.window.card_table.cards[worker["id"]].meta_label.text(), "名称:小号名称", ) def test_log_stream_is_incremental_and_capped(self): self.backend.logs_ready.emit([f"entry-{i}" for i in range(2502)]) self.assertEqual(self.window.log.document().blockCount(), 2000) self.assertTrue(self.window.log.toPlainText().startswith("entry-502\n")) self.backend.logs_ready.emit(["latest"]) self.assertEqual(self.window.log.document().blockCount(), 2000) self.assertTrue(self.window.log.toPlainText().endswith("latest")) self.assertEqual(self.window.log.document().maximumBlockCount(), 2000) def test_worker_delete_requires_explicit_confirmation(self): with ( patch.object( self.window, "selected", return_value={"id": "worker", "name": "测试小号", "role": "worker"}, ), patch.object(self.window, "send") as send, ): with patch.object( QMessageBox, "question", return_value=QMessageBox.StandardButton.No ) as question: self.window.delete_worker() send.assert_not_called() self.assertEqual( question.call_args.args[-1], QMessageBox.StandardButton.No ) with patch.object( QMessageBox, "question", return_value=QMessageBox.StandardButton.Yes ): self.window.delete_worker() send.assert_called_once_with("delete_worker", {"id": "worker"}) with ( patch.object( self.window, "selected", return_value={"id": "main", "role": "main"} ), patch.object(QMessageBox, "information"), patch.object(self.window, "send") as send, ): self.window.delete_worker() send.assert_not_called() def test_history_and_works_buttons_require_bound_main(self): main = {"id": "main", "role": "main", "uid": "101"} with ( patch.object(self.window, "selected", return_value=main), patch.object(self.window, "send") as send, ): self.window.history_events() self.window.fetch_works() self.assertEqual( [call.args for call in send.call_args_list], [ ("history_fetch", {"id": "main"}), ("works_open", {"id": "main"}), ], ) def test_history_preview_requires_selection_and_explicit_confirmation(self): payload = { "account": "main", "baseline": "rule-and-works-fingerprint", "items": [ { "nid": "9007199254740993", "create_time": 123, "kind": "comment", "work_id": "700", "work_desc": "完整作品描述", "actor_uids": ["999"], "actor_names": ["完整昵称"], "comment": "完整评论正文", "origin": "history", "business": { "nid_str": "9007199254740993", "comment": "完整评论正文", }, } ], } with ( patch.object(QMessageBox, "information"), patch.object( QMessageBox, "question", return_value=QMessageBox.StandardButton.Yes ), patch.object(self.window, "send") as send, ): self.window.history_dialog(payload) table = self.window.history_table content = table.item(0, 6) assert content is not None self.assertIn("完整评论正文", content.text()) values = [] for column in range(table.columnCount()): cell = table.item(0, column) assert cell is not None values.append(cell.text()) self.assertNotIn("{", " ".join(values)) check = table.item(0, 0) assert check is not None check.setCheckState(Qt.CheckState.Checked) self.window.confirm_history() send.assert_called_once_with( "history_enqueue", { "id": "main", "ids": ["9007199254740993"], "baseline": "rule-and-works-fingerprint", }, ) def test_works_preview_saves_selected_mode_and_full_business_data(self): payload = { "account": "main", "mode": "selected", "selected": [], "refresh_interval": 3600, "items": [ { "aweme_id": "700", "desc": "完整作品描述", "create_time": 123, "statistics": {"collect_count": 8}, "cover": "https://example.invalid/full.jpg", "updated": 456, "business": {"aweme_id": "700", "desc": "完整作品描述"}, } ], } with ( patch.object( self.window, "selected", return_value={"id": "main", "role": "main"}, ), patch.object(self.window, "send") as send, ): self.window.works_dialog(payload) table = self.window.works_table description = table.item(0, 2) assert description is not None self.assertIn("完整作品描述", description.text()) values = [] for column in range(table.columnCount()): cell = table.item(0, column) assert cell is not None values.append(cell.text()) self.assertNotIn("{", " ".join(values)) check = table.item(0, 0) assert check is not None check.setCheckState(Qt.CheckState.Checked) self.window.works_all_mode.setChecked(False) self.window.save_work_filter() send.assert_called_once_with( "work_filter", { "id": "main", "mode": "selected", "ids": ["700"], "refresh_interval": 3600, }, ) def test_lists_scroll_and_copy_rows(self): self.window.resize(1260, 860) self.window.show() items = [ { "aweme_id": str(i), "desc": f"作品 {i}", "create_time": 123, "statistics": {}, "cover": "", "updated": 456, } for i in range(30) ] self.window.works_dialog( { "account": "main", "items": items, "selected": [], "mode": "selected", "refresh_interval": 3600, } ) self.app.processEvents() self.assertGreater(self.window.works_card_area.verticalScrollBar().maximum(), 0) tasks = [ { "id": str(i), "event": "通知", "source": "大号", "worker": "小号", "target": f"用户{i}", "action": "follow", "status": "pending", "result": "", "created_at": 123, "started_at": 123, "finished_at": 123, } for i in range(100) ] self.window._fill_task_table(self.window.current_task_table, tasks, {}, True) self.app.processEvents() self.assertGreater(self.window.current_task_table.verticalScrollBar().maximum(), 0) self.window.copy_table_row(self.window.current_task_table, 0) copied = QApplication.clipboard().text() self.assertIn("选择: 未选择", copied) self.assertIn("任务: 0", copied) self.window._copy_text(self.window._work_row_text(items[0])) self.assertIn("作品ID: 0", QApplication.clipboard().text()) def test_anonymous_profile_is_never_bound(self): for user in ({}, {"uid": "0"}, {"uid": True}, {"uid": "not-a-uid"}): with self.assertRaises(RuntimeError): parse_user_response( { "status": 200, "body": json.dumps({"status_code": 0, "user": user}), } ) if __name__ == "__main__": unittest.main()