From 094d560c30f81bb5d11d48cac479f9eea76a09ac Mon Sep 17 00:00:00 2001 From: mkelvers Date: Sat, 27 Jun 2026 22:43:42 +0200 Subject: [PATCH] test: cover error sanitization for metadata and skip segment endpoints --- .../playback/handler/episode_metadata_test.go | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/internal/playback/handler/episode_metadata_test.go b/internal/playback/handler/episode_metadata_test.go index 62736c60..94d8d9c7 100644 --- a/internal/playback/handler/episode_metadata_test.go +++ b/internal/playback/handler/episode_metadata_test.go @@ -1,11 +1,15 @@ package handler import ( + "bytes" "context" "encoding/json" + "errors" + "mal/internal/db" "mal/internal/domain" "net/http" "net/http/httptest" + "strings" "testing" "github.com/gin-gonic/gin" @@ -13,9 +17,13 @@ import ( type episodeMetadataAnimeService struct { domain.AnimePlaybackService + err error } func (s episodeMetadataAnimeService) GetAllEpisodes(context.Context, int) ([]domain.EpisodeData, error) { + if s.err != nil { + return nil, s.err + } return []domain.EpisodeData{ {MalID: 1, Title: "The Beginning"}, {MalID: 2, Title: "The Follow-Up"}, @@ -26,6 +34,14 @@ func (s episodeMetadataAnimeService) GetAnimeByID(context.Context, int) (domain. return domain.Anime{}, nil } +type failingSkipSegmentPlaybackService struct { + domain.PlaybackService +} + +func (s failingSkipSegmentPlaybackService) UpsertSkipSegmentOverride(context.Context, string, int64, int, string, float64, float64) error { + return errors.New("database constraint skip_segments_user_id_fkey failed") +} + func TestEpisodeMetadataRouteReturnsEpisodeTitles(t *testing.T) { t.Parallel() @@ -57,3 +73,56 @@ func TestEpisodeMetadataRouteReturnsEpisodeTitles(t *testing.T) { t.Fatalf("got[0] = %+v, want episode 1 title", got[0]) } } + +func TestEpisodeMetadataRouteHidesServiceError(t *testing.T) { + t.Parallel() + + gin.SetMode(gin.TestMode) + router := gin.New() + h := NewPlaybackHandler(nil, episodeMetadataAnimeService{err: errors.New("upstream returned credentials in URL")}) + h.Register(router) + + req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/api/watch/episodes/123/metadata", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusInternalServerError { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusInternalServerError) + } + body := rec.Body.String() + if !strings.Contains(body, `"error":"failed to load episode metadata"`) { + t.Fatalf("expected stable public error, got %s", body) + } + if strings.Contains(body, "upstream returned credentials") { + t.Fatalf("expected private service error to stay out of response, got %s", body) + } +} + +func TestUpsertSkipSegmentRouteHidesServiceError(t *testing.T) { + t.Parallel() + + gin.SetMode(gin.TestMode) + router := gin.New() + router.Use(func(c *gin.Context) { + c.Set("User", &domain.User{User: db.User{ID: "user-1"}}) + }) + h := NewPlaybackHandler(failingSkipSegmentPlaybackService{}, nil) + h.Register(router) + + body := bytes.NewBufferString(`{"mal_id":123,"episode":1,"skip_type":"op","start_time":10,"end_time":90}`) + req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/api/watch/segments", body) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest) + } + responseBody := rec.Body.String() + if !strings.Contains(responseBody, `"error":"invalid skip segment"`) { + t.Fatalf("expected stable public error, got %s", responseBody) + } + if strings.Contains(responseBody, "database constraint") { + t.Fatalf("expected private service error to stay out of response, got %s", responseBody) + } +}