subscription_test.go
⎇
Raw
1package service
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7 "time"
8
9 "vidarchive/internal/config"
10 "vidarchive/internal/models"
11 "vidarchive/internal/repository"
12)
13
14func TestComputeNextRun(t *testing.T) {
15 svc := &SubscriptionService{}
16 // A fixed reference time: 2026-06-21 12:30 local.
17 from := time.Date(2026, 6, 21, 12, 30, 0, 0, time.Local)
18
19 cases := []struct {
20 kind string
21 custom string
22 wantErr bool
23 check func(t *testing.T, next time.Time)
24 }{
25 {kind: "hourly", check: func(t *testing.T, next time.Time) {
26 if !next.After(from) || next.Minute() != 0 {
27 t.Errorf("hourly: got %v", next)
28 }
29 }},
30 {kind: "daily", check: func(t *testing.T, next time.Time) {
31 if next.Hour() != 3 || next.Minute() != 0 || !next.After(from) {
32 t.Errorf("daily: got %v", next)
33 }
34 }},
35 {kind: "weekly", check: func(t *testing.T, next time.Time) {
36 if next.Weekday() != time.Sunday || !next.After(from) {
37 t.Errorf("weekly: got %v", next)
38 }
39 }},
40 {kind: "monthly", check: func(t *testing.T, next time.Time) {
41 if next.Day() != 1 || !next.After(from) {
42 t.Errorf("monthly: got %v", next)
43 }
44 }},
45 {kind: "cron", custom: "*/15 * * * *", check: func(t *testing.T, next time.Time) {
46 if next.Minute() != 45 || !next.After(from) {
47 t.Errorf("custom cron */15: got %v", next)
48 }
49 }},
50 {kind: "cron", custom: "not a cron", wantErr: true},
51 {kind: "cron", custom: "", wantErr: true},
52 {kind: "bogus", wantErr: true},
53 }
54
55 for _, c := range cases {
56 sub := &models.Subscription{ScheduleKind: c.kind, CronExpr: c.custom}
57 next, err := svc.ComputeNextRun(sub, from)
58 if c.wantErr {
59 if err == nil {
60 t.Errorf("kind=%q custom=%q: expected error, got %v", c.kind, c.custom, next)
61 }
62 continue
63 }
64 if err != nil {
65 t.Errorf("kind=%q custom=%q: unexpected error: %v", c.kind, c.custom, err)
66 continue
67 }
68 c.check(t, next)
69 }
70}
71
72// Deleting a subscription removes its download-archive file. A later
73// subscription that reuses the id would otherwise silently skip entries it
74// never downloaded.
75func TestSubscriptionDeleteRemovesArchive(t *testing.T) {
76 db := setupTestDB(t)
77 defer db.Close()
78
79 cfg := &config.Config{DataDir: t.TempDir()}
80 svc := NewSubscriptionService(repository.NewSubscriptionRepository(db), cfg)
81
82 sub := &models.Subscription{
83 Name: "Channel",
84 URL: "https://example.com/channel",
85 Enabled: true,
86 RefreshMode: "skip",
87 ScheduleKind: "daily",
88 }
89 if err := svc.Create(sub); err != nil {
90 t.Fatalf("create: %v", err)
91 }
92
93 archive := svc.ArchivePath(sub.ID)
94 if err := os.MkdirAll(filepath.Dir(archive), 0o755); err != nil {
95 t.Fatal(err)
96 }
97 if err := os.WriteFile(archive, []byte("youtube abc123\n"), 0o644); err != nil {
98 t.Fatal(err)
99 }
100
101 if err := svc.Delete(sub.ID); err != nil {
102 t.Fatalf("delete: %v", err)
103 }
104 if _, err := os.Stat(archive); !os.IsNotExist(err) {
105 t.Errorf("archive file still present after delete: %v", err)
106 }
107 if _, err := svc.GetByID(sub.ID); err == nil {
108 t.Error("subscription row still exists after delete")
109 }
110}
111
112// A subscription with no archive file (any mode other than "skip") deletes
113// cleanly rather than reporting the missing file as a failure.
114func TestSubscriptionDeleteWithoutArchive(t *testing.T) {
115 db := setupTestDB(t)
116 defer db.Close()
117
118 svc := NewSubscriptionService(repository.NewSubscriptionRepository(db), &config.Config{DataDir: t.TempDir()})
119 sub := &models.Subscription{
120 Name: "Channel",
121 URL: "https://example.com/channel",
122 RefreshMode: "overwrite",
123 ScheduleKind: "daily",
124 }
125 if err := svc.Create(sub); err != nil {
126 t.Fatalf("create: %v", err)
127 }
128 if err := svc.Delete(sub.ID); err != nil {
129 t.Errorf("delete without an archive file: %v", err)
130 }
131}
132