Skip to content

Commit 1d8f65a

Browse files
committed
Complete test suite
Not the greatest of tests, but having 49 tests with a total test coverage of 91% is a good starting point.
1 parent 6562adf commit 1d8f65a

7 files changed

Lines changed: 812 additions & 54 deletions

File tree

tests/examples.yaml

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/test_baker.py

Lines changed: 86 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,101 @@
11
"""Tests for the PDFBaker class and related functionality."""
22

3+
import logging
34
import shutil
45
from pathlib import Path
56

7+
import pytest
8+
69
from pdfbaker.baker import PDFBaker, PDFBakerOptions
10+
from pdfbaker.errors import ConfigurationError
11+
from pdfbaker.logging import TRACE
12+
13+
14+
# PDFBakerOptions tests
15+
def test_baker_options_defaults() -> None:
16+
"""Test PDFBakerOptions default values."""
17+
options = PDFBakerOptions()
18+
assert not options.quiet
19+
assert not options.verbose
20+
assert not options.trace
21+
assert not options.keep_build
22+
assert options.default_config_overrides is None
23+
24+
25+
def test_baker_options_logging_levels() -> None:
26+
"""Test different logging level configurations."""
27+
test_cases = [
28+
(PDFBakerOptions(quiet=True), logging.ERROR),
29+
(PDFBakerOptions(verbose=True), logging.DEBUG),
30+
(PDFBakerOptions(trace=True), TRACE),
31+
(PDFBakerOptions(), logging.INFO), # default
32+
]
33+
34+
examples_config = Path(__file__).parent.parent / "examples" / "examples.yaml"
35+
for options, expected_level in test_cases:
36+
PDFBaker(examples_config, options=options)
37+
assert logging.getLogger().level == expected_level
38+
739

40+
def test_baker_options_default_config_overrides(tmp_path: Path) -> None:
41+
"""Test PDFBakerOptions with default_config_overrides."""
42+
# Create a minimal valid config
43+
config_file = tmp_path / "test.yaml"
44+
config_file.write_text("documents: [test]")
845

9-
def test_examples() -> None:
10-
"""Test all examples."""
11-
examples_dir = Path(__file__).parent.parent / "examples"
46+
custom_dir = tmp_path / "custom"
47+
options = PDFBakerOptions(
48+
default_config_overrides={
49+
"directories": {
50+
"documents": str(custom_dir),
51+
}
52+
}
53+
)
54+
55+
baker = PDFBaker(config_file, options=options)
56+
assert str(baker.config["directories"]["documents"]) == str(custom_dir)
57+
58+
59+
# PDFBaker initialization tests
60+
def test_baker_init_invalid_config(tmp_path: Path) -> None:
61+
"""Test PDFBaker initialization with invalid configuration."""
62+
# Create an invalid config file (missing 'documents' key)
63+
config_file = tmp_path / "invalid.yaml"
64+
config_file.write_text("title: test")
65+
66+
with pytest.raises(ConfigurationError, match=".*documents.*missing.*"):
67+
PDFBaker(config_file)
68+
69+
70+
# PDFBaker functionality tests
71+
def test_baker_examples() -> None:
72+
"""Test baking all examples."""
1273
test_dir = Path(__file__).parent
74+
examples_config = test_dir.parent / "examples" / "examples.yaml"
1375

14-
# Create build and dist directories
76+
# Create test output directories
1577
build_dir = test_dir / "build"
1678
dist_dir = test_dir / "dist"
1779
build_dir.mkdir(exist_ok=True)
1880
dist_dir.mkdir(exist_ok=True)
1981

20-
# Copy and modify examples config
21-
config = examples_dir / "examples.yaml"
22-
test_config = test_dir / "examples.yaml"
23-
shutil.copy(config, test_config)
24-
25-
# Modify paths in config
26-
with open(test_config, encoding="utf-8") as f:
27-
content = f.read()
28-
content = content.replace("build_dir: build", f"build_dir: {build_dir}")
29-
content = content.replace("dist_dir: dist", f"dist_dir: {dist_dir}")
30-
with open(test_config, "w", encoding="utf-8") as f:
31-
f.write(content)
32-
33-
# Run baker
34-
options = PDFBakerOptions(quiet=True, keep_build=True)
35-
baker = PDFBaker(test_config, options=options)
36-
baker.bake()
82+
options = PDFBakerOptions(
83+
quiet=True,
84+
keep_build=True,
85+
default_config_overrides={
86+
"directories": {
87+
"build": str(build_dir),
88+
"dist": str(dist_dir),
89+
}
90+
},
91+
)
92+
93+
try:
94+
baker = PDFBaker(examples_config, options=options)
95+
baker.bake()
96+
finally:
97+
# Clean up test directories
98+
if build_dir.exists():
99+
shutil.rmtree(build_dir)
100+
if dist_dir.exists():
101+
shutil.rmtree(dist_dir)

tests/test_cli.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Tests for CLI functionality."""
2+
3+
from pathlib import Path
4+
5+
from click.testing import CliRunner
6+
7+
from pdfbaker.__main__ import cli
8+
9+
10+
def test_cli_version() -> None:
11+
"""Test CLI version command."""
12+
runner = CliRunner()
13+
result = runner.invoke(cli, ["--version"])
14+
assert result.exit_code == 0
15+
assert "version" in result.output.lower()
16+
17+
18+
def test_cli_help() -> None:
19+
"""Test CLI help command."""
20+
runner = CliRunner()
21+
result = runner.invoke(cli, ["--help"])
22+
assert result.exit_code == 0
23+
assert "Usage:" in result.output
24+
25+
26+
def test_cli_bake_help() -> None:
27+
"""Test CLI bake help command."""
28+
runner = CliRunner()
29+
result = runner.invoke(cli, ["bake", "--help"])
30+
assert result.exit_code == 0
31+
assert "Usage:" in result.output
32+
33+
34+
def test_cli_bake_missing_config(tmp_path: Path) -> None:
35+
"""Test CLI bake command with missing config file."""
36+
runner = CliRunner()
37+
result = runner.invoke(cli, ["bake", str(tmp_path / "missing.yaml")])
38+
assert result.exit_code == 2
39+
assert "does not exist" in result.output
40+
41+
42+
def test_cli_bake_invalid_config(tmp_path: Path) -> None:
43+
"""Test CLI bake command with invalid config file."""
44+
config_file = tmp_path / "invalid.yaml"
45+
config_file.write_text("invalid: yaml: content")
46+
47+
runner = CliRunner()
48+
result = runner.invoke(cli, ["bake", str(config_file)])
49+
assert result.exit_code == 1
50+
assert "Invalid YAML" in result.output
51+
52+
53+
def test_cli_bake_quiet_mode(tmp_path: Path) -> None:
54+
"""Test CLI bake command in quiet mode."""
55+
# Test case 1: Failure - should show errors
56+
failing_config = tmp_path / "failing.yaml"
57+
failing_config.write_text("""
58+
pages: [page1.yaml]
59+
directories:
60+
build: build
61+
""")
62+
63+
runner = CliRunner()
64+
result = runner.invoke(cli, ["bake", "--quiet", str(failing_config)])
65+
assert result.exit_code == 1 # Will fail because page1.yaml doesn't exist
66+
assert "error" in result.output.lower() # Should show error message
67+
assert "info" not in result.output.lower() # Should not show info messages
68+
69+
# Test case 2: Success - should be completely quiet
70+
success_config = tmp_path / "success.yaml"
71+
success_config.write_text("""
72+
documents: [] # Empty list of documents is valid
73+
""")
74+
75+
result = runner.invoke(cli, ["bake", "--quiet", str(success_config)])
76+
assert result.exit_code == 0
77+
assert not result.output # Should be completely quiet on success

tests/test_config.py

Lines changed: 97 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
"""Tests for common functionality."""
1+
"""Tests for configuration functionality."""
22

33
from pathlib import Path
44

55
import pytest
6+
import yaml
67

7-
from pdfbaker.config import PDFBakerConfiguration, deep_merge
8+
from pdfbaker.config import PDFBakerConfiguration, deep_merge, render_config
9+
from pdfbaker.errors import ConfigurationError
810

911

10-
def test_deep_merge_basic():
12+
# Dictionary merging tests
13+
def test_deep_merge_basic() -> None:
1114
"""Test basic dictionary merging."""
1215
base = {
1316
"title": "Document",
@@ -34,8 +37,8 @@ def test_deep_merge_basic():
3437
assert deep_merge(base, update) == expected
3538

3639

37-
def test_deep_merge_nested():
38-
"""Test merging of nested dictionaries."""
40+
def test_deep_merge_nested() -> None:
41+
"""Test nested dictionary merging."""
3942
base = {
4043
"document": {
4144
"title": "Main Document",
@@ -85,7 +88,7 @@ def test_deep_merge_nested():
8588
assert deep_merge(base, update) == expected
8689

8790

88-
def test_deep_merge_empty():
91+
def test_deep_merge_empty() -> None:
8992
"""Test merging with empty dictionaries."""
9093
base = {
9194
"title": "Document",
@@ -101,44 +104,116 @@ def test_deep_merge_empty():
101104
assert deep_merge(update, base) == base
102105

103106

104-
def test_configuration_init_with_dict():
107+
# Configuration initialization tests
108+
def test_configuration_init_with_dict(tmp_path: Path) -> None:
105109
"""Test initializing Configuration with a dictionary."""
106-
config = PDFBakerConfiguration({}, {"title": "Document"})
110+
config_file = tmp_path / "test.yaml"
111+
config_file.write_text(yaml.dump({"title": "Document"}))
112+
113+
config = PDFBakerConfiguration({}, config_file)
107114
assert config["title"] == "Document"
108115

109116

110-
def test_configuration_init_with_path(tmp_path):
117+
def test_configuration_init_with_path(tmp_path: Path) -> None:
111118
"""Test initializing Configuration with a file path."""
112119
config_file = tmp_path / "test.yaml"
113-
config_file.write_text("title: Document")
120+
config_file.write_text(yaml.dump({"title": "Document"}))
114121

115122
config = PDFBakerConfiguration({}, config_file)
116123
assert config["title"] == "Document"
117-
assert config.directory == tmp_path
124+
assert config["directories"]["config"] == tmp_path
118125

119126

120-
def test_configuration_init_with_directory(tmp_path):
127+
def test_configuration_init_with_directory(tmp_path: Path) -> None:
121128
"""Test initializing Configuration with custom directory."""
122129
config_file = tmp_path / "test.yaml"
123-
config_file.write_text('{"title": "Document"}')
130+
config_file.write_text(yaml.dump({"title": "Document"}))
131+
124132
config = PDFBakerConfiguration({}, config_file)
125133
assert config["title"] == "Document"
126-
assert config.directory == tmp_path
134+
assert config["directories"]["config"] == tmp_path
127135

128136

129-
def test_configuration_resolve_path():
137+
def test_configuration_init_invalid_yaml(tmp_path: Path) -> None:
138+
"""Test configuration with invalid YAML."""
139+
config_file = tmp_path / "invalid.yaml"
140+
config_file.write_text("invalid: [yaml: content")
141+
142+
with pytest.raises(ConfigurationError, match="Failed to load config file"):
143+
PDFBakerConfiguration({}, config_file)
144+
145+
146+
# Path resolution tests
147+
def test_configuration_resolve_path(tmp_path: Path) -> None:
130148
"""Test path resolution."""
131-
config = PDFBakerConfiguration({}, {"template": "test.yaml"})
132-
config.directory = Path("/base") # Set directory explicitly for testing
133-
assert config.resolve_path("test.yaml") == Path("/base/test.yaml")
149+
config_file = tmp_path / "test.yaml"
150+
config_file.write_text(yaml.dump({"template": "test.yaml"}))
151+
152+
config = PDFBakerConfiguration({}, config_file)
153+
154+
# Test relative path
155+
assert config.resolve_path("test.yaml") == tmp_path / "test.yaml"
156+
157+
# Test absolute path
134158
assert config.resolve_path({"path": "/absolute/path.yaml"}) == Path(
135159
"/absolute/path.yaml"
136160
)
137-
assert config.resolve_path({"name": "test.yaml"}) == Path("/base/test.yaml")
138161

162+
# Test named path
163+
assert config.resolve_path({"name": "test.yaml"}) == tmp_path / "test.yaml"
139164

140-
def test_configuration_resolve_path_invalid():
165+
166+
def test_configuration_resolve_path_invalid(tmp_path: Path) -> None:
141167
"""Test invalid path specification."""
142-
config = PDFBakerConfiguration({}, {})
143-
with pytest.raises(ValueError, match="Invalid path specification"):
168+
config_file = tmp_path / "test.yaml"
169+
config_file.write_text(yaml.dump({}))
170+
171+
config = PDFBakerConfiguration({}, config_file)
172+
with pytest.raises(ConfigurationError, match="Invalid path specification"):
144173
config.resolve_path({})
174+
175+
176+
# Configuration rendering tests
177+
def test_render_config_basic() -> None:
178+
"""Test basic template rendering in configuration."""
179+
config = {
180+
"name": "test",
181+
"title": "{{ name }} document",
182+
"nested": {
183+
"value": "{{ title }}",
184+
},
185+
}
186+
187+
rendered = render_config(config)
188+
assert rendered["title"] == "test document"
189+
assert rendered["nested"]["value"] == "test document"
190+
191+
192+
def test_render_config_circular() -> None:
193+
"""Test detection of circular references in config rendering."""
194+
config = {
195+
"a": "{{ b }}",
196+
"b": "{{ a }}",
197+
}
198+
199+
with pytest.raises(ConfigurationError, match="(?i).*circular.*"):
200+
render_config(config)
201+
202+
203+
# Utility method tests
204+
def test_configuration_pretty(tmp_path: Path) -> None:
205+
"""Test configuration pretty printing."""
206+
config_file = tmp_path / "test.yaml"
207+
config_file.write_text(
208+
yaml.dump(
209+
{
210+
"title": "Test",
211+
"content": "A" * 100, # Long string that should be truncated
212+
}
213+
)
214+
)
215+
216+
config = PDFBakerConfiguration({}, config_file)
217+
pretty = config.pretty(max_chars=20)
218+
assert "…" in pretty # Should show truncation
219+
assert "Test" in pretty

0 commit comments

Comments
 (0)