This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
openclaw-skills/skills/obsidian-kb/test_outputs.py
T
OpenClaw Agent 08f7c9f26c feat: integrate bilibili-extractor into obsidian-kb
- Add extractors layer with BilibiliExtractor (Playwright stealth mode)
- Add outputs layer with ObsidianWriter and GitSync
- Implement subtitle/no-subtitle graceful degradation
- Update config with output and bilibili extraction settings
- Complete integration pipeline: extract → process → output → sync
- Add comprehensive tests (100% pass rate)
- Update documentation (SKILL.md, README.md, requirements.txt)
2026-02-07 13:07:43 +08:00

318 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
测试输出器功能
"""
import sys
import os
from pathlib import Path
# 添加项目路径到 sys.path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from src.outputs import ObsidianWriter, GitSync
from src.models import Content, ContentType, ContentStatus, TimelineEntry, ContentAnalysis
from src.config import Config
def create_test_content() -> Content:
"""创建测试数据"""
content = Content(
id="test001",
source="bilibili",
source_id="BV1234567890",
url="https://www.bilibili.com/video/BV1234567890",
title="Python 高级编程教程 - 装饰器深入解析",
author="编程大师",
description="深入讲解 Python 装饰器的原理和应用",
duration=1800, # 30分钟
content_type=ContentType.VIDEO,
status=ContentStatus.GENERATED,
transcript="""欢迎来到本期的 Python 高级编程教程。
今天我们要深入探讨装饰器的原理和应用。
首先,让我们理解装饰器的基本概念...
[更多字幕内容...]""",
summary="本教程深入讲解了 Python 装饰器的原理,包括闭包、函数装饰器、类装饰器等核心概念,并通过实际案例展示了装饰器在日志、缓存、权限验证等场景中的应用。",
one_liner="30分钟深入理解 Python 装饰器,从原理到实战",
key_points=[
"装饰器本质上是高阶函数",
"闭包是装饰器实现的基础",
"@语法糖只是语法糖",
"装饰器可以带参数",
"类装饰器也是常见模式",
],
tags=["Python", "装饰器", "高级编程", "教程"],
quality_score=85.5,
outline=[
"什么是装饰器",
"装饰器的原理",
"函数装饰器",
"带参数的装饰器",
"类装饰器",
"装饰器应用场景",
"实战案例",
],
timeline=[
TimelineEntry(
timestamp="00:00",
title="课程介绍",
description="介绍本课程的主要内容和学习目标"
),
TimelineEntry(
timestamp="02:30",
title="装饰器基础",
description="讲解装饰器的基本概念和语法"
),
TimelineEntry(
timestamp="10:00",
title="装饰器原理",
description="深入理解装饰器背后的实现原理"
),
TimelineEntry(
timestamp="20:00",
title="实战案例",
description="通过实际代码演示装饰器的应用"
),
],
category="编程",
)
# 添加详细分析
content.analysis = ContentAnalysis(
one_liner="30分钟深入理解 Python 装饰器,从原理到实战",
summary="本教程深入讲解了 Python 装饰器的原理,包括闭包、函数装饰器、类装饰器等核心概念。",
key_points=[
"装饰器本质上是高阶函数",
"闭包是装饰器实现的基础",
"@语法糖只是语法糖",
],
tags=["Python", "装饰器"],
quality_score=85.5,
prerequisites=["Python 基础语法", "函数基础", "面向对象基础"],
outline=[
{"title": "装饰器基础", "content": "什么是装饰器,基本语法"},
{"title": "实现原理", "content": "闭包的作用,装饰器的执行流程"},
{"title": "高级用法", "content": "带参数的装饰器,类装饰器"},
{"title": "实际应用", "content": "日志记录、性能测试、权限验证"},
],
pitfalls=[
"忘记保留原函数的元数据(如 __name__, __doc__)",
"装饰器中修改参数可能导致意外行为",
"多层装饰器的执行顺序容易混淆",
],
practice_advice=[
"多动手实践,编写不同类型的装饰器",
"使用 functools.wraps 保留函数元数据",
"在复杂场景下考虑使用类装饰器",
],
)
return content
def create_test_content_no_subtitle() -> Content:
"""创建无字幕的测试数据"""
content = Content(
id="test002",
source="bilibili",
source_id="BV0987654321",
url="https://www.bilibili.com/video/BV0987654321",
title="无字幕测试视频",
author="测试UP主",
description="这是一个没有字幕的测试视频",
duration=300, # 5分钟
content_type=ContentType.VIDEO,
status=ContentStatus.GENERATED,
transcript="", # 空字幕
summary="这是一个测试视频,用于验证无字幕时的输出",
one_liner="测试无字幕视频的输出功能",
key_points=[
"测试要点1",
"测试要点2",
],
tags=["测试"],
quality_score=60.0,
)
return content
def test_obsidian_writer():
"""测试 ObsidianWriter"""
print("=" * 60)
print("测试 ObsidianWriter")
print("=" * 60)
# 创建测试配置
config = Config()
# 使用临时测试目录
test_vault = Path("/tmp/test_obsidian_vault")
if test_vault.exists():
import shutil
shutil.rmtree(test_vault)
test_vault.mkdir(parents=True, exist_ok=True)
# 修改配置使用测试 vault
config._config['obsidian']['vault_path'] = str(test_vault)
# 创建写入器
writer = ObsidianWriter(config)
# 测试1: 有字幕的内容
print("\n测试1: 写入有字幕的内容")
content1 = create_test_content()
file_path1 = writer.write(content1, config)
if file_path1:
print(f"✓ 成功写入: {file_path1}")
# 检查文件是否存在
if Path(file_path1).exists():
print(f"✓ 文件存在")
# 读取并显示前20行
print("\n文件内容预览(前20行):")
with open(file_path1, 'r', encoding='utf-8') as f:
lines = f.readlines()[:20]
for i, line in enumerate(lines, 1):
print(f"{i:2d}: {line.rstrip()}")
else:
print(f"✗ 文件不存在")
else:
print("✗ 写入失败")
# 测试2: 无字幕的内容
print("\n" + "-" * 60)
print("测试2: 写入无字幕的内容")
content2 = create_test_content_no_subtitle()
file_path2 = writer.write(content2, config)
if file_path2:
print(f"✓ 成功写入: {file_path2}")
# 检查文件是否存在
if Path(file_path2).exists():
print(f"✓ 文件存在")
# 读取并显示最后几行
print("\n文件内容预览(最后10行):")
with open(file_path2, 'r', encoding='utf-8') as f:
lines = f.readlines()
for line in lines[-10:]:
print(f" {line.rstrip()}")
else:
print(f"✗ 文件不存在")
else:
print("✗ 写入失败")
# 检查目录结构
print("\n" + "-" * 60)
print("目录结构:")
print(f"{test_vault}/")
for root, dirs, files in os.walk(test_vault):
level = root.replace(str(test_vault), '').count(os.sep)
indent = ' ' * 2 * level
print(f"{indent}{os.path.basename(root)}/")
subindent = ' ' * 2 * (level + 1)
for file in files:
print(f"{subindent}{file}")
# 清理测试目录
print("\n" + "-" * 60)
print(f"测试目录: {test_vault}")
print("测试完成后可手动删除测试目录")
return file_path1 is not None and file_path2 is not None
def test_git_sync():
"""测试 GitSync"""
print("\n" + "=" * 60)
print("测试 GitSync")
print("=" * 60)
# 创建测试 vault
test_vault = Path("/tmp/test_git_vault")
if test_vault.exists():
import shutil
shutil.rmtree(test_vault)
test_vault.mkdir(parents=True, exist_ok=True)
# 初始化 Git 仓库
try:
subprocess_result = os.system(f"cd {test_vault} && git init && git config user.email 'test@example.com' && git config user.name 'Test User' > /dev/null 2>&1")
print(f"✓ Git 仓库初始化成功")
except:
print("✗ Git 初始化失败,跳过 GitSync 测试")
return False
# 创建测试配置
config = Config()
config._config['obsidian']['vault_path'] = str(test_vault)
config._config['git']['enabled'] = True
config._config['git']['auto_push'] = False # 不测试 push,避免需要远程仓库
# 创建 Git 同步器
sync = GitSync(config)
# 测试1: 检查 Git 状态
print("\n测试1: 检查是否为 Git 仓库")
is_repo = sync._is_git_repo()
print(f"✓ 是否为 Git 仓库: {is_repo}")
# 测试2: 创建测试文件并同步
print("\n测试2: 创建文件并同步")
test_file = test_vault / "test.md"
test_file.write_text("# 测试文件\n\n这是一个测试文件。")
result = sync.sync(config)
print(f"✓ 同步结果: {result}")
# 测试3: 检查 Git 状态
print("\n测试3: 检查 Git 状态")
os.system(f"cd {test_vault} && git log --oneline -n 3")
print("\n" + "-" * 60)
print(f"测试目录: {test_vault}")
print("测试完成后可手动删除测试目录")
return result
def main():
"""主测试函数"""
print("\n" + "=" * 60)
print("Obsidian-KB 输出器测试")
print("=" * 60)
results = {}
# 测试 ObsidianWriter
results['obsidian_writer'] = test_obsidian_writer()
# 测试 GitSync
results['git_sync'] = test_git_sync()
# 汇总结果
print("\n" + "=" * 60)
print("测试结果汇总")
print("=" * 60)
for test_name, result in results.items():
status = "✓ 通过" if result else "✗ 失败"
print(f"{test_name}: {status}")
# 总体结果
all_passed = all(results.values())
print("\n" + "=" * 60)
if all_passed:
print("✓ 所有测试通过!")
else:
print("✗ 部分测试失败")
print("=" * 60)
return all_passed
if __name__ == "__main__":
success = main()
sys.exit(0 if success else 1)