作品栏按 creator_hash 把作品归到博主的组里,可那是个哈希;creator_name 是平台昵称,
粉丝少的号常常认不出。两样都没法把账号对上人。
* 新表 monitor_creator_alias,键取 **(platform, creator_hash)** 而不是按任务:
哈希对同一个 uid 是稳定的,所以同一个博主出现在多个任务里时,备注只填一次。
* list_notes 带上 creator_alias(整体查一次再在内存里取,不是每条作品查一次)。
* `PUT /monitor/creators/{creator_hash}`,空串即清掉那条备注。
* 作品栏的博主组头:**优先显示备注**,起过备注之后平台昵称降成副标题(它仍是有用的
对照);组头上一个铅笔按钮就地编辑,回车保存、失焦保存、Esc 取消。
Esc 那条要单独处理:取消之后紧接着的失焦会把刚放弃的内容存进去,所以用一个标记让那次
失焦闭嘴。
测试 +6:没起过时是空串、起了会跟着作品返回、**跨任务共用一条**、**不串到别的平台**、
空串清掉、前后空格会被去掉。
125 lines
4.3 KiB
Python
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"
|