(feature) Add run completion notifications
Add email and webhook notification targets with delivery tracking, and send notifications when backup runs reach a terminal status. Expose notification target management in the Django UI and keep delivery failures recorded without failing the backup worker.
This commit is contained in:
@@ -18,6 +18,8 @@ from pobsync_backend.models import (
|
||||
BackupRun,
|
||||
GlobalConfig,
|
||||
HostConfig,
|
||||
NotificationDelivery,
|
||||
NotificationTarget,
|
||||
PurgedSnapshot,
|
||||
ScheduleConfig,
|
||||
SnapshotRecord,
|
||||
@@ -52,6 +54,7 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, reverse("dashboard"))
|
||||
self.assertContains(response, reverse("hosts_list"))
|
||||
self.assertContains(response, reverse("ssh_credentials"))
|
||||
self.assertContains(response, reverse("notification_targets"))
|
||||
self.assertContains(response, reverse("logs"))
|
||||
self.assertContains(response, reverse("purged_snapshots"))
|
||||
self.assertContains(response, reverse("self_check"))
|
||||
@@ -94,6 +97,70 @@ class ViewTests(TestCase):
|
||||
self.assertContains(response, "Django control panel")
|
||||
self.assertContains(response, "Native systemd installer")
|
||||
|
||||
def test_notification_targets_view_renders_targets_and_deliveries(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
run = BackupRun.objects.create(host=host, status=BackupRun.Status.SUCCESS)
|
||||
target = NotificationTarget.objects.create(
|
||||
name="ops",
|
||||
channel=NotificationTarget.Channel.EMAIL,
|
||||
email_to="ops@example.test",
|
||||
last_status=NotificationDelivery.Status.SENT,
|
||||
)
|
||||
NotificationDelivery.objects.create(target=target, run=run, status=NotificationDelivery.Status.SENT)
|
||||
|
||||
response = self.client.get(reverse("notification_targets"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Notifications")
|
||||
self.assertContains(response, "ops")
|
||||
self.assertContains(response, "ops@example.test")
|
||||
self.assertContains(response, f"Run {run.id}")
|
||||
|
||||
def test_notification_target_form_creates_email_target(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("create_notification_target"),
|
||||
{
|
||||
"name": "ops",
|
||||
"enabled": "on",
|
||||
"channel": NotificationTarget.Channel.EMAIL,
|
||||
"statuses": [BackupRun.Status.FAILED, BackupRun.Status.WARNING],
|
||||
"email_to": "ops@example.test, backup@example.test",
|
||||
"webhook_headers": "{}",
|
||||
"notes": "Notify ops",
|
||||
},
|
||||
follow=True,
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("notification_targets"))
|
||||
self.assertContains(response, "Notification target ops created.")
|
||||
target = NotificationTarget.objects.get(name="ops")
|
||||
self.assertEqual(target.channel, NotificationTarget.Channel.EMAIL)
|
||||
self.assertEqual(target.statuses, [BackupRun.Status.FAILED, BackupRun.Status.WARNING])
|
||||
self.assertEqual(target.email_to, "ops@example.test\nbackup@example.test")
|
||||
|
||||
def test_notification_target_form_requires_channel_destination(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("create_notification_target"),
|
||||
{
|
||||
"name": "broken",
|
||||
"enabled": "on",
|
||||
"channel": NotificationTarget.Channel.WEBHOOK,
|
||||
"statuses": [BackupRun.Status.FAILED],
|
||||
"email_to": "",
|
||||
"webhook_url": "",
|
||||
"webhook_headers": "{}",
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Webhook targets need a URL.")
|
||||
self.assertFalse(NotificationTarget.objects.exists())
|
||||
|
||||
def test_dashboard_renders_hosts_and_latest_runs(self) -> None:
|
||||
self.client.force_login(self.staff_user)
|
||||
host = HostConfig.objects.create(host="web-01", address="web-01.example.test")
|
||||
|
||||
Reference in New Issue
Block a user