package repository import ( "database/sql" "testing" "time" "vidarchive/internal/models" ) func TestSubscriptionRepositoryCRUD(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewSubscriptionRepository(db) sub := &models.Subscription{ Name: "News", URL: "https://example.com/playlist", Enabled: true, RefreshMode: "overwrite", ScheduleKind: "daily", CronExpr: "0 3 * * *", OutputDir: "subscriptions/news", PruneRemoved: true, } if err := repo.Create(sub); err != nil { t.Fatalf("create: %v", err) } if sub.ID == 0 { t.Fatal("expected an assigned ID after create") } got, err := repo.GetByID(sub.ID) if err != nil { t.Fatalf("get: %v", err) } if got.Name != "News" || got.URL != sub.URL || !got.Enabled || got.RefreshMode != "overwrite" || got.OutputDir != "subscriptions/news" || !got.PruneRemoved { t.Errorf("round-trip mismatch: %+v", got) } got.Name = "Updated" got.RefreshMode = "skip" if err := repo.Update(got); err != nil { t.Fatalf("update: %v", err) } again, _ := repo.GetByID(sub.ID) if again.Name != "Updated" || again.RefreshMode != "skip" { t.Errorf("update not persisted: %+v", again) } if err := repo.SetEnabled(sub.ID, false); err != nil { t.Fatalf("set enabled: %v", err) } if again, _ = repo.GetByID(sub.ID); again.Enabled { t.Error("expected disabled after SetEnabled(false)") } if err := repo.Delete(sub.ID); err != nil { t.Fatalf("delete: %v", err) } if _, err := repo.GetByID(sub.ID); err == nil { t.Error("expected error fetching deleted subscription") } } func TestSubscriptionRepositoryGetDueAndMarkRun(t *testing.T) { db := setupTestDB(t) defer db.Close() repo := NewSubscriptionRepository(db) now := time.Now() // Due: next run in the past. due := &models.Subscription{ Name: "due", URL: "u1", Enabled: true, RefreshMode: "overwrite", ScheduleKind: "daily", CronExpr: "0 3 * * *", OutputDir: "a", NextRunAt: nullTime(now.Add(-time.Hour)), } // Not due: next run in the future. future := &models.Subscription{ Name: "future", URL: "u2", Enabled: true, RefreshMode: "overwrite", ScheduleKind: "daily", CronExpr: "0 3 * * *", OutputDir: "b", NextRunAt: nullTime(now.Add(time.Hour)), } // Disabled: never due even though its next run is in the past. disabled := &models.Subscription{ Name: "disabled", URL: "u3", Enabled: false, RefreshMode: "overwrite", ScheduleKind: "daily", CronExpr: "0 3 * * *", OutputDir: "c", NextRunAt: nullTime(now.Add(-time.Hour)), } for _, s := range []*models.Subscription{due, future, disabled} { if err := repo.Create(s); err != nil { t.Fatalf("create %s: %v", s.Name, err) } } gotDue, err := repo.GetDue(now) if err != nil { t.Fatalf("get due: %v", err) } if len(gotDue) != 1 || gotDue[0].Name != "due" { names := make([]string, len(gotDue)) for i, s := range gotDue { names[i] = s.Name } t.Fatalf("expected only the due subscription, got %v", names) } next := now.Add(24 * time.Hour) if err := repo.MarkRun(due.ID, now, next, "queued"); err != nil { t.Fatalf("mark run: %v", err) } after, _ := repo.GetByID(due.ID) if !after.LastRunAt.Valid || after.LastStatus.String != "queued" { t.Errorf("MarkRun not persisted: %+v", after) } // After marking run, it should no longer be due. if gotDue, _ = repo.GetDue(now); len(gotDue) != 0 { t.Errorf("expected no due subscriptions after MarkRun, got %d", len(gotDue)) } } func nullTime(t time.Time) sql.NullTime { return sql.NullTime{Time: t, Valid: true} }