Files
douyin-pc/src/test_onboarding_ui.py
T

378 lines
13 KiB
Python

"""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()