fix: address code review findings

- Start Web UI before the blocking initial sync so the dashboard is
  reachable during the (rate-limited, potentially multi-minute) first
  sync; fold the immediate sync into startPeriodicSync's overlap guard
  so it can never race a concurrent tick over the shared DB / MB client.
- Make MarkNotificationSent idempotent: INSERT OR IGNORE for same-second
  PK collisions, and explicitly swallow FK violations when a release was
  pruned by a concurrent re-sync. Prevents a single vanished/duplicate
  release from aborting the digest mark-sent loop and re-sending.
- Do not abort NotifyOnce's mark-sent loop on a single failure; log and
  continue so every release in the batch is marked.
- NULL-safe reads: COALESCE(type,''), COALESCE(release_date,'') in the
  external_releases and unnotified readers to match the cache reader.
- Update/extend tests for the new idempotency and startup contracts.
This commit is contained in:
2026-07-20 06:27:42 +03:00
parent a8aa445d94
commit aee0241bb7
6 changed files with 110 additions and 42 deletions

View File

@@ -41,9 +41,38 @@ func TestMarkNotificationSent_New(t *testing.T) {
}
}
// TestMarkNotificationSent_DuplicateSecond verifies that inserting the same RGID twice
// within the same second fails due to the composite primary key (rgid, sent_at).
// In practice, notifications are sent at most once per day, so this is acceptable.
// TestMarkNotificationSent_MissingReleaseIsNoOp verifies that marking a release
// whose external_releases row does not exist (e.g. pruned by a concurrent
// re-sync) does not error: the FK violation is swallowed by INSERT OR IGNORE so
// a single vanished release cannot abort a digest's mark-sent loop.
func TestMarkNotificationSent_MissingReleaseIsNoOp(t *testing.T) {
db, err := New(":memory:")
if err != nil {
t.Fatalf("New() error: %v", err)
}
defer db.Close()
// No artist/release inserted: rgid-gone has no external_releases row.
if err := MarkNotificationSent(db, "rgid-gone"); err != nil {
t.Fatalf("MarkNotificationSent() for missing release should be a no-op, got error: %v", err)
}
// Nothing should have been recorded (FK violation ignored, row skipped).
var count int
if err := db.Conn().QueryRow("SELECT COUNT(*) FROM notifications_sent WHERE rgid = ?", "rgid-gone").Scan(&count); err != nil {
t.Fatalf("count query error: %v", err)
}
if count != 0 {
t.Errorf("expected 0 notification rows for missing release, got %d", count)
}
}
// TestMarkNotificationSent_DuplicateSecond verifies that marking the same RGID
// twice within the same second is an idempotent no-op (INSERT OR IGNORE) rather
// than an error: a same-second collision on the composite primary key
// (rgid, sent_at) must not abort a digest's mark-sent loop, since that would
// leave later releases unmarked and cause duplicate notifications on the next
// run. The marker's presence, not its exact timestamp, is what matters.
func TestMarkNotificationSent_DuplicateSecond(t *testing.T) {
db, err := New(":memory:")
if err != nil {
@@ -60,10 +89,9 @@ func TestMarkNotificationSent_DuplicateSecond(t *testing.T) {
if err := MarkNotificationSent(db, "rgid-1"); err != nil {
t.Fatalf("first MarkNotificationSent() error: %v", err)
}
// Second insert in the same second should fail with a UNIQUE constraint error.
err = MarkNotificationSent(db, "rgid-1")
if err == nil {
t.Fatal("expected UNIQUE constraint error on duplicate insert, got nil")
// Second mark in the same second should be a silent no-op, not an error.
if err := MarkNotificationSent(db, "rgid-1"); err != nil {
t.Fatalf("duplicate MarkNotificationSent() should be a no-op, got error: %v", err)
}
// Should still have exactly one row.