Current Coverage: 56.75% | Target: 70%+
Impact: High - These are the main entry points for users Effort: Low - Can be tested with mocks
Tests Needed:
# tests/test_api.py
@pytest.mark.unit
def test_sync_with_module_names():
"""Test sync() function with samizdatmodules parameter"""
from unittest.mock import patch, MagicMock
from dbsamizdat import sync
with patch('dbsamizdat.api._cmd_sync') as mock_sync:
sync("postgresql:///test", samizdatmodules=["myapp.views"])
mock_sync.assert_called_once()
args = mock_sync.call_args[0][0]
assert args.samizdatmodules == ["myapp.views"]
@pytest.mark.unit
def test_refresh_with_belownodes():
"""Test refresh() with belownodes filter"""
from unittest.mock import patch
from dbsamizdat import refresh
with patch('dbsamizdat.api._cmd_refresh') as mock_refresh:
refresh("postgresql:///test", belownodes=["users"])
args = mock_refresh.call_args[0][0]
assert "users" in args.belownodes
@pytest.mark.unit
def test_nuke_function():
"""Test nuke() function"""
from unittest.mock import patch
from dbsamizdat import nuke
with patch('dbsamizdat.api._cmd_nuke') as mock_nuke:
nuke("postgresql:///test")
mock_nuke.assert_called_once()
@pytest.mark.unit
def test_api_functions_use_default_dburl():
"""Test that API functions use DBURL env var when dburl not provided"""
import os
from unittest.mock import patch
from dbsamizdat import sync
os.environ['DBURL'] = 'postgresql:///default'
with patch('dbsamizdat.api._cmd_sync') as mock_sync:
sync()
args = mock_sync.call_args[0][0]
assert args.dburl == 'postgresql:///default'
del os.environ['DBURL']Expected Coverage Gain: +2.5%
Impact: Medium - Useful for debugging dependency graphs Effort: Low - Pure function, no database needed
Tests Needed:
# tests/test_graphvizdot.py
@pytest.mark.unit
def test_dot_simple_view():
"""Test dot() generates valid GraphViz for simple view"""
from dbsamizdat.graphvizdot import dot
from dbsamizdat import SamizdatView
class TestView(SamizdatView):
sql_template = "${preamble} SELECT 1 ${postamble}"
output = list(dot([TestView]))
dot_str = "\n".join(output)
assert 'digraph' in dot_str
assert 'TestView' in dot_str
assert 'shape=box' in dot_str # VIEW shape
@pytest.mark.unit
def test_dot_materialized_view():
"""Test dot() generates correct shape for materialized views"""
from dbsamizdat.graphvizdot import dot
from dbsamizdat import SamizdatMaterializedView
class TestMatView(SamizdatMaterializedView):
sql_template = "${preamble} SELECT 1 ${postamble}"
output = list(dot([TestMatView]))
dot_str = "\n".join(output)
assert 'shape=box3d' in dot_str # MATVIEW shape
assert 'fillcolor=red' in dot_str
@pytest.mark.unit
def test_dot_with_dependencies():
"""Test dot() shows dependency edges"""
from dbsamizdat.graphvizdot import dot
from dbsamizdat import SamizdatView
class BaseView(SamizdatView):
sql_template = "${preamble} SELECT 1 ${postamble}"
class DependentView(SamizdatView):
deps_on = {BaseView}
sql_template = "${preamble} SELECT * FROM \"BaseView\" ${postamble}"
output = list(dot([BaseView, DependentView]))
dot_str = "\n".join(output)
assert 'BaseView' in dot_str
assert 'DependentView' in dot_str
assert '->' in dot_str # Dependency edge
@pytest.mark.unit
def test_dot_with_unmanaged_dependencies():
"""Test dot() shows unmanaged dependencies"""
from dbsamizdat.graphvizdot import dot
from dbsamizdat import SamizdatView
class ViewWithUnmanaged(SamizdatView):
deps_on_unmanaged = {"public", "users"}
sql_template = "${preamble} SELECT * FROM users ${postamble}"
output = list(dot([ViewWithUnmanaged]))
dot_str = "\n".join(output)
assert 'shape=house' in dot_str # Unmanaged nodes
assert 'fillcolor=yellow' in dot_strExpected Coverage Gain: +15%
Impact: Medium - Important for CLI usability Effort: Low - Can test argument parsing without database
Tests Needed:
# tests/test_cli.py
@pytest.mark.unit
def test_augment_argument_parser_adds_subcommands():
"""Test that augment_argument_parser adds all expected subcommands"""
import argparse
from dbsamizdat.runner.cli import augment_argument_parser
parser = argparse.ArgumentParser()
augment_argument_parser(parser, in_django=False)
# Check subcommands exist
subcommands = [action.dest for action in parser._actions if hasattr(action, 'dest')]
assert 'func' in subcommands
@pytest.mark.unit
def test_cli_requires_modules_when_not_django():
"""Test that CLI requires samizdatmodules when not in Django"""
import argparse
from dbsamizdat.runner.cli import augment_argument_parser
parser = argparse.ArgumentParser()
augment_argument_parser(parser, in_django=False)
# Try parsing without modules - should fail
with pytest.raises(SystemExit):
parser.parse_args(['sync', 'postgresql:///test'])
@pytest.mark.unit
def test_cli_django_mode_uses_dbconn():
"""Test that Django mode uses dbconn instead of dburl"""
import argparse
from dbsamizdat.runner.cli import augment_argument_parser
parser = argparse.ArgumentParser()
augment_argument_parser(parser, in_django=True)
args = parser.parse_args(['sync', 'custom_conn'])
assert args.dbconn == 'custom_conn'
@pytest.mark.unit
def test_main_handles_samizdat_exception():
"""Test that main() handles SamizdatException gracefully"""
from unittest.mock import patch, MagicMock
from dbsamizdat.runner.cli import main
from dbsamizdat.exceptions import SamizdatException
with patch('sys.argv', ['dbsamizdat', 'sync', 'postgresql:///test', 'module']):
with patch('dbsamizdat.runner.cli.augment_argument_parser') as mock_parser:
mock_args = MagicMock()
mock_args.func = MagicMock(side_effect=SamizdatException("Test error"))
mock_parser.return_value.parse_args.return_value = mock_args
with pytest.raises(SystemExit):
main()Expected Coverage Gain: +25%
Impact: High - Core functionality Effort: Medium - Requires database but tests are straightforward
Tests Needed:
# tests/test_commands.py (additions)
@pytest.mark.integration
def test_cmd_sync_with_module_names(clean_db):
"""Test cmd_sync works with samizdatmodules"""
from dbsamizdat.runner import cmd_sync, ArgType
args = ArgType(
dburl=clean_db.dburl,
samizdatmodules=["sample_app.dbsamizdat_defs"],
in_django=False
)
cmd_sync(args)
# Verify objects were created
@pytest.mark.integration
def test_cmd_refresh_with_belownodes(clean_db):
"""Test cmd_refresh filters by belownodes"""
# Create some views
# Refresh with belownodes filter
# Verify only filtered views were refreshed
@pytest.mark.integration
def test_cmd_diff_shows_differences(clean_db):
"""Test cmd_diff correctly identifies differences"""
# Create some views
# Drop one manually
# Run diff
# Verify it detects the difference
@pytest.mark.integration
def test_cmd_printdot_output(clean_db):
"""Test cmd_printdot generates valid DOT output"""
from dbsamizdat.runner import cmd_printdot, ArgType
from io import StringIO
import sys
args = ArgType(
dburl=clean_db.dburl,
samizdatmodules=["sample_app.dbsamizdat_defs"],
in_django=False
)
old_stdout = sys.stdout
sys.stdout = StringIO()
try:
cmd_printdot(args)
output = sys.stdout.getvalue()
assert 'digraph' in output
finally:
sys.stdout = old_stdoutExpected Coverage Gain: +30%
Impact: High - Core execution engine Effort: Medium - Requires database
Tests Needed:
# tests/test_executor.py
@pytest.mark.integration
def test_executor_progress_reporting(clean_db):
"""Test executor prints progress with verbosity"""
from dbsamizdat.runner import executor, ArgType
from dbsamizdat.samizdat import SamizdatView
class TestView(SamizdatView):
sql_template = "${preamble} SELECT 1 ${postamble}"
args = ArgType(verbosity=2, dburl=clean_db.dburl)
with get_cursor(args) as cursor:
def actions():
yield "create", TestView, TestView.create()
yield "sign", TestView, TestView.sign(cursor)
executor(actions(), args, cursor, timing=True)
# Verify output was printed
@pytest.mark.integration
def test_executor_handles_errors(clean_db):
"""Test executor raises DatabaseError on SQL errors"""
from dbsamizdat.runner import executor, ArgType
from dbsamizdat.exceptions import DatabaseError
args = ArgType(dburl=clean_db.dburl)
with get_cursor(args) as cursor:
def bad_actions():
yield "create", None, "INVALID SQL SYNTAX;"
with pytest.raises(DatabaseError):
executor(bad_actions(), args, cursor)
@pytest.mark.integration
def test_executor_checkpoint_mode(clean_db):
"""Test executor commits after each action in checkpoint mode"""
from dbsamizdat.runner import executor, ArgType, txstyle
args = ArgType(
dburl=clean_db.dburl,
txdiscipline=txstyle.CHECKPOINT.value
)
# Test that commits happen at checkpointsExpected Coverage Gain: +20%
Impact: Medium - Better error messages Effort: Low - Can test exception creation
Tests Needed:
# tests/test_exceptions.py
@pytest.mark.unit
def test_database_error_formatting():
"""Test DatabaseError formats error message correctly"""
from dbsamizdat.exceptions import DatabaseError
from dbsamizdat.samizdat import SamizdatView
class TestView(SamizdatView):
sql_template = "${preamble} SELECT 1 ${postamble}"
error = Exception("SQL syntax error")
db_error = DatabaseError("create failed", error, TestView, "CREATE VIEW...")
assert "create failed" in str(db_error)
assert "TestView" in str(db_error)
@pytest.mark.unit
def test_function_signature_error():
"""Test FunctionSignatureError shows candidate signatures"""
from dbsamizdat.exceptions import FunctionSignatureError
from dbsamizdat.samizdat import SamizdatFunction
class TestFunc(SamizdatFunction):
sql_template = "${preamble} RETURNS TEXT AS $BODY$ SELECT 1 $BODY$"
error = FunctionSignatureError(TestFunc, ["text", "integer"])
assert "candidate" in str(error).lower() or "signature" in str(error).lower()Expected Coverage Gain: +10%
Impact: Low - Utility functions Effort: Low - Simple unit tests
Tests Needed:
# tests/test_util.py
@pytest.mark.unit
def test_nodenamefmt():
"""Test nodenamefmt formats node names correctly"""
from dbsamizdat.util import nodenamefmt
from dbsamizdat.samtypes import FQTuple
fq = FQTuple("public", "MyView")
assert nodenamefmt(fq) == "public.MyView"
# Test with tuple
assert nodenamefmt(("public", "MyView")) == "public.MyView"Expected Coverage Gain: +5%
Impact: Medium - Important for Django users Effort: Medium - Requires Django setup
Tests Needed:
# tests/test_django_api.py
@pytest.mark.django
def test_django_sync_function(django_setup):
"""Test django_api.sync() function"""
from dbsamizdat import django_api
django_api.sync()
# Verify samizdats were synced
@pytest.mark.django
def test_django_refresh_function(django_setup):
"""Test django_api.refresh() function"""
from dbsamizdat import django_api
django_api.refresh()
# Verify materialized views were refreshedExpected Coverage Gain: +2%
- ✅ Add tests for
api.pyfunctions (mocked) - ✅ Add tests for
graphvizdot.py - ✅ Add tests for
cli.pyargument parsing - ✅ Add tests for exception formatting
Estimated Time: 2-3 hours Expected Coverage: 65%
- ✅ Add integration tests for command functions
- ✅ Add tests for executor function
- ✅ Add tests for edge cases
Estimated Time: 4-6 hours Expected Coverage: 70%
- ✅ Add Django API tests
- ✅ Add more edge case tests
- ✅ Add tests for error recovery
Estimated Time: 3-4 hours Expected Coverage: 75%+
- Use mocks for external dependencies - Don't require database for unit tests
- Test error paths - Exception handling is often untested
- Test edge cases - Empty lists, None values, boundary conditions
- Test with different verbosity levels - Many functions have verbosity-dependent behavior
- Test transaction disciplines - CHECKPOINT vs JUMBO vs DRYRUN
tests/test_api.py- Library API teststests/test_graphvizdot.py- GraphViz generation teststests/test_cli.py- CLI argument parsing teststests/test_executor.py- Executor function teststests/test_exceptions.py- Exception formatting teststests/test_util.py- Utility function teststests/test_django_api.py- Django API tests
- Many integration tests require a database, which is why they're currently skipped
- Focus on unit tests first (no database needed) for quick coverage gains
- Mock database cursors where possible to test logic without database
- Use
@pytest.mark.unitfor tests that don't need a database - Use
@pytest.mark.integrationfor tests that need a database