diff options
Diffstat (limited to 'internal/api/handlers_podcast_test.go')
| -rw-r--r-- | internal/api/handlers_podcast_test.go | 370 |
1 files changed, 0 insertions, 370 deletions
diff --git a/internal/api/handlers_podcast_test.go b/internal/api/handlers_podcast_test.go deleted file mode 100644 index 5f1c73d..0000000 --- a/internal/api/handlers_podcast_test.go +++ /dev/null @@ -1,370 +0,0 @@ -package api - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "codeberg.org/snonux/player/internal" - "codeberg.org/snonux/player/internal/auth" - "codeberg.org/snonux/player/internal/clock" - "codeberg.org/snonux/player/internal/model" - "codeberg.org/snonux/player/internal/probe" - "codeberg.org/snonux/player/internal/repository" - "codeberg.org/snonux/player/internal/service" - "codeberg.org/snonux/player/internal/thumb" -) - -// newPodcastTestServer is like newTestServer but accepts a real podcast service. -func newPodcastTestServer(t *testing.T, store repository.Store, hasher auth.Hasher, sm auth.SessionManager, cfg *internal.Config, - browseSvc service.MediaBrowseService, - writeSvc service.MediaWriteService, - shareSvc service.MediaShareService, - tagSvc service.MediaTagService, - favSvc service.MediaFavoriteService, - noteSvc service.MediaNoteService, - adminSvc service.AdminService, - progressSvc service.ProgressService, - authSvc service.AuthService, - podcastSvc service.PodcastEpisodeService, - fs http.FileSystem, -) *Server { - t.Helper() - if fs == nil { - fs = newTestFS(map[string]string{ - "index.html": "index", - "login.html": "login", - "bootstrap.html": "bootstrap", - "share.html": "share", - }) - } - if authSvc == nil { - authSvc = &service.MockAuthService{ - CountUsersFunc: func(context.Context) (int, error) { return 1, nil }, - GetUserByIDFunc: func(context.Context, int64) (*model.User, error) { return &model.User{ID: 1, IsAdmin: true}, nil }, - } - } - return NewServer(ServerDeps{ - Store: store, - Hasher: hasher, - SessionManager: sm, - Config: cfg, - Services: ServerServices{ - Browse: browseSvc, - Write: writeSvc, - Share: shareSvc, - Tag: tagSvc, - Favorite: favSvc, - Note: noteSvc, - Admin: adminSvc, - Progress: progressSvc, - Auth: authSvc, - Podcast: podcastSvc, - }, - StaticFS: fs, - }) -} - -// setupPodcastE2E creates a full server with a real SQLite store and real services. -func setupPodcastE2E(t *testing.T) (srv *Server, store repository.Store, sm auth.SessionManager, adminID int64, cleanup func()) { - t.Helper() - - dbStore, err := repository.Open(":memory:") - if err != nil { - t.Fatalf("open db: %v", err) - } - - ctx := context.Background() - now := time.Now() - clk := &clock.MockClock{T: now} - - adminID, err = dbStore.CreateUser(ctx, &model.User{ - Username: "admin", - PasswordHash: "hashed", - IsAdmin: true, - CreatedAt: now, - }) - if err != nil { - t.Fatalf("create admin: %v", err) - } - - hasher := &staticHasher{fixed: "hashed"} - sm = auth.NewSessionManager(dbStore, clk, time.Hour) - authSvc := service.NewAuthService(dbStore, clk, hasher, sm) - - mediaRoot := t.TempDir() - helper := service.NewAccessHelper(dbStore) - prober := &probe.MockProber{} - thumbGen := &thumb.MockGenerator{} - - mediaSvc := service.NewMediaService(dbStore, clk, mediaRoot, thumbGen, prober) - podcastSvc := service.NewPodcastService(dbStore, clk, mediaRoot, helper, prober, thumbGen, &http.Client{Timeout: service.DefaultHTTPClientTimeout}, 60) - - cfg := &internal.Config{ - SessionTimeoutHours: 24, - MaxUploadSizeMB: 10, - MediaRoot: mediaRoot, - } - - srv = newPodcastTestServer(t, dbStore, hasher, sm, cfg, mediaSvc, mediaSvc, mediaSvc, mediaSvc, mediaSvc, mediaSvc, nil, nil, authSvc, podcastSvc, nil) - - cleanup = func() { - dbStore.Close() - } - - return srv, dbStore, sm, adminID, cleanup -} - -func TestPodcastE2E_FullFlow(t *testing.T) { - srv, store, sm, adminID, cleanup := setupPodcastE2E(t) - defer cleanup() - - // Audio server serves dummy MP3 bytes. - audioServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "audio/mpeg") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("dummy audio data")) - })) - defer audioServer.Close() - - rssBody := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?> -<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"> - <channel> - <title>Test Podcast</title> - <description>A test podcast</description> - <item> - <title>Episode 1</title> - <guid>ep-1</guid> - <pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate> - <enclosure url="%s/audio.mp3" length="1234" type="audio/mpeg"/> - <itunes:duration>00:05:00</itunes:duration> - </item> - </channel> -</rss>`, audioServer.URL) - - rssServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/rss+xml") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(rssBody)) - })) - defer rssServer.Close() - - secondRSSBody := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?> -<rss version="2.0"> - <channel> - <title>Second Podcast</title> - <description>Another test podcast</description> - <item> - <title>Second Episode</title> - <guid>second-ep-1</guid> - <enclosure url="%s/second.mp3" length="4321" type="audio/mpeg"/> - </item> - </channel> -</rss>`, audioServer.URL) - secondRSSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/rss+xml") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(secondRSSBody)) - })) - defer secondRSSServer.Close() - - badRSSServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("not a valid feed")) - })) - defer badRSSServer.Close() - - cookie := addSessionCookie(t, store, sm, adminID) - - var podcastSetID int64 - var episodeID int64 - var downloadedMediaID int64 - - t.Run("subscribe podcast", func(t *testing.T) { - body := fmt.Sprintf(`{"feed_url":"%s/rss.xml","set_name":"test-podcast"}`, rssServer.URL) - req := httptest.NewRequest(http.MethodPost, "/api/podcasts", strings.NewReader(body)) - req.Header.Set("Content-Type", "application/json") - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusOK { - t.Fatalf("expected %d, got %d: %s", http.StatusOK, rr.Code, rr.Body.String()) - } - - var feed model.PodcastFeed - if err := json.Unmarshal(rr.Body.Bytes(), &feed); err != nil { - t.Fatalf("unmarshal feed: %v", err) - } - if feed.ID <= 0 { - t.Fatalf("expected feed id > 0, got %d", feed.ID) - } - podcastSetID = feed.SetID - }) - - t.Run("subscribe second podcast uses same set", func(t *testing.T) { - body := fmt.Sprintf(`{"feed_url":"%s/rss.xml","set_name":"second-podcast"}`, secondRSSServer.URL) - req := httptest.NewRequest(http.MethodPost, "/api/podcasts", strings.NewReader(body)) - req.Header.Set("Content-Type", "application/json") - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusOK { - t.Fatalf("expected %d, got %d: %s", http.StatusOK, rr.Code, rr.Body.String()) - } - - var feed model.PodcastFeed - if err := json.Unmarshal(rr.Body.Bytes(), &feed); err != nil { - t.Fatalf("unmarshal feed: %v", err) - } - if feed.SetID != podcastSetID { - t.Fatalf("expected set_id %d, got %d", podcastSetID, feed.SetID) - } - }) - - t.Run("list podcasts", func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/api/podcasts", nil) - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusOK { - t.Fatalf("expected %d, got %d: %s", http.StatusOK, rr.Code, rr.Body.String()) - } - - var feeds []model.PodcastFeed - if err := json.Unmarshal(rr.Body.Bytes(), &feeds); err != nil { - t.Fatalf("unmarshal feeds: %v", err) - } - - if len(feeds) != 2 { - t.Fatalf("expected 2 podcast feeds, got %d", len(feeds)) - } - for _, feed := range feeds { - if feed.SetID != podcastSetID { - t.Fatalf("expected all feeds in set %d, got feed %+v", podcastSetID, feed) - } - } - }) - - t.Run("list episodes", func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/podcasts/%d/episodes", podcastSetID), nil) - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusOK { - t.Fatalf("expected %d, got %d: %s", http.StatusOK, rr.Code, rr.Body.String()) - } - - var episodes []model.PodcastEpisodeWithStatus - if err := json.Unmarshal(rr.Body.Bytes(), &episodes); err != nil { - t.Fatalf("unmarshal episodes: %v", err) - } - if len(episodes) < 1 { - t.Fatalf("expected at least 1 episode, got %d", len(episodes)) - } - episodeID = episodes[0].ID - }) - - t.Run("download episode", func(t *testing.T) { - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/podcasts/episodes/%d/download", episodeID), nil) - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusOK { - t.Fatalf("expected %d, got %d: %s", http.StatusOK, rr.Code, rr.Body.String()) - } - - var media model.Media - if err := json.Unmarshal(rr.Body.Bytes(), &media); err != nil { - t.Fatalf("unmarshal media: %v", err) - } - if media.ID <= 0 { - t.Fatalf("expected media id > 0, got %d", media.ID) - } - downloadedMediaID = media.ID - }) - - t.Run("toggle complete", func(t *testing.T) { - req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/podcasts/episodes/%d/complete", episodeID), nil) - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusNoContent { - t.Fatalf("expected %d, got %d", http.StatusNoContent, rr.Code) - } - }) - - t.Run("media list includes downloaded episode", func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/api/media", nil) - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusOK { - t.Fatalf("expected %d, got %d: %s", http.StatusOK, rr.Code, rr.Body.String()) - } - - var mediaList []model.Media - if err := json.Unmarshal(rr.Body.Bytes(), &mediaList); err != nil { - t.Fatalf("unmarshal media list: %v", err) - } - - found := false - for _, m := range mediaList { - if m.ID == downloadedMediaID { - found = true - break - } - } - if !found { - t.Fatalf("expected downloaded media %d in list", downloadedMediaID) - } - }) - - // Error cases. - t.Run("subscribe invalid url", func(t *testing.T) { - body := fmt.Sprintf(`{"feed_url":"%s","set_name":"bad"}`, badRSSServer.URL) - req := httptest.NewRequest(http.MethodPost, "/api/podcasts", strings.NewReader(body)) - req.Header.Set("Content-Type", "application/json") - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusBadRequest { - t.Fatalf("expected %d, got %d", http.StatusBadRequest, rr.Code) - } - }) - - t.Run("episodes non-existent set", func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/api/podcasts/99999/episodes", nil) - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusNotFound { - t.Fatalf("expected %d, got %d", http.StatusNotFound, rr.Code) - } - }) - - t.Run("download non-existent episode", func(t *testing.T) { - req := httptest.NewRequest(http.MethodPost, "/api/podcasts/episodes/99999/download", nil) - req.AddCookie(cookie) - rr := httptest.NewRecorder() - srv.ServeHTTP(rr, req) - - if rr.Code != http.StatusNotFound { - t.Fatalf("expected %d, got %d", http.StatusNotFound, rr.Code) - } - }) -} |
