From 395f2f9ae53c8255097a591e5311f2df3273bf97 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 3 Sep 2026 23:25:01 -0400 Subject: [PATCH] test(nativeapi): pin the artwork explain wire format Extend the admin report spec to assert the full stored object and step fields, and add a spec covering the queued object and lastAttemptFailed, so a wrong JSON tag in toExplainDTO fails a test instead of shipping. --- server/nativeapi/artwork_explain_test.go | 46 ++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/server/nativeapi/artwork_explain_test.go b/server/nativeapi/artwork_explain_test.go index d316a3357..4a76be76d 100644 --- a/server/nativeapi/artwork_explain_test.go +++ b/server/nativeapi/artwork_explain_test.go @@ -73,10 +73,11 @@ var _ = Describe("GET /artwork/explain", func() { It("returns the report for an admin", func() { // Storage shape from core/artwork/trace.go's storedStep: single-letter keys, "d" optional. trace := `[{"c":"external:deezer","o":"hit","d":"https://cdn/x.jpg"}]` + attemptedAt := time.Date(2026, 9, 1, 10, 0, 0, 0, time.UTC) Expect(artRepo.PutItemArtwork(&model.ItemArtwork{ ItemKind: model.KindArtistArtwork.Prefix(), ItemID: "ar-1", ImageType: model.ImageTypePrimary, - Hash: "abc", Source: "external:deezer", Trace: trace, - AttemptedAt: time.Date(2026, 9, 1, 10, 0, 0, 0, time.UTC), + Hash: "abc", Source: "external:deezer", SourcePath: "/music/Radiohead/folder.jpg", Trace: trace, + AttemptedAt: attemptedAt, })).To(Succeed()) req := createAuthenticatedRequest("GET", "/artwork/explain?kind=ar&id=ar-1", nil, adminToken) @@ -89,8 +90,47 @@ var _ = Describe("GET /artwork/explain", func() { Expect(got["name"]).To(Equal("Radiohead")) Expect(got["result"]).To(Equal("resolved from external:deezer")) Expect(got["chainOrigin"]).To(ContainSubstring("recorded")) - Expect(got["steps"]).To(HaveLen(1)) Expect(got["config"]).To(HaveKeyWithValue("setting", "ArtistArtPriority")) + + Expect(got["stored"]).To(Equal(map[string]any{ + "source": "external:deezer", + "hash": "abc", + "sourcePath": "/music/Radiohead/folder.jpg", + "attemptedAt": attemptedAt.Format(time.RFC3339), + })) + Expect(got["steps"]).To(Equal([]any{ + map[string]any{"candidate": "external:deezer", "outcome": "hit", "detail": "https://cdn/x.jpg"}, + })) + }) + + It("returns the queue state and the last-attempt trace", func() { + Expect(queueRepo.Enqueue(model.ArtworkQueueItem{ + ItemKind: model.KindArtistArtwork.Prefix(), ItemID: "ar-1", ImageType: model.ImageTypePrimary, + Priority: model.ArtworkPriorityScan, + })).To(Succeed()) + seen, err := queueRepo.Get(model.KindArtistArtwork, "ar-1", model.ImageTypePrimary) + Expect(err).ToNot(HaveOccurred()) + + retryAt := time.Date(2026, 9, 2, 8, 0, 0, 0, time.UTC) + failTrace := `[{"c":"external:deezer","o":"error","d":"timeout"}]` + Expect(queueRepo.MarkFailedIfUnchanged(model.KindArtistArtwork.Prefix(), "ar-1", model.ImageTypePrimary, + seen.RetryAt, retryAt, failTrace)).To(Succeed()) + + req := createAuthenticatedRequest("GET", "/artwork/explain?kind=ar&id=ar-1", nil, adminToken) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + Expect(w.Code).To(Equal(http.StatusOK)) + + var got map[string]any + Expect(json.Unmarshal(w.Body.Bytes(), &got)).To(Succeed()) + Expect(got["queued"]).To(Equal(map[string]any{ + "priority": float64(model.ArtworkPriorityScan), + "attempts": float64(1), + "retryAt": retryAt.Format(time.RFC3339), + })) + Expect(got["lastAttemptFailed"]).To(Equal([]any{ + map[string]any{"candidate": "external:deezer", "outcome": "error", "detail": "timeout"}, + })) }) It("omits empty sections", func() {