subscription_test.go
⎇
Raw
1package repository
2
3import (
4 "database/sql"
5 "testing"
6 "time"
7
8 "vidarchive/internal/models"
9)
10
11func TestSubscriptionRepositoryCRUD(t *testing.T) {
12 db := setupTestDB(t)
13 defer db.Close()
14
15 repo := NewSubscriptionRepository(db)
16
17 sub := &models.Subscription{
18 Name: "News",
19 URL: "https://example.com/playlist",
20 Enabled: true,
21 RefreshMode: "overwrite",
22 ScheduleKind: "daily",
23 CronExpr: "0 3 * * *",
24 OutputDir: "subscriptions/news",
25 PruneRemoved: true,
26 }
27 if err := repo.Create(sub); err != nil {
28 t.Fatalf("create: %v", err)
29 }
30 if sub.ID == 0 {
31 t.Fatal("expected an assigned ID after create")
32 }
33
34 got, err := repo.GetByID(sub.ID)
35 if err != nil {
36 t.Fatalf("get: %v", err)
37 }
38 if got.Name != "News" || got.URL != sub.URL || !got.Enabled || got.RefreshMode != "overwrite" ||
39 got.OutputDir != "subscriptions/news" || !got.PruneRemoved {
40 t.Errorf("round-trip mismatch: %+v", got)
41 }
42
43 got.Name = "Updated"
44 got.RefreshMode = "skip"
45 if err := repo.Update(got); err != nil {
46 t.Fatalf("update: %v", err)
47 }
48 again, _ := repo.GetByID(sub.ID)
49 if again.Name != "Updated" || again.RefreshMode != "skip" {
50 t.Errorf("update not persisted: %+v", again)
51 }
52
53 if err := repo.SetEnabled(sub.ID, false); err != nil {
54 t.Fatalf("set enabled: %v", err)
55 }
56 if again, _ = repo.GetByID(sub.ID); again.Enabled {
57 t.Error("expected disabled after SetEnabled(false)")
58 }
59
60 if err := repo.Delete(sub.ID); err != nil {
61 t.Fatalf("delete: %v", err)
62 }
63 if _, err := repo.GetByID(sub.ID); err == nil {
64 t.Error("expected error fetching deleted subscription")
65 }
66}
67
68func TestSubscriptionRepositoryGetDueAndMarkRun(t *testing.T) {
69 db := setupTestDB(t)
70 defer db.Close()
71
72 repo := NewSubscriptionRepository(db)
73 now := time.Now()
74
75 // Due: next run in the past.
76 due := &models.Subscription{
77 Name: "due", URL: "u1", Enabled: true, RefreshMode: "overwrite",
78 ScheduleKind: "daily", CronExpr: "0 3 * * *", OutputDir: "a",
79 NextRunAt: nullTime(now.Add(-time.Hour)),
80 }
81 // Not due: next run in the future.
82 future := &models.Subscription{
83 Name: "future", URL: "u2", Enabled: true, RefreshMode: "overwrite",
84 ScheduleKind: "daily", CronExpr: "0 3 * * *", OutputDir: "b",
85 NextRunAt: nullTime(now.Add(time.Hour)),
86 }
87 // Disabled: never due even though its next run is in the past.
88 disabled := &models.Subscription{
89 Name: "disabled", URL: "u3", Enabled: false, RefreshMode: "overwrite",
90 ScheduleKind: "daily", CronExpr: "0 3 * * *", OutputDir: "c",
91 NextRunAt: nullTime(now.Add(-time.Hour)),
92 }
93 for _, s := range []*models.Subscription{due, future, disabled} {
94 if err := repo.Create(s); err != nil {
95 t.Fatalf("create %s: %v", s.Name, err)
96 }
97 }
98
99 gotDue, err := repo.GetDue(now)
100 if err != nil {
101 t.Fatalf("get due: %v", err)
102 }
103 if len(gotDue) != 1 || gotDue[0].Name != "due" {
104 names := make([]string, len(gotDue))
105 for i, s := range gotDue {
106 names[i] = s.Name
107 }
108 t.Fatalf("expected only the due subscription, got %v", names)
109 }
110
111 next := now.Add(24 * time.Hour)
112 if err := repo.MarkRun(due.ID, now, next, "queued"); err != nil {
113 t.Fatalf("mark run: %v", err)
114 }
115 after, _ := repo.GetByID(due.ID)
116 if !after.LastRunAt.Valid || after.LastStatus.String != "queued" {
117 t.Errorf("MarkRun not persisted: %+v", after)
118 }
119 // After marking run, it should no longer be due.
120 if gotDue, _ = repo.GetDue(now); len(gotDue) != 0 {
121 t.Errorf("expected no due subscriptions after MarkRun, got %d", len(gotDue))
122 }
123}
124
125func nullTime(t time.Time) sql.NullTime {
126 return sql.NullTime{Time: t, Valid: true}
127}
128
129// The download worker reports a run's outcome through SetLastStatus. It must
130// change only the status: the run times belong to whoever started the run.
131func TestSetLastStatusKeepsRunTimes(t *testing.T) {
132 db := setupTestDB(t)
133 defer db.Close()
134
135 repo := NewSubscriptionRepository(db)
136 now := time.Now().Truncate(time.Second)
137 next := now.Add(24 * time.Hour)
138 sub := &models.Subscription{
139 Name: "s", URL: "u", Enabled: true, RefreshMode: "overwrite",
140 ScheduleKind: "daily", CronExpr: "0 3 * * *", OutputDir: "dir",
141 }
142 if err := repo.Create(sub); err != nil {
143 t.Fatal(err)
144 }
145 if err := repo.MarkRun(sub.ID, now, next, "queued"); err != nil {
146 t.Fatal(err)
147 }
148
149 if err := repo.SetLastStatus(sub.ID, "completed"); err != nil {
150 t.Fatalf("set last status: %v", err)
151 }
152
153 got, err := repo.GetByID(sub.ID)
154 if err != nil {
155 t.Fatal(err)
156 }
157 if got.LastStatus.String != "completed" {
158 t.Errorf("LastStatus = %q, want completed", got.LastStatus.String)
159 }
160 if !got.NextRunAt.Valid || !got.NextRunAt.Time.Equal(next) {
161 t.Errorf("NextRunAt = %v, want it unchanged at %v", got.NextRunAt.Time, next)
162 }
163 if !got.LastRunAt.Valid || !got.LastRunAt.Time.Equal(now) {
164 t.Errorf("LastRunAt = %v, want it unchanged at %v", got.LastRunAt.Time, now)
165 }
166}
167
168// Running a subscription by hand records the run but must not move its
169// schedule: the next scheduled run stays where the cron expression put it.
170func TestMarkManualRunKeepsNextRun(t *testing.T) {
171 db := setupTestDB(t)
172 defer db.Close()
173
174 repo := NewSubscriptionRepository(db)
175 scheduled := time.Now().Add(6 * time.Hour).Truncate(time.Second)
176 sub := &models.Subscription{
177 Name: "s", URL: "u", Enabled: true, RefreshMode: "overwrite",
178 ScheduleKind: "daily", CronExpr: "0 3 * * *", OutputDir: "dir",
179 NextRunAt: nullTime(scheduled),
180 }
181 if err := repo.Create(sub); err != nil {
182 t.Fatal(err)
183 }
184
185 manualAt := time.Now().Truncate(time.Second)
186 if err := repo.MarkManualRun(sub.ID, manualAt, "queued"); err != nil {
187 t.Fatalf("mark manual run: %v", err)
188 }
189
190 got, err := repo.GetByID(sub.ID)
191 if err != nil {
192 t.Fatal(err)
193 }
194 if got.LastStatus.String != "queued" || !got.LastRunAt.Time.Equal(manualAt) {
195 t.Errorf("run not recorded: status=%q last=%v", got.LastStatus.String, got.LastRunAt.Time)
196 }
197 if !got.NextRunAt.Time.Equal(scheduled) {
198 t.Errorf("NextRunAt = %v, want it unchanged at %v", got.NextRunAt.Time, scheduled)
199 }
200}
201