# -*- coding: utf-8 -*- """Web 查询/服务测试共用的临时数据库与配置。""" from __future__ import annotations import json from datetime import date from pathlib import Path from reaction_board.bilibili.models import FavResource from reaction_board.config import FolderConfig from reaction_board.database import Database from reaction_board.repository import Repository from reaction_board.timeutil import day_bounds FOLDER = FolderConfig(year=2026, media_id=3700023351, title="2026") DATES = ("2026-10-29", "2026-10-30", "2026-10-31") def write_config( tmp: str | Path, *, token: str = "", host: str = "127.0.0.1", port: int = 8080, ) -> Path: root = Path(tmp) path = root / "config.json" path.write_text(json.dumps({ "bilibili": { "read": {"mid": 1, "csrf": "read-csrf", "cookie": "SESSDATA=fake-read"}, "write": {"mid": 2, "csrf": "write-csrf", "cookie": "SESSDATA=fake-write"}, }, "telegram": {"enabled": False, "bot_token": "", "chat_id": 0}, "web": { "host": host, "port": port, "auth_token": token, "public_base_url": "", }, "storage": { "database_path": str(root / "app.db"), "backup_dir": str(root / "backup"), "backup_keep_days": 7, }, "folders": [ {"year": 2026, "media_id": 3700023351, "title": "2026"}, ], "track_fans": [37754047], "up_blacklist_seed": [], "schedule": { "discover_minutes": 60, "snapshot_at": "00:01", "candidate_ttl_days": 7, "recent_snapshot_minutes": 60, "recent_video_days": 2, }, }, ensure_ascii=False, indent=2), encoding="utf-8") return path def resource( aid: int, *, up_mid: int, up_name: str, pubdate: int, attr: int = 0, title: str | None = None, ) -> FavResource: return FavResource( aid=aid, bvid=f"BV{aid}", title=title or f"咻咻满 reaction 视频 {aid}", up_mid=up_mid, up_name=up_name, pubdate=pubdate, duration=7200, cover=f"//i0.hdslb.com/{aid}.jpg", attr=attr, fav_time=None, ) def build_db(tmp: str | Path) -> Path: root = Path(tmp) path = root / "app.db" db = Database(path) db.initialize() try: repo = Repository(db) repo.ensure_folder(FOLDER) repo.seed_blacklist([2]) def ts(day: str) -> int: return int(day_bounds(day)[0]) videos = { 101: (1, "UP A", int(day_bounds("2026-10-30")[0]), 0, "2026-10-29"), 102: (1, "UP A", int(day_bounds("2026-10-20")[0]), 0, "2026-10-29"), 103: (2, "UP B", int(day_bounds("2026-10-31")[0]), 0, "2026-10-31"), 104: (3, "UP C", int(day_bounds("2026-09-01")[0]), 9, "2026-10-29"), } for aid, (up_mid, up_name, pubdate, attr, first_day) in videos.items(): repo.upsert_from_fav( resource(aid, up_mid=up_mid, up_name=up_name, pubdate=pubdate, attr=attr), FOLDER.media_id, was_added=True, now=ts(first_day), ) repo.set_video_status(103, "unfaved") # UP 头像与最新粉丝数(UP 列表/详情展示用) repo.apply_up_follower( mid=1, name="UP A", follower=12345, following=5, now=ts("2026-10-30") ) repo.apply_up_follower( mid=2, name="UP B", follower=678, following=1, now=ts("2026-10-31") ) repo.apply_video_up_face(101, "//i0.hdslb.com/bfs/face/a.jpg") repo.apply_video_up_face(103, "//i0.hdslb.com/bfs/face/b.jpg") metrics = { 101: {"view": 1000, "like": 100, "coin": 20, "favorite": 30, "share": 2, "reply": 10, "danmaku": 12}, 102: {"view": 5000, "like": 200, "coin": 50, "favorite": 100, "share": 10, "reply": 30, "danmaku": 40}, 104: {"view": 50, "like": 1, "coin": 0, "favorite": 0, "share": 0, "reply": 0, "danmaku": 0}, } for index, day in enumerate(DATES): for aid in (101, 102): base = metrics[aid] values = {} for key, value in base.items(): values[key] = value + (index * 100 if key == "view" else index * 2) repo.upsert_snapshot( day, aid, metrics=values, ok=True, err=None, captured_at=ts(day) + 60, ) repo.upsert_up_stat( day, 37754047, follower=1000 + index * 10, following=5, ok=True, captured_at=ts(day) + 60, ) # 103 只有最后一天快照,用于验证样本不足;104 另加一条失败快照 repo.upsert_snapshot( DATES[-1], 103, metrics={"view": 120, "like": 5, "coin": 1, "favorite": 2, "share": 0, "reply": 1, "danmaku": 1}, ok=True, err=None, captured_at=ts(DATES[-1]) + 60, ) repo.upsert_snapshot( "2026-10-30", 104, metrics=None, ok=False, err="simulated failure", captured_at=ts("2026-10-30") + 60, ) repo.upsert_candidate( aid=201, bvid="BV201", title="咻咻满 reaction 候选", up_mid=9, up_name="候选UP", pubdate=1_790_000_000, cover=None, score=6, channel="search:test#p1", now=ts("2026-10-30"), play=1000, like=50, ) repo.mark_notified( 201, channel="telegram", batch_id="b1", ok=True, now=ts("2026-10-30") + 10, ) repo.upsert_candidate( aid=202, bvid="BV202", title="咻咻满 切片候选", up_mid=10, up_name="切片UP", pubdate=1_790_000_000, cover=None, score=1, channel="search:test#p1", now=ts("2026-10-29"), ) repo.mark_candidate_decision(202, "rejected", now=ts("2026-10-30")) repo.finish_folder_sync(FOLDER, media_count=3, full_refresh=True) repo.kv_set("snapshot:last_completed", DATES[-1]) for day in DATES: repo.kv_set(f"snapshot:completed:{day}", "1") repo.kv_set("auth:ok", "1") repo.kv_set("auth:message", "OK") repo.kv_set( "task:last:snapshot", json.dumps({ "name": "snapshot", "ok": True, "started_at": ts(DATES[-1]), "finished_at": ts(DATES[-1]) + 60, "error": None, "summary": {"completed": True}, }), ) repo.kv_set( "task:last:fans", json.dumps({ "name": "fans", "ok": True, "started_at": ts(DATES[-1]) + 60, "finished_at": ts(DATES[-1]) + 90, "error": None, "summary": {"fans_ok": 1, "fans_failed": 0}, }), ) repo.kv_set( "task:last:discovery", json.dumps({ "name": "discovery", "ok": True, "started_at": ts(DATES[-1]) - 3600, "finished_at": ts(DATES[-1]) - 3500, "error": None, "summary": {"candidates": 0}, }), ) finally: db.close() return path def day_date(value: str) -> date: return date.fromisoformat(value)