Notify App Test Restructure Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Restructure notify app tests to mirror the source directory structure as specified in agents.md.

Architecture: Create a tests/ directory that mirrors the source layout. Each source module gets a corresponding test file (e.g., drivers/slack.pytests/drivers/test_slack.py). Remove the legacy tests.py file.

Tech Stack: pytest, pytest-django, pytest-cov


Current State

apps/notify/
├── drivers/
│   ├── __init__.py
│   ├── base.py
│   ├── email.py
│   ├── generic.py
│   ├── pagerduty.py
│   └── slack.py
├── management/commands/
│   ├── list_notify_drivers.py
│   └── test_notify.py
├── models.py
├── services.py
├── templating.py
├── views.py
├── tests.py              ← Legacy empty file (to remove)
└── tests/
    └── test_slack_payload.py  ← Existing test (to reorganize)

Target State

apps/notify/
├── tests/
│   ├── __init__.py
│   ├── drivers/
│   │   ├── __init__.py
│   │   ├── test_base.py
│   │   ├── test_email.py
│   │   ├── test_generic.py
│   │   ├── test_pagerduty.py
│   │   └── test_slack.py
│   ├── test_models.py
│   ├── test_services.py
│   └── test_templating.py
└── (no tests.py - removed)

Task 1: Create test directory structure

Files:

  • Create: apps/notify/tests/__init__.py
  • Create: apps/notify/tests/drivers/__init__.py

Step 1: Create __init__.py files

# apps/notify/tests/__init__.py
# (empty file)
# apps/notify/tests/drivers/__init__.py
# (empty file)

Step 2: Verify structure

Run: ls -la apps/notify/tests/ Expected: Shows __init__.py and drivers/ directory

Step 3: Commit

git add apps/notify/tests/__init__.py apps/notify/tests/drivers/__init__.py
git commit -m "chore(notify): create test directory structure"

Task 2: Move and rename existing Slack test

Files:

  • Move: apps/notify/tests/test_slack_payload.pyapps/notify/tests/drivers/test_slack.py

Step 1: Move the file

mv apps/notify/tests/test_slack_payload.py apps/notify/tests/drivers/test_slack.py

Step 2: Run tests to verify they still pass

Run: uv run pytest apps/notify/tests/drivers/test_slack.py -v Expected: 2 tests pass

Step 3: Commit

git add apps/notify/tests/drivers/test_slack.py
git add apps/notify/tests/test_slack_payload.py  # stages deletion
git commit -m "refactor(notify): move slack tests to drivers/ subdirectory"

Task 3: Create test_base.py for BaseNotifyDriver

Files:

  • Create: apps/notify/tests/drivers/test_base.py
  • Reference: apps/notify/drivers/base.py

Step 1: Write the test file

"""Tests for BaseNotifyDriver and NotificationMessage."""

import pytest

from apps.notify.drivers.base import BaseNotifyDriver, NotificationMessage


class TestNotificationMessage:
    """Tests for NotificationMessage dataclass."""

    def test_message_normalizes_severity(self):
        """Severity should be lowercased and validated."""
        msg = NotificationMessage(title="Test", message="Body", severity="WARNING")
        assert msg.severity == "warning"

    def test_message_invalid_severity_defaults_to_info(self):
        """Invalid severity should default to 'info'."""
        msg = NotificationMessage(title="Test", message="Body", severity="invalid")
        assert msg.severity == "info"

    def test_message_empty_severity_defaults_to_info(self):
        """Empty severity should default to 'info'."""
        msg = NotificationMessage(title="Test", message="Body", severity="")
        assert msg.severity == "info"

    def test_message_valid_severities(self):
        """All valid severities should be accepted."""
        for sev in ["critical", "warning", "info", "success"]:
            msg = NotificationMessage(title="Test", message="Body", severity=sev)
            assert msg.severity == sev

    def test_message_default_values(self):
        """Default values should be set correctly."""
        msg = NotificationMessage(title="Test", message="Body", severity="info")
        assert msg.channel == "default"
        assert msg.tags == {}
        assert msg.context == {}


class TestBaseNotifyDriver:
    """Tests for BaseNotifyDriver helper methods."""

    def test_message_to_dict(self):
        """_message_to_dict should convert message to dictionary."""
        msg = NotificationMessage(
            title="Test",
            message="Body",
            severity="warning",
            channel="ops",
            tags={"env": "prod"},
            context={"key": "value"},
        )

        class ConcreteDriver(BaseNotifyDriver):
            name = "test"

            def validate_config(self, config):
                return True

            def send(self, message, config):
                return {"success": True}

        driver = ConcreteDriver()
        result = driver._message_to_dict(msg)

        assert result["title"] == "Test"
        assert result["message"] == "Body"
        assert result["severity"] == "warning"
        assert result["channel"] == "ops"
        assert result["tags"] == {"env": "prod"}
        assert result["context"] == {"key": "value"}

    def test_severity_colors_defined(self):
        """SEVERITY_COLORS should have all severity levels."""
        assert "critical" in BaseNotifyDriver.SEVERITY_COLORS
        assert "warning" in BaseNotifyDriver.SEVERITY_COLORS
        assert "info" in BaseNotifyDriver.SEVERITY_COLORS
        assert "success" in BaseNotifyDriver.SEVERITY_COLORS

    def test_priority_map_defined(self):
        """PRIORITY_MAP should have all severity levels."""
        assert "critical" in BaseNotifyDriver.PRIORITY_MAP
        assert "warning" in BaseNotifyDriver.PRIORITY_MAP
        assert "info" in BaseNotifyDriver.PRIORITY_MAP
        assert "success" in BaseNotifyDriver.PRIORITY_MAP

Step 2: Run tests

Run: uv run pytest apps/notify/tests/drivers/test_base.py -v Expected: All tests pass

Step 3: Commit

git add apps/notify/tests/drivers/test_base.py
git commit -m "test(notify): add tests for BaseNotifyDriver and NotificationMessage"

Task 4: Create test_email.py for EmailNotifyDriver

Files:

  • Create: apps/notify/tests/drivers/test_email.py
  • Reference: apps/notify/drivers/email.py

Step 1: Write the test file

"""Tests for EmailNotifyDriver."""

import pytest

from apps.notify.drivers.base import NotificationMessage
from apps.notify.drivers.email import EmailNotifyDriver


class TestEmailNotifyDriver:
    """Tests for EmailNotifyDriver."""

    @pytest.fixture
    def driver(self):
        return EmailNotifyDriver()

    @pytest.fixture
    def message(self):
        return NotificationMessage(
            title="Test Alert",
            message="Test message body",
            severity="warning",
            tags={"env": "test"},
            context={"key": "value"},
        )

    @pytest.fixture
    def valid_config(self):
        return {
            "smtp_host": "smtp.example.com",
            "from_address": "alerts@example.com",
            "to_addresses": ["ops@example.com"],
        }

    def test_driver_name(self, driver):
        """Driver should have correct name."""
        assert driver.name == "email"

    def test_validate_config_valid(self, driver, valid_config):
        """Valid config should pass validation."""
        assert driver.validate_config(valid_config) is True

    def test_validate_config_missing_smtp_host(self, driver):
        """Config without smtp_host should fail."""
        config = {"from_address": "alerts@example.com"}
        assert driver.validate_config(config) is False

    def test_validate_config_missing_from_address(self, driver):
        """Config without from_address should fail."""
        config = {"smtp_host": "smtp.example.com"}
        assert driver.validate_config(config) is False

    def test_build_email_subject_format(self, driver, message, valid_config):
        """Email subject should include severity and title."""
        email = driver._build_email(message, valid_config)
        assert email["Subject"] == "[WARNING] Test Alert"

    def test_build_email_from_address(self, driver, message, valid_config):
        """Email should use from_address from config."""
        email = driver._build_email(message, valid_config)
        assert email["From"] == "alerts@example.com"

    def test_build_email_to_addresses(self, driver, message, valid_config):
        """Email should use to_addresses from config."""
        email = driver._build_email(message, valid_config)
        assert "ops@example.com" in email["To"]

    def test_build_email_has_text_part(self, driver, message, valid_config):
        """Email should have plain text part from template."""
        email = driver._build_email(message, valid_config)
        parts = email.get_payload()
        content_types = [p.get_content_type() for p in parts]
        assert "text/plain" in content_types

    def test_build_email_has_html_part(self, driver, message, valid_config):
        """Email should have HTML part from template."""
        email = driver._build_email(message, valid_config)
        parts = email.get_payload()
        content_types = [p.get_content_type() for p in parts]
        assert "text/html" in content_types

    def test_build_email_priority_header(self, driver, message, valid_config):
        """Email should have X-Priority header based on severity."""
        email = driver._build_email(message, valid_config)
        assert email["X-Priority"] == "2"  # warning = priority 2

    def test_send_returns_error_for_invalid_config(self, driver, message):
        """Send should return error dict for invalid config."""
        result = driver.send(message, {})
        assert result["success"] is False
        assert "error" in result

Step 2: Run tests

Run: uv run pytest apps/notify/tests/drivers/test_email.py -v Expected: All tests pass

Step 3: Commit

git add apps/notify/tests/drivers/test_email.py
git commit -m "test(notify): add tests for EmailNotifyDriver"

Task 5: Create test_pagerduty.py for PagerDutyNotifyDriver

Files:

  • Create: apps/notify/tests/drivers/test_pagerduty.py
  • Reference: apps/notify/drivers/pagerduty.py

Step 1: Write the test file

"""Tests for PagerDutyNotifyDriver."""

import json

import pytest

from apps.notify.drivers.base import NotificationMessage
from apps.notify.drivers.pagerduty import PagerDutyNotifyDriver


class TestPagerDutyNotifyDriver:
    """Tests for PagerDutyNotifyDriver."""

    @pytest.fixture
    def driver(self):
        return PagerDutyNotifyDriver()

    @pytest.fixture
    def message(self):
        return NotificationMessage(
            title="Test Alert",
            message="Test message body",
            severity="critical",
            channel="ops",
            tags={"env": "prod", "service": "api"},
            context={"cpu_percent": 95},
        )

    @pytest.fixture
    def valid_config(self):
        return {"integration_key": "test-key-12345678901234567890"}

    def test_driver_name(self, driver):
        """Driver should have correct name."""
        assert driver.name == "pagerduty"

    def test_validate_config_valid(self, driver, valid_config):
        """Valid config should pass validation."""
        assert driver.validate_config(valid_config) is True

    def test_validate_config_missing_key(self, driver):
        """Config without integration_key should fail."""
        assert driver.validate_config({}) is False

    def test_validate_config_short_key(self, driver):
        """Config with too short integration_key should fail."""
        assert driver.validate_config({"integration_key": "short"}) is False

    def test_build_payload_has_routing_key(self, driver, message, valid_config):
        """Payload should include routing_key from config."""
        payload = driver._build_payload(message, valid_config)
        assert payload["routing_key"] == valid_config["integration_key"]

    def test_build_payload_has_event_action(self, driver, message, valid_config):
        """Payload should have event_action defaulting to trigger."""
        payload = driver._build_payload(message, valid_config)
        assert payload["event_action"] == "trigger"

    def test_build_payload_has_summary(self, driver, message, valid_config):
        """Payload should have summary with severity and title."""
        payload = driver._build_payload(message, valid_config)
        assert "[CRITICAL]" in payload["payload"]["summary"]
        assert "Test Alert" in payload["payload"]["summary"]

    def test_build_payload_severity_mapping(self, driver, valid_config):
        """Severity should be mapped to PagerDuty values."""
        for sev, expected in [
            ("critical", "critical"),
            ("warning", "warning"),
            ("info", "info"),
            ("success", "info"),
        ]:
            msg = NotificationMessage(title="Test", message="Body", severity=sev)
            payload = driver._build_payload(msg, valid_config)
            assert payload["payload"]["severity"] == expected

    def test_build_payload_uses_dedup_key_from_config(self, driver, message):
        """Payload should use dedup_key from config if provided."""
        config = {
            "integration_key": "test-key-12345678901234567890",
            "dedup_key": "custom-dedup",
        }
        payload = driver._build_payload(message, config)
        assert payload["dedup_key"] == "custom-dedup"

    def test_build_payload_uses_fingerprint_as_dedup(self, driver, valid_config):
        """Payload should use fingerprint tag as dedup_key if no dedup_key in config."""
        msg = NotificationMessage(
            title="Test",
            message="Body",
            severity="warning",
            tags={"fingerprint": "fp-123"},
        )
        payload = driver._build_payload(msg, valid_config)
        assert payload["dedup_key"] == "fp-123"

    def test_build_payload_json_serializable(self, driver, message, valid_config):
        """Payload should be JSON serializable."""
        payload = driver._build_payload(message, valid_config)
        # Remove incident key for serialization test (may have non-serializable datetime)
        payload_copy = {k: v for k, v in payload.items() if k != "incident"}
        dumped = json.dumps(payload_copy, default=str)
        assert dumped

    def test_send_returns_error_for_invalid_config(self, driver, message):
        """Send should return error dict for invalid config."""
        result = driver.send(message, {})
        assert result["success"] is False
        assert "error" in result

Step 2: Run tests

Run: uv run pytest apps/notify/tests/drivers/test_pagerduty.py -v Expected: All tests pass

Step 3: Commit

git add apps/notify/tests/drivers/test_pagerduty.py
git commit -m "test(notify): add tests for PagerDutyNotifyDriver"

Task 6: Create test_generic.py for GenericNotifyDriver

Files:

  • Create: apps/notify/tests/drivers/test_generic.py
  • Reference: apps/notify/drivers/generic.py

Step 1: Write the test file

"""Tests for GenericNotifyDriver."""

import json

import pytest

from apps.notify.drivers.base import NotificationMessage
from apps.notify.drivers.generic import GenericNotifyDriver


class TestGenericNotifyDriver:
    """Tests for GenericNotifyDriver."""

    @pytest.fixture
    def driver(self):
        return GenericNotifyDriver()

    @pytest.fixture
    def message(self):
        return NotificationMessage(
            title="Test Alert",
            message="Test message body",
            severity="warning",
            channel="alerts",
            tags={"env": "test"},
            context={"key": "value"},
        )

    @pytest.fixture
    def valid_config(self):
        return {"endpoint": "https://example.com/webhook"}

    def test_driver_name(self, driver):
        """Driver should have correct name."""
        assert driver.name == "generic"

    def test_validate_config_with_endpoint(self, driver, valid_config):
        """Config with endpoint should pass validation."""
        assert driver.validate_config(valid_config) is True

    def test_validate_config_with_webhook_url(self, driver):
        """Config with webhook_url should pass validation."""
        config = {"webhook_url": "https://example.com/hook"}
        assert driver.validate_config(config) is True

    def test_validate_config_empty_is_valid(self, driver):
        """Empty config should be valid (disabled mode)."""
        assert driver.validate_config({}) is True

    def test_validate_config_disabled_is_valid(self, driver):
        """Config with disabled=True should be valid."""
        assert driver.validate_config({"disabled": True}) is True

    def test_validate_config_invalid_url(self, driver):
        """Config with non-http URL should fail."""
        config = {"endpoint": "ftp://example.com/webhook"}
        assert driver.validate_config(config) is False

    def test_build_payload_has_title(self, driver, message, valid_config):
        """Payload should include title."""
        payload = driver._build_payload(message, valid_config)
        assert payload["title"] == "Test Alert"

    def test_build_payload_has_message(self, driver, message, valid_config):
        """Payload should include message."""
        payload = driver._build_payload(message, valid_config)
        assert payload["message"] == "Test message body"

    def test_build_payload_has_severity(self, driver, message, valid_config):
        """Payload should include severity."""
        payload = driver._build_payload(message, valid_config)
        assert payload["severity"] == "warning"

    def test_build_payload_has_tags(self, driver, message, valid_config):
        """Payload should include tags."""
        payload = driver._build_payload(message, valid_config)
        assert payload["tags"] == {"env": "test"}

    def test_build_payload_has_incident(self, driver, message, valid_config):
        """Payload should include incident details."""
        payload = driver._build_payload(message, valid_config)
        assert "incident" in payload

    def test_build_payload_json_serializable(self, driver, message, valid_config):
        """Payload should be JSON serializable."""
        payload = driver._build_payload(message, valid_config)
        dumped = json.dumps(payload, default=str)
        assert dumped

    def test_send_returns_error_for_invalid_config(self, driver, message):
        """Send should return error dict for invalid URL config."""
        result = driver.send(message, {"endpoint": "invalid-url"})
        assert result["success"] is False
        assert "error" in result

Step 2: Run tests

Run: uv run pytest apps/notify/tests/drivers/test_generic.py -v Expected: All tests pass

Step 3: Commit

git add apps/notify/tests/drivers/test_generic.py
git commit -m "test(notify): add tests for GenericNotifyDriver"

Task 7: Create test_templating.py for templating module

Files:

  • Create: apps/notify/tests/test_templating.py
  • Reference: apps/notify/templating.py

Step 1: Write the test file

"""Tests for notification templating module."""

import pytest

from apps.notify.templating import (
    NotificationTemplatingService,
    render_template,
)


class TestRenderTemplate:
    """Tests for render_template function."""

    def test_render_none_returns_none(self):
        """None spec should return None."""
        assert render_template(None, {}) is None

    def test_render_empty_string_returns_none(self):
        """Empty string spec should return None."""
        assert render_template("", {}) is None

    def test_render_inline_template(self):
        """Inline template should render with context."""
        result = render_template("Hello ", {"name": "World"})
        assert result == "Hello World"

    def test_render_file_template(self):
        """File template should load and render."""
        ctx = {
            "title": "Test",
            "severity": "info",
            "message": "Test message",
            "tags": {},
            "context": {},
            "incident": {},
            "intelligence": None,
            "recommendations": [],
        }
        result = render_template("file:email_text.j2", ctx)
        assert "Test" in result
        assert "INFO" in result

    def test_render_file_without_prefix(self):
        """File template without 'file:' prefix should work if file exists."""
        ctx = {
            "title": "Test",
            "severity": "warning",
            "message": "Body",
            "tags": {},
            "context": {},
            "incident": {},
            "intelligence": None,
            "recommendations": [],
        }
        result = render_template("email_text.j2", ctx)
        assert "Test" in result

    def test_render_missing_file_raises(self):
        """Missing file template should raise ValueError."""
        with pytest.raises(ValueError, match="Template file not found"):
            render_template("file:nonexistent.j2", {})

    def test_render_dict_spec_inline(self):
        """Dict spec with type=inline should render inline template."""
        spec = {"type": "inline", "template": "Value: "}
        result = render_template(spec, {"x": 42})
        assert result == "Value: 42"

    def test_render_dict_spec_file(self):
        """Dict spec with type=file should load file template."""
        spec = {"type": "file", "template": "email_text.j2"}
        ctx = {
            "title": "Test",
            "severity": "info",
            "message": "Body",
            "tags": {},
            "context": {},
            "incident": {},
            "intelligence": None,
            "recommendations": [],
        }
        result = render_template(spec, ctx)
        assert "Test" in result


class TestNotificationTemplatingService:
    """Tests for NotificationTemplatingService."""

    @pytest.fixture
    def service(self):
        return NotificationTemplatingService()

    @pytest.fixture
    def message_dict(self):
        return {
            "title": "Test Alert",
            "message": "Alert body",
            "severity": "warning",
            "channel": "ops",
            "tags": {"env": "prod"},
            "context": {"key": "value"},
        }

    def test_compose_incident_details_has_metrics(self, service, message_dict):
        """Incident details should include system metrics."""
        result = service.compose_incident_details(message_dict, {})
        assert "cpu_count" in result
        assert "ram_total_bytes" in result
        assert "ram_total_human" in result

    def test_compose_incident_details_has_message_fields(self, service, message_dict):
        """Incident details should include message fields."""
        result = service.compose_incident_details(message_dict, {})
        assert result["title"] == "Test Alert"
        assert result["severity"] == "warning"

    def test_compose_incident_details_has_generated_at(self, service, message_dict):
        """Incident details should include timestamp."""
        result = service.compose_incident_details(message_dict, {})
        assert "generated_at" in result

    def test_build_template_context_has_top_level_fields(self, service, message_dict):
        """Template context should have top-level convenience fields."""
        incident = service.compose_incident_details(message_dict, {})
        ctx = service.build_template_context(message_dict, incident)

        assert ctx["title"] == "Test Alert"
        assert ctx["message"] == "Alert body"
        assert ctx["severity"] == "warning"
        assert "incident" in ctx

    def test_build_template_context_has_convenience_aliases(self, service, message_dict):
        """Template context should have convenience aliases."""
        message_dict["context"]["intelligence"] = {"summary": "Test summary"}
        incident = service.compose_incident_details(message_dict, {})
        ctx = service.build_template_context(message_dict, incident)

        assert "intelligence" in ctx
        assert "recommendations" in ctx
        assert "incident_id" in ctx

    def test_render_message_templates_uses_driver_default(self, service, message_dict):
        """Should use driver-default template file."""
        result = service.render_message_templates("email", message_dict, {})
        assert result["text"] is not None
        assert "Test Alert" in result["text"]

    def test_render_message_templates_raises_for_missing_driver(self, service, message_dict):
        """Should raise error when no template found for driver."""
        with pytest.raises(ValueError, match="No template found"):
            service.render_message_templates("nonexistent_driver", message_dict, {})

Step 2: Run tests

Run: uv run pytest apps/notify/tests/test_templating.py -v Expected: All tests pass

Step 3: Commit

git add apps/notify/tests/test_templating.py
git commit -m "test(notify): add tests for templating module"

Task 8: Create test_models.py for models

Files:

  • Create: apps/notify/tests/test_models.py
  • Reference: apps/notify/models.py

Step 1: Write the test file

"""Tests for notify app models."""

import pytest

from apps.notify.models import NotificationChannel, NotificationSeverity


@pytest.mark.django_db
class TestNotificationChannel:
    """Tests for NotificationChannel model."""

    def test_create_channel(self):
        """Should create a notification channel."""
        channel = NotificationChannel.objects.create(
            name="test-slack",
            driver="slack",
            config={"webhook_url": "https://hooks.slack.com/services/T/B/X"},
        )
        assert channel.pk is not None
        assert channel.name == "test-slack"
        assert channel.driver == "slack"

    def test_channel_is_active_default(self):
        """Channel should be active by default."""
        channel = NotificationChannel.objects.create(
            name="test-channel",
            driver="email",
            config={},
        )
        assert channel.is_active is True

    def test_channel_str_representation(self):
        """Channel __str__ should return name."""
        channel = NotificationChannel(name="ops-alerts", driver="slack")
        assert str(channel) == "ops-alerts"

    def test_channel_unique_name(self):
        """Channel name should be unique."""
        NotificationChannel.objects.create(name="unique-name", driver="email", config={})
        with pytest.raises(Exception):  # IntegrityError
            NotificationChannel.objects.create(name="unique-name", driver="slack", config={})


class TestNotificationSeverity:
    """Tests for NotificationSeverity choices."""

    def test_severity_choices_exist(self):
        """Severity choices should be defined."""
        choices = NotificationSeverity.choices
        assert len(choices) >= 4

    def test_severity_values(self):
        """Severity should have expected values."""
        values = [c[0] for c in NotificationSeverity.choices]
        assert "critical" in values
        assert "warning" in values
        assert "info" in values
        assert "success" in values

Step 2: Run tests

Run: uv run pytest apps/notify/tests/test_models.py -v Expected: All tests pass

Step 3: Commit

git add apps/notify/tests/test_models.py
git commit -m "test(notify): add tests for NotificationChannel model"

Task 9: Remove legacy tests.py file

Files:

  • Delete: apps/notify/tests.py

Step 1: Remove the file

rm apps/notify/tests.py

Step 2: Run all tests to verify nothing breaks

Run: uv run pytest apps/notify/tests/ -v Expected: All tests pass

Step 3: Commit

git add apps/notify/tests.py  # stages deletion
git commit -m "chore(notify): remove legacy tests.py file"

Task 10: Run coverage and verify improvement

Files:

  • None (verification only)

Step 1: Run tests with coverage

Run: uv run pytest apps/notify/tests/ --cov=apps/notify --cov-report=term-missing Expected: Coverage report showing improved coverage for notify app

Step 2: Verify test structure matches source

Run: find apps/notify/tests -name "*.py" | sort Expected:

apps/notify/tests/__init__.py
apps/notify/tests/drivers/__init__.py
apps/notify/tests/drivers/test_base.py
apps/notify/tests/drivers/test_email.py
apps/notify/tests/drivers/test_generic.py
apps/notify/tests/drivers/test_pagerduty.py
apps/notify/tests/drivers/test_slack.py
apps/notify/tests/test_models.py
apps/notify/tests/test_templating.py

Step 3: Final commit with all tests passing

git status  # verify clean state

Summary

After completing all tasks, the test structure will be:

Source File Test File
drivers/base.py tests/drivers/test_base.py
drivers/email.py tests/drivers/test_email.py
drivers/generic.py tests/drivers/test_generic.py
drivers/pagerduty.py tests/drivers/test_pagerduty.py
drivers/slack.py tests/drivers/test_slack.py
templating.py tests/test_templating.py
models.py tests/test_models.py

Coverage should improve significantly from the current 11% for the notify app.


This site uses Just the Docs, a documentation theme for Jekyll.