Initial commit: Video Library Manager
- Add core VLM modules (scanner, parser, planner, executor, analysis) - Add CLI with quarantine, reports, rollback, and state management - Add comprehensive test suite - Add project configuration and documentation - Add .gitignore for Python project
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
"""Integration tests for CLI report commands.
|
||||
|
||||
Tests the report commands to ensure they properly wire to the Report Generator.
|
||||
"""
|
||||
|
||||
import json
|
||||
import csv
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
from click.testing import CliRunner
|
||||
from vlm.cli import main
|
||||
|
||||
|
||||
class TestCLIReports:
|
||||
"""Test CLI report commands."""
|
||||
|
||||
def test_report_inventory_csv(self, tmp_path):
|
||||
"""Test inventory report generation in CSV format."""
|
||||
# Create test inventory CSV
|
||||
inventory_file = tmp_path / "inventory.csv"
|
||||
with open(inventory_file, 'w', encoding='utf-8') as f:
|
||||
f.write("# Generated: 2024-01-01T00:00:00\n")
|
||||
f.write("# Library Root: /test/library\n")
|
||||
f.write("path,filename,size_bytes,modified_timestamp,category,resolution,codec,duration_seconds,bitrate_kbps\n")
|
||||
f.write("/test/library/movie/Movie1.mkv,Movie1.mkv,1000000,2024-01-01T00:00:00,movie,1920x1080,h264,7200,5000\n")
|
||||
f.write("/test/library/series/Show.S01E01.mkv,Show.S01E01.mkv,800000,2024-01-01T00:00:00,series,1280x720,h264,2700,3000\n")
|
||||
|
||||
# Run command
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ['report', 'inventory', '--input', str(inventory_file), '--format', 'csv'])
|
||||
|
||||
# Verify success
|
||||
assert result.exit_code == 0
|
||||
assert "Loaded 2 files" in result.output
|
||||
assert "Movie1.mkv" in result.output
|
||||
assert "Show.S01E01.mkv" in result.output
|
||||
|
||||
def test_report_inventory_json(self, tmp_path):
|
||||
"""Test inventory report generation in JSON format."""
|
||||
# Create test inventory CSV
|
||||
inventory_file = tmp_path / "inventory.csv"
|
||||
with open(inventory_file, 'w', encoding='utf-8') as f:
|
||||
f.write("# Generated: 2024-01-01T00:00:00\n")
|
||||
f.write("# Library Root: /test/library\n")
|
||||
f.write("path,filename,size_bytes,modified_timestamp,category,resolution,codec,duration_seconds,bitrate_kbps\n")
|
||||
f.write("/test/library/movie/Movie1.mkv,Movie1.mkv,1000000,2024-01-01T00:00:00,movie,,,\n")
|
||||
|
||||
# Run command with output file
|
||||
output_file = tmp_path / "inventory_report.json"
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [
|
||||
'report', 'inventory',
|
||||
'--input', str(inventory_file),
|
||||
'--format', 'json',
|
||||
'--output', str(output_file)
|
||||
])
|
||||
|
||||
# Verify success
|
||||
assert result.exit_code == 0
|
||||
assert output_file.exists()
|
||||
|
||||
# Verify JSON content
|
||||
with open(output_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert data['metadata']['file_count'] == 1
|
||||
assert len(data['files']) == 1
|
||||
assert data['files'][0]['filename'] == 'Movie1.mkv'
|
||||
|
||||
def test_report_completeness_text(self, tmp_path):
|
||||
"""Test completeness report generation in text format."""
|
||||
# Create test analysis JSON
|
||||
analysis_file = tmp_path / "analysis.json"
|
||||
analysis_data = {
|
||||
"metadata": {
|
||||
"generated": "2024-01-01T00:00:00",
|
||||
"source_identities": "identities.json",
|
||||
"total_movies": 0,
|
||||
"total_series": 3
|
||||
},
|
||||
"completeness": [
|
||||
{
|
||||
"series_title": "Breaking Bad",
|
||||
"season": 1,
|
||||
"episodes_found": [1, 2, 4],
|
||||
"episodes_missing": [3]
|
||||
}
|
||||
],
|
||||
"duplicates": []
|
||||
}
|
||||
|
||||
with open(analysis_file, 'w') as f:
|
||||
json.dump(analysis_data, f)
|
||||
|
||||
# Run command
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [
|
||||
'report', 'completeness',
|
||||
'--input', str(analysis_file),
|
||||
'--format', 'text'
|
||||
])
|
||||
|
||||
# Verify success
|
||||
assert result.exit_code == 0
|
||||
assert "Loaded 1 series with gaps" in result.output
|
||||
assert "Breaking Bad" in result.output
|
||||
assert "Episodes missing:" in result.output
|
||||
|
||||
def test_report_completeness_json(self, tmp_path):
|
||||
"""Test completeness report generation in JSON format."""
|
||||
# Create test analysis JSON
|
||||
analysis_file = tmp_path / "analysis.json"
|
||||
analysis_data = {
|
||||
"metadata": {},
|
||||
"completeness": [
|
||||
{
|
||||
"series_title": "The Wire",
|
||||
"season": 1,
|
||||
"episodes_found": [1, 3],
|
||||
"episodes_missing": [2]
|
||||
}
|
||||
],
|
||||
"duplicates": []
|
||||
}
|
||||
|
||||
with open(analysis_file, 'w') as f:
|
||||
json.dump(analysis_data, f)
|
||||
|
||||
# Run command with output file
|
||||
output_file = tmp_path / "completeness_report.json"
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [
|
||||
'report', 'completeness',
|
||||
'--input', str(analysis_file),
|
||||
'--format', 'json',
|
||||
'--output', str(output_file)
|
||||
])
|
||||
|
||||
# Verify success
|
||||
assert result.exit_code == 0
|
||||
assert output_file.exists()
|
||||
|
||||
# Verify JSON content
|
||||
with open(output_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert data['metadata']['series_count'] == 1
|
||||
assert len(data['series']) == 1
|
||||
assert data['series'][0]['title'] == 'The Wire'
|
||||
|
||||
def test_report_duplicates_text(self, tmp_path):
|
||||
"""Test duplicate report generation in text format."""
|
||||
# Create test analysis JSON
|
||||
analysis_file = tmp_path / "analysis.json"
|
||||
analysis_data = {
|
||||
"metadata": {},
|
||||
"completeness": [],
|
||||
"duplicates": [
|
||||
{
|
||||
"identity": {
|
||||
"type": "movie",
|
||||
"title": "The Matrix",
|
||||
"year": 1999
|
||||
},
|
||||
"files": [
|
||||
"/movies/The.Matrix.1999.1080p.mkv",
|
||||
"/movies/The.Matrix.1999.720p.mkv"
|
||||
],
|
||||
"quality_comparison": [
|
||||
{
|
||||
"filename": "The.Matrix.1999.1080p.mkv",
|
||||
"path": "/movies/The.Matrix.1999.1080p.mkv",
|
||||
"size_bytes": 2000000000,
|
||||
"resolution": "1920x1080"
|
||||
},
|
||||
{
|
||||
"filename": "The.Matrix.1999.720p.mkv",
|
||||
"path": "/movies/The.Matrix.1999.720p.mkv",
|
||||
"size_bytes": 1000000000,
|
||||
"resolution": "1280x720"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
with open(analysis_file, 'w') as f:
|
||||
json.dump(analysis_data, f)
|
||||
|
||||
# Run command
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [
|
||||
'report', 'duplicates',
|
||||
'--input', str(analysis_file),
|
||||
'--format', 'text'
|
||||
])
|
||||
|
||||
# Verify success
|
||||
assert result.exit_code == 0
|
||||
assert "Loaded 1 duplicate groups" in result.output
|
||||
assert "The Matrix (1999)" in result.output
|
||||
assert "1920x1080" in result.output
|
||||
|
||||
def test_report_duplicates_json(self, tmp_path):
|
||||
"""Test duplicate report generation in JSON format."""
|
||||
# Create test analysis JSON
|
||||
analysis_file = tmp_path / "analysis.json"
|
||||
analysis_data = {
|
||||
"metadata": {},
|
||||
"completeness": [],
|
||||
"duplicates": [
|
||||
{
|
||||
"identity": {
|
||||
"type": "series",
|
||||
"title": "Breaking Bad",
|
||||
"season": 1,
|
||||
"episodes": [1]
|
||||
},
|
||||
"files": [
|
||||
"/series/Breaking.Bad.S01E01.1080p.mkv",
|
||||
"/series/Breaking.Bad.S01E01.720p.mkv"
|
||||
],
|
||||
"quality_comparison": [
|
||||
{
|
||||
"filename": "Breaking.Bad.S01E01.1080p.mkv",
|
||||
"path": "/series/Breaking.Bad.S01E01.1080p.mkv",
|
||||
"size_bytes": 1500000000
|
||||
},
|
||||
{
|
||||
"filename": "Breaking.Bad.S01E01.720p.mkv",
|
||||
"path": "/series/Breaking.Bad.S01E01.720p.mkv",
|
||||
"size_bytes": 800000000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
with open(analysis_file, 'w') as f:
|
||||
json.dump(analysis_data, f)
|
||||
|
||||
# Run command with output file
|
||||
output_file = tmp_path / "duplicates_report.json"
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [
|
||||
'report', 'duplicates',
|
||||
'--input', str(analysis_file),
|
||||
'--format', 'json',
|
||||
'--output', str(output_file)
|
||||
])
|
||||
|
||||
# Verify success
|
||||
assert result.exit_code == 0
|
||||
assert output_file.exists()
|
||||
|
||||
# Verify JSON content
|
||||
with open(output_file, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert data['metadata']['duplicate_groups'] == 1
|
||||
assert len(data['duplicates']) == 1
|
||||
|
||||
def test_report_summary(self, tmp_path):
|
||||
"""Test summary report generation."""
|
||||
# Create test inventory CSV
|
||||
inventory_file = tmp_path / "inventory.csv"
|
||||
with open(inventory_file, 'w', encoding='utf-8') as f:
|
||||
f.write("# Generated: 2024-01-01T00:00:00\n")
|
||||
f.write("# Library Root: /test/library\n")
|
||||
f.write("path,filename,size_bytes,modified_timestamp,category,resolution,codec,duration_seconds,bitrate_kbps\n")
|
||||
f.write("/test/library/movie/Movie1.mkv,Movie1.mkv,2000000000,2024-01-01T00:00:00,movie,,,\n")
|
||||
f.write("/test/library/movie/Movie2.mkv,Movie2.mkv,1500000000,2024-01-01T00:00:00,movie,,,\n")
|
||||
f.write("/test/library/series/Show.S01E01.mkv,Show.S01E01.mkv,1000000000,2024-01-01T00:00:00,series,,,\n")
|
||||
f.write("/test/library/anime/Anime1.mkv,Anime1.mkv,800000000,2024-01-01T00:00:00,anime,,,\n")
|
||||
|
||||
# Run command
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [
|
||||
'report', 'summary',
|
||||
'--input', str(inventory_file)
|
||||
])
|
||||
|
||||
# Verify success
|
||||
assert result.exit_code == 0
|
||||
assert "Loaded 4 files" in result.output
|
||||
assert "Total Files: 4" in result.output
|
||||
assert "Movie:" in result.output
|
||||
assert "Series:" in result.output
|
||||
assert "Anime:" in result.output
|
||||
|
||||
def test_report_summary_with_output_file(self, tmp_path):
|
||||
"""Test summary report generation with output file."""
|
||||
# Create test inventory CSV
|
||||
inventory_file = tmp_path / "inventory.csv"
|
||||
with open(inventory_file, 'w', encoding='utf-8') as f:
|
||||
f.write("# Generated: 2024-01-01T00:00:00\n")
|
||||
f.write("# Library Root: /test/library\n")
|
||||
f.write("path,filename,size_bytes,modified_timestamp,category,resolution,codec,duration_seconds,bitrate_kbps\n")
|
||||
f.write("/test/library/movie/Movie1.mkv,Movie1.mkv,1000000,2024-01-01T00:00:00,movie,,,\n")
|
||||
|
||||
# Run command with output file
|
||||
output_file = tmp_path / "summary_report.txt"
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [
|
||||
'report', 'summary',
|
||||
'--input', str(inventory_file),
|
||||
'--output', str(output_file)
|
||||
])
|
||||
|
||||
# Verify success
|
||||
assert result.exit_code == 0
|
||||
assert output_file.exists()
|
||||
|
||||
# Verify file content
|
||||
with open(output_file, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
assert "Total Files: 1" in content
|
||||
assert "Movie:" in content
|
||||
|
||||
def test_report_inventory_missing_file(self, tmp_path):
|
||||
"""Test inventory report with missing input file."""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [
|
||||
'report', 'inventory',
|
||||
'--input', str(tmp_path / "nonexistent.csv")
|
||||
])
|
||||
|
||||
# Verify error (Click validates file existence before our code runs)
|
||||
assert result.exit_code != 0
|
||||
assert "does not exist" in result.output
|
||||
|
||||
def test_report_completeness_missing_file(self, tmp_path):
|
||||
"""Test completeness report with missing input file."""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [
|
||||
'report', 'completeness',
|
||||
'--input', str(tmp_path / "nonexistent.json")
|
||||
])
|
||||
|
||||
# Verify error (Click validates file existence before our code runs)
|
||||
assert result.exit_code != 0
|
||||
assert "does not exist" in result.output
|
||||
Reference in New Issue
Block a user