Files
MediaCrawler/tests/test_monitor_creators.py
T
butubb 0a88474c92
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s
feat(monitor): 给博主起备注 —— 作品栏里才认得出「这是谁」
作品栏按 creator_hash 把作品归到博主的组里,可那是个哈希;creator_name 是平台昵称,
粉丝少的号常常认不出。两样都没法把账号对上人。

* 新表 monitor_creator_alias,键取 **(platform, creator_hash)** 而不是按任务:
  哈希对同一个 uid 是稳定的,所以同一个博主出现在多个任务里时,备注只填一次。
* list_notes 带上 creator_alias(整体查一次再在内存里取,不是每条作品查一次)。
* `PUT /monitor/creators/{creator_hash}`,空串即清掉那条备注。
* 作品栏的博主组头:**优先显示备注**,起过备注之后平台昵称降成副标题(它仍是有用的
  对照);组头上一个铅笔按钮就地编辑,回车保存、失焦保存、Esc 取消。

Esc 那条要单独处理:取消之后紧接着的失焦会把刚放弃的内容存进去,所以用一个标记让那次
失焦闭嘴。

测试 +6:没起过时是空串、起了会跟着作品返回、**跨任务共用一条**、**不串到别的平台**、
空串清掉、前后空格会被去掉。
2026-10-10 18:00:13 +08:00

125 lines
4.3 KiB
Python

# -*- coding: utf-8 -*-
"""博主备注 —— 作品栏里那一位到底是谁。
按 creator_hash 分组、显示 creator_name,两样都认不出人:一个是哈希,一个是平台昵称。
备注是人自己起的名字。
"""
import httpx
import pytest
import pytest_asyncio
from api.main import app
from api.monitor import db as monitor_db
from api.monitor.models import MODE_CREATOR, MonitorNote, MonitorTask
CREATOR_HASH = "hash-a"
NICKNAME = "张三"
@pytest_asyncio.fixture
async def client(tmp_path):
monitor_db.set_sqlite_path(tmp_path / "monitor.db")
await monitor_db.init_db()
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://test") as http_client:
yield http_client
await monitor_db.dispose_engine()
async def _seed(platform: str = "xhs", task_name: str = "任务") -> int:
"""一个任务 + 一条作品,博主固定用 CREATOR_HASH / NICKNAME。"""
async with monitor_db.get_session() as session:
task = MonitorTask(
name=task_name, platform=platform, mode=MODE_CREATOR, enabled=True,
interval_minutes=60, max_notes_count=20, enable_comments=False,
max_comments_count=50, run_timeout_seconds=3600,
notify_enabled=False, created_at=0, updated_at=0,
)
session.add(task)
await session.flush()
session.add(
MonitorNote(
task_id=task.id, note_id=f"{platform}-n1", title="作品",
note_url="", cover="", creator_hash=CREATOR_HASH,
creator_name=NICKNAME, source_kind="", published_at=None,
first_seen_run_id=1, first_seen_at=0,
last_seen_run_id=1, last_seen_at=0,
)
)
return task.id
async def _notes(client, platform: str = "xhs"):
return (await client.get("/api/monitor/notes", params={"platform": platform})).json()["notes"]
async def _set_alias(client, alias: str, platform: str = "xhs", creator_hash=CREATOR_HASH):
return await client.put(
f"/api/monitor/creators/{creator_hash}",
params={"platform": platform},
json={"alias": alias},
)
class TestCreatorAlias:
@pytest.mark.asyncio
async def test_notes_start_without_an_alias(self, client):
await _seed()
assert (await _notes(client))[0]["creator_alias"] == ""
@pytest.mark.asyncio
async def test_an_alias_comes_back_with_the_notes(self, client):
await _seed()
response = await _set_alias(client, "竞品A")
assert response.status_code == 200
note = (await _notes(client))[0]
assert note["creator_alias"] == "竞品A"
# 备注是**叠加**在昵称之上的,不是替换 —— 昵称仍然是有用的对照。
assert note["creator_name"] == NICKNAME
@pytest.mark.asyncio
async def test_an_alias_is_shared_across_tasks(self, client):
"""同一个博主出现在两个任务里,备注只该填一次。
creator_hash 对同一个 uid 是稳定的,所以键取 (platform, creator_hash) 而不是
按任务存 —— 否则每加一个任务都要重新认一遍人。
"""
await _seed(task_name="任务甲")
await _seed(task_name="任务乙")
await _set_alias(client, "竞品A")
for note in await _notes(client):
assert note["creator_alias"] == "竞品A"
@pytest.mark.asyncio
async def test_the_alias_does_not_leak_to_another_platform(self, client):
"""同一个哈希在另一个平台上是另一个(或同一个)人 —— 别串味。"""
await _seed(platform="xhs")
await _seed(platform="dy")
await _set_alias(client, "小红书那边的", platform="xhs")
assert (await _notes(client, "xhs"))[0]["creator_alias"] == "小红书那边的"
assert (await _notes(client, "dy"))[0]["creator_alias"] == ""
@pytest.mark.asyncio
async def test_an_empty_alias_clears_it(self, client):
await _seed()
await _set_alias(client, "竞品A")
await _set_alias(client, "")
assert (await _notes(client))[0]["creator_alias"] == ""
@pytest.mark.asyncio
async def test_an_alias_is_trimmed(self, client):
await _seed()
await _set_alias(client, " 竞品A ")
assert (await _notes(client))[0]["creator_alias"] == "竞品A"