package server import ( "net/http" "net/http/httptest" "net/url" "strings" "testing" ) // createSubscription posts the create form and returns the id of the new row. // The test server's worker pool is never started, so a queued run stays queued — // which is exactly the state the duplicate-run guard looks for. func createSubscription(t *testing.T, router http.Handler) string { t.Helper() w := postForm(router, "/subscriptions", url.Values{ "name": {"My Sub"}, "url": {"https://example.com/playlist"}, "output_dir": {"subs/mine"}, "refresh_mode": {"overwrite"}, "schedule_kind": {"daily"}, }) if w.Code != http.StatusSeeOther { t.Fatalf("create subscription: expected 303, got %d", w.Code) } // The delete form on the listing carries the id. req := httptest.NewRequest("GET", "/subscriptions", nil) lw := httptest.NewRecorder() router.ServeHTTP(lw, req) body := lw.Body.String() const marker = `action="/subscriptions/` i := strings.Index(body, marker) if i < 0 { t.Fatal("no subscription id found on the listing") } rest := body[i+len(marker):] id := rest[:strings.IndexAny(rest, `/"`)] if id == "" { t.Fatal("empty subscription id") } return id } // Running a subscription by hand queues one run and records it. func TestRunSubscriptionQueuesOnce(t *testing.T) { srv, _, cleanup := setupTestServer(t) defer cleanup() router := srv.Router() id := createSubscription(t, router) w := postForm(router, "/subscriptions/"+id+"/run", nil) if w.Code != http.StatusSeeOther { t.Fatalf("run: expected 303, got %d", w.Code) } if hasErrorFlash(w) { t.Error("first run reported an error") } // The queue now holds the run, and the subscription reports it. req := httptest.NewRequest("GET", "/queue", nil) qw := httptest.NewRecorder() router.ServeHTTP(qw, req) if !strings.Contains(qw.Body.String(), "https://example.com/playlist") { t.Error("queue page does not show the subscription run") } req = httptest.NewRequest("GET", "/subscriptions", nil) sw := httptest.NewRecorder() router.ServeHTTP(sw, req) if !strings.Contains(sw.Body.String(), "queued") { t.Error("subscriptions page does not show the run status") } } // A second "Run now" while the first is still queued must be refused. Both runs // share the subscription's output directory, so in overwrite mode they would // race: one deletes the item the other just imported. func TestRunSubscriptionRefusesConcurrentRun(t *testing.T) { srv, _, cleanup := setupTestServer(t) defer cleanup() router := srv.Router() id := createSubscription(t, router) if w := postForm(router, "/subscriptions/"+id+"/run", nil); hasErrorFlash(w) { t.Fatal("first run reported an error") } second := postForm(router, "/subscriptions/"+id+"/run", nil) if second.Code != http.StatusSeeOther { t.Fatalf("second run: expected 303, got %d", second.Code) } if !hasErrorFlash(second) { t.Error("second run was accepted while the first was still queued") } // Still exactly one queued download for that URL. req := httptest.NewRequest("GET", "/queue", nil) qw := httptest.NewRecorder() router.ServeHTTP(qw, req) if n := strings.Count(qw.Body.String(), "https://example.com/playlist"); n != 1 { t.Errorf("queue shows %d runs, want 1", n) } } func TestRunUnknownSubscriptionIsNotFound(t *testing.T) { srv, _, cleanup := setupTestServer(t) defer cleanup() w := postForm(srv.Router(), "/subscriptions/4242/run", nil) if w.Code != http.StatusNotFound { t.Errorf("expected 404 for an unknown subscription, got %d", w.Code) } }