# -*- coding: utf-8 -*- """P1 纯函数测试:`名字_日期_编号` 解析(不碰 DB、不碰用户数据)。""" import io import os import sys sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') # 仓库根 = scripts/tests/ 往上三级(不写死绝对路径,换机器也能跑) _ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.insert(0, _ROOT) os.chdir(_ROOT) from core import video_plan as vp # noqa: E402 fails = [] def check(label, got, want): ok = got == want if not ok: fails.append(label) print((' ✅ ' if ok else ' ❌ ') + label + ('' if ok else '\n 得到 %r\n 期望 %r' % (got, want))) print('=== parse_video_name:合法 ===') check('名字_日期_编号', vp.parse_video_name('小王_20260912_1.mp4')[0], {"name": "小王", "date": "2026-09-12", "seq": 1, "seq_given": True}) check('名字_日期(无编号)', vp.parse_video_name('建材王总_20260913.mp4')[0], {"name": "建材王总", "date": "2026-09-13", "seq": None, "seq_given": False}) check('名字里带数字', vp.parse_video_name('翻斗花园我最强2号_20260913.mp4')[0]['name'], '翻斗花园我最强2号') check('名字里带空格/表情', vp.parse_video_name('有牛奶面包 你吃吗?_20260912_2.mp4')[0]['name'], '有牛奶面包 你吃吗?') check('名字里带表情', vp.parse_video_name('🐟小鱼_20260912.mp4')[0]['name'], '🐟小鱼') check('大写扩展名', vp.parse_video_name('小王_20260912.MP4')[0]['date'], '2026-09-12') check('编号 3 位', vp.parse_video_name('小王_20260912_999.mp4')[0]['seq'], 999) print('=== parse_video_name:图片(图文)也认 ===') check('jpg', vp.parse_video_name('小王_20260912_1.jpg')[0], {"name": "小王", "date": "2026-09-12", "seq": 1, "seq_given": True}) check('png(无编号)', vp.parse_video_name('建材王总_20260913.png')[0], {"name": "建材王总", "date": "2026-09-13", "seq": None, "seq_given": False}) check('大写 JPG', vp.parse_video_name('小王_20260912.JPG')[0]['name'], '小王') check('webp', vp.parse_video_name('小王_20260912.webp')[0]['date'], '2026-09-12') check('gif 不收(没进白名单)', '不是视频/图片' in (vp.parse_video_name('小王_20260912.gif')[1] or ''), True) print('=== parse_video_name:拒收 ===') def err_of(fn): return fn[1] check('名字带下划线', err_of(vp.parse_video_name('小_王_20260912.mp4')), '名字里不能有下划线:`小_王_20260912` 里日期前面有 2 段 ——应为 名字_日期_编号') check('名字是纯数字', err_of(vp.parse_video_name('12345678_20260912.mp4')), '名字不能是纯数字(12345678 会被当成日期/编号)—— 请填抖音上的昵称') check('结尾不是日期', err_of(vp.parse_video_name('小王_20260912_1234.mp4')), '文件名格式不对:`小王_20260912_1234` 的结尾既不是日期(8 位数字,如 20260912),' '也不是「日期_编号」—— 应为 名字_日期_编号') check('老格式(手机号在前)', err_of(vp.parse_video_name('13382508423_20261011_01.mp4')), '这一段(13382508423)是手机号 —— 文件名第一段要填**抖音上的名字**,' '格式是 名字_日期_编号(现在不认手机号了)') check('非法日期', err_of(vp.parse_video_name('小王_20260230.mp4')), '日期 20260230 不是合法日期(应为 20260912 这种 8 位)') check('只有一段', err_of(vp.parse_video_name('小王.mp4')), '文件名格式不对,应为 名字_日期_编号(编号可省略,如 小王_20260912.mp4)') check('不是视频/图片', err_of(vp.parse_video_name('小王_20260912.txt')), '不是视频/图片文件(支持 mp4/mov/mkv/avi/3gp/flv/wmv/m4v/ts/webm/jpg/jpeg/png/webp/heic)') check('空文件名', err_of(vp.parse_video_name('')), '文件名为空') check('多段(名字含下划线还带日期)', err_of(vp.parse_video_name('小_王_总_20260912.mp4')), '名字里不能有下划线:`小_王_总_20260912` 里日期前面有 3 段 ——应为 名字_日期_编号') print('=== parse_title_line:合法 ===') check('标题 || 正文_名字_日期_编号', vp.parse_title_line('中秋祝福 || 祝大家中秋快乐_小王_20260912_1')[0], {"title": "中秋祝福", "body": "祝大家中秋快乐", "name": "小王", "date": "2026-09-12", "seq": 1, "seq_given": True}) check('只有标题', vp.parse_title_line('中秋祝福_小王_20260912')[0], {"title": "中秋祝福", "body": "", "name": "小王", "date": "2026-09-12", "seq": None, "seq_given": False}) check('只有正文(|| 在最前)', vp.parse_title_line(' || 只有正文_小王_20260912')[0]['body'], '只有正文') check('标题里带下划线', vp.parse_title_line('名字带_下划线也行_小王_20260912')[0]['title'], '名字带_下划线也行') check('#话题开头的标题', vp.parse_title_line('#中秋快乐_小王_20260929')[0]['title'], '#中秋快乐') check('正文里再有 || 原样保留', vp.parse_title_line('标题 || 正文 || 又一处_小王_20260912')[0]['body'], '正文 || 又一处') check('标题是纯数字', vp.parse_title_line('2026_小王_20260912_1')[0]['title'], '2026') print('=== parse_title_line:拒收 ===') check('只有名字/日期(没文字)', err_of(vp.parse_title_line('小王_20260912')), '缺标题/正文内容(只有名字/日期/编号,没有文字)') check('名字/日期/编号(没文字)', err_of(vp.parse_title_line('小王_20260912_1')), '缺标题/正文内容(只有名字/日期/编号,没有文字)') check('老写法(名字在前)', err_of(vp.parse_title_line('19286456013_20260912_1_标题')), "格式不对,应为「标题 || 正文_名字_日期_编号」(编号可省略;日期是 8 位数字,如 20260912)") check('名字是手机号', err_of(vp.parse_title_line('标题_13382508423_20260912')), '这一段(13382508423)是手机号 —— 文件名第一段要填**抖音上的名字**,' '格式是 名字_日期_编号(现在不认手机号了)') check('标题里带下划线(名字取紧挨日期那段)', vp.parse_title_line('标题_小_王_20260912')[0], {"title": "标题_小", "body": "", "name": "王", "date": "2026-09-12", "seq": None, "seq_given": False}) check('空行', err_of(vp.parse_title_line(' ')), '空行') print('=== parse_titles_text:注释与多行 ===') rows, errors = vp.parse_titles_text( '标题一_小王_20260912\n# 这是注释\n\n#中秋快乐_小王_20260913\n' '#真注释_没有日期\n标题没有日期') check('收下 2 条标题', [r['title'] for r in rows], ['标题一', '#中秋快乐']) check('# 认不出名字/日期 → 当注释忽略', [(e['line'], e['raw']) for e in errors], [(6, '标题没有日期')]) print('=== pair_slots:按名字配对 ===') videos = [dict(vp.parse_video_name('小王_20260912_1.mp4')[0], ref='v1'), dict(vp.parse_video_name('小王_20260912_2.mp4')[0], ref='v2'), dict(vp.parse_video_name('小李_20260912.mp4')[0], ref='v3')] titles = [dict(vp.parse_title_line('标题A_小王_20260912')[0], ref='t1'), dict(vp.parse_title_line('标题B_小王_20260912')[0], ref='t2'), dict(vp.parse_title_line('标题C_小李_20260912')[0], ref='t3')] pairs, rejected = vp.pair_slots(videos, titles) check('配成 3 对', len(pairs), 3) check('没有拒收', rejected, []) check('按 (日期,名字,编号) 排序', [(p['date'], p['name'], p['seq']) for p in pairs], [('2026-09-12', '小李', 1), ('2026-09-12', '小王', 1), ('2026-09-12', '小王', 2)]) check('每对都配上标题', sorted(p['title']['title'] for p in pairs), ['标题A', '标题B', '标题C']) check('配对结果里的键是 name', all('name' in p and 'phone' not in p for p in pairs), True) print() if fails: print('❌ 失败 %d 项:%s' % (len(fails), ' / '.join(fails))) sys.exit(1) print('✅ 全部通过')