tests/test_resource_identification.py
Rama master · réplica de lectura del repositorio original.
"""无番号视频识别清单与候选合并的隔离回归。"""
import json
import sqlite3
import tempfile
import unittest
from pathlib import Path
from peach.resource_identification import (
CANDIDATE_FILENAME,
build_worklist,
ingest_results,
performer_guess,
query_variants,
)
from peach.review_csv import read_rows, write_rows
from support.ledger import fresh_ledger
class QueryVariantTests(unittest.TestCase):
def test_chinese_performer_prefix_stays_verbatim_and_guessable(self):
name = "梓怡-背著老公和合租室友的狂歡 長相乖巧,被大肉棒瘋狂抽插.mp4"
variants = query_variants(name)
self.assertEqual(variants[0], "梓怡-背著老公和合租室友的狂歡 長相乖巧,被大肉棒瘋狂抽插")
self.assertIn("梓怡 背著老公和合租室友的狂歡 長相乖巧,被大肉棒瘋狂抽插", variants)
self.assertEqual(performer_guess(name), "梓怡")
def test_unspaced_english_filename_gains_word_bounds(self):
variants = query_variants("YouGetFootjobStandingFromStarttoFinish9.mp4")
self.assertIn("You Get Footjob Standing From Startto Finish 9", variants)
self.assertEqual(performer_guess("YouGetFootjobStandingFromStarttoFinish9.mp4"), "")
def test_watermarks_and_quality_tails_are_stripped_in_variants(self):
variants = query_variants("www.98t.la@MIAD-573-uncensored-HD.mp4")
self.assertEqual(variants[0], "www.98t.la@MIAD-573-uncensored-HD")
self.assertIn("MIAD-573-uncensored-HD", variants)
self.assertIn("MIAD 573", variants)
def test_jav_code_stems_are_not_performer_guesses(self):
self.assertEqual(performer_guess("259LUXU-1055.mp4"), "")
self.assertEqual(performer_guess("ABW-153.mp4"), "")
class ResourceWorklistTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name).resolve()
self.db_path = fresh_ledger(self.root)
def add(self, asset_id, location, path, medium, size=1000, duration=None,
code=None, disposal=None):
connection = sqlite3.connect(self.db_path)
try:
connection.execute(
"INSERT INTO asset(id,location,path,name,medium,size,duration,code,disposal) "
"VALUES(?,?,?,?,?,?,?,?,?)",
(asset_id, location, path, path.rsplit("\\", 1)[-1], medium,
size, duration, code, disposal),
)
connection.commit()
finally:
connection.close()
def test_worklist_pairs_a_cover_and_skips_code_assets(self):
base = r"B:\xxr\0208 (23)"
self.add(1, "115", rf"{base}\梓怡-背著老公.mp4", "video",
985 * 1024**2, 1647)
self.add(2, "115", rf"{base}\梓怡-背著老公.png", "image", 2 * 1024**2)
self.add(3, "115", rf"{base}\ABW-153.mp4", "video",
500 * 1024**2, 3600, code="ABW-153")
connection = sqlite3.connect(self.db_path)
connection.row_factory = sqlite3.Row
try:
rows = build_worklist(connection, locations=("115",), prefix=base)
finally:
connection.close()
self.assertEqual([row["asset_id"] for row in rows], [1])
self.assertEqual(rows[0]["cover_path"], rf"{base}\梓怡-背著老公.png")
self.assertEqual(rows[0]["performer_guess"], "梓怡")
self.assertIn("梓怡", rows[0]["query_variants"])
def test_paired_only_keeps_videos_with_a_sibling_image(self):
self.add(1, "115", r"B:\xxr\a\有图.mp4", "video", 100 * 1024**2, 600)
self.add(2, "115", r"B:\xxr\a\有图.jpg", "image", 1024)
self.add(3, "115", r"B:\xxr\b\无图.mp4", "video", 100 * 1024**2, 600)
connection = sqlite3.connect(self.db_path)
connection.row_factory = sqlite3.Row
try:
rows = build_worklist(connection, locations=("115",), prefix=r"B:\xxr",
paired_only=True)
finally:
connection.close()
self.assertEqual([row["asset_id"] for row in rows], [1])
def test_sparse_only_keeps_assets_without_any_metadata(self):
self.add(1, "115", r"B:\xxr\a\已有标题.mp4", "video", 100 * 1024**2, 600)
self.add(2, "115", r"B:\xxr\b\没有元信息.mp4", "video", 100 * 1024**2, 600)
connection = sqlite3.connect(self.db_path)
connection.row_factory = sqlite3.Row
try:
with connection:
connection.execute(
"UPDATE asset SET catalog_title='已有标题' WHERE id=1")
rows = build_worklist(connection, locations=("115",), prefix=r"B:\xxr",
sparse_only=True)
all_rows = build_worklist(connection, locations=("115",), prefix=r"B:\xxr")
finally:
connection.close()
self.assertEqual([row["asset_id"] for row in rows], [2])
self.assertEqual(rows[0]["metadata_hits"], 0)
self.assertEqual([row["asset_id"] for row in all_rows], [2, 1])
self.assertEqual(all_rows[1]["metadata_hits"], 1)
class ResourceIngestTests(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory()
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name).resolve()
self.db_path = fresh_ledger(self.root)
self.candidates = self.root / "generated" / CANDIDATE_FILENAME
self.candidates.parent.mkdir(parents=True)
connection = sqlite3.connect(self.db_path)
try:
connection.execute(
"INSERT INTO asset(id,location,path,name,medium,size,duration) "
"VALUES(1,'115',?,?,'video',?,1647)",
(r"B:\xxr\0208 (23)\梓怡-背著老公.mp4", "梓怡-背著老公.mp4", 985 * 1024**2),
)
connection.execute(
"INSERT INTO asset(id,location,path,name,medium,size,duration,code) "
"VALUES(2,'115',?,?,'video',?,3600,'ABW-153')",
(r"B:\xxr\0208 (23)\ABW-153.mp4", "ABW-153.mp4", 500 * 1024**2),
)
connection.commit()
finally:
connection.close()
def write_existing_local_candidate(self):
write_rows(self.candidates, (
"item_key", "code", "query", "asset_id", "asset_path", "field", "field_label",
"current_value", "candidates_json", "source_count", "source_profile",
"policy_version", "status", "size_gb", "videos", "fetched_at",
), [{
"item_key": "asset:1:title", "code": "", "query": "梓怡-背著老公.mp4",
"asset_id": 1, "asset_path": r"B:\xxr\0208 (23)\梓怡-背著老公.mp4",
"field": "title", "field_label": "标题", "current_value": "",
"candidates_json": json.dumps([{
"candidate_key": "local_nfo:abc", "source": "local_nfo",
"value": "旧标题", "display_value": "旧标题", "confidence": 0.9,
}], ensure_ascii=False),
"source_count": 1, "source_profile": "library", "policy_version": "library-v1",
"status": "candidate", "size_gb": 0.96, "videos": 1, "fetched_at": "",
}], atomic=True)
RESULTS = [
{"asset_id": "1", "field": "title",
"value": "背着老公和合租室友的狂欢", "source_url": "https://av911.tv/video/113896",
"confidence": "0.7", "note": "麻豆传媒作品页"},
{"asset_id": "1", "field": "performers", "value": "梓怡",
"source_url": "https://av911.tv/video/113896", "confidence": "0.7", "note": ""},
{"asset_id": "1", "field": "studio", "value": "麻豆传媒",
"source_url": "https://av911.tv/video/113896", "confidence": "0.6", "note": ""},
{"asset_id": "2", "field": "title", "value": "不会写入",
"source_url": "", "confidence": "0.5", "note": ""},
]
def test_results_merge_with_existing_sources_and_stay_idempotent(self):
self.write_existing_local_candidate()
connection = sqlite3.connect(self.db_path)
connection.row_factory = sqlite3.Row
try:
stats = ingest_results(connection, self.candidates, self.RESULTS)
finally:
connection.close()
self.assertEqual(stats["applied"], 3)
self.assertEqual(stats["skipped"], 1)
groups = {row["item_key"]: row for row in read_rows(self.candidates)}
title_choices = json.loads(groups["asset:1:title"]["candidates_json"])
self.assertEqual({choice["source"] for choice in title_choices},
{"local_nfo", "websearch"})
performer = json.loads(groups["asset:1:performers"]["candidates_json"])[0]
self.assertEqual(performer["value"], [{"name": "梓怡"}])
self.assertEqual(performer["display_value"], "梓怡")
self.assertEqual(groups["asset:1:performers"]["asset_path"],
r"B:\xxr\0208 (23)\梓怡-背著老公.mp4")
connection = sqlite3.connect(self.db_path)
connection.row_factory = sqlite3.Row
try:
ingest_results(connection, self.candidates, self.RESULTS)
finally:
connection.close()
groups = {row["item_key"]: row for row in read_rows(self.candidates)}
self.assertEqual(len(json.loads(groups["asset:1:title"]["candidates_json"])), 2)
def test_invalid_values_are_skipped(self):
connection = sqlite3.connect(self.db_path)
connection.row_factory = sqlite3.Row
try:
stats = ingest_results(connection, self.candidates, [
{"asset_id": "1", "field": "release_date", "value": "2026/01/01"},
{"asset_id": "1", "field": "tags", "value": "有码"},
{"asset_id": "1", "field": "title", "value": " "},
{"asset_id": "404", "field": "title", "value": "不存在"},
])
finally:
connection.close()
self.assertEqual(stats["applied"], 0)
self.assertEqual(stats["skipped"], 4)
if __name__ == "__main__":
unittest.main()