378 lines
13 KiB
Python
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()
|