fix(artwork): set Walked before the Explainable gate, fix zero-time ChainOrigin

Explain now sets rep.Walked ahead of the !Explainable early return, matching
the pre-refactor CLI's unconditional flag (e.g. --live on a playlist), and
ChainOrigin renders a zero AttemptedAt as "-" like the CLI's formatTime did.
This commit is contained in:
Deluan 2026-09-03 23:10:46 -04:00
commit 03743a030b
2 changed files with 41 additions and 2 deletions

View file

@ -131,6 +131,9 @@ func Explain(ctx context.Context, ds model.DataStore, ag *agents.Agents, kind mo
return ExplainReport{}, fmt.Errorf("reading the artwork queue: %w", err)
}
}
// Set even for a kind the chain never walks, so a caller that asked to walk (e.g. --live on a
// playlist) is reported as having tried, matching the pre-refactor CLI's unconditional flag.
rep.Walked = opts.Walk != nil
if !Explainable(kind) {
return rep, nil
}
@ -138,7 +141,6 @@ func Explain(ctx context.Context, ds model.DataStore, ag *agents.Agents, kind mo
rep.Agents = FormatAgents(conf.Server.Agents, ImageAgentNames(ag, kind), opts.UnavailableNote)
}
rep.Walked = opts.Walk != nil
switch {
case rep.Walked:
trace := &ChainTrace{}
@ -161,11 +163,19 @@ func (r ExplainReport) ChainOrigin() string {
return "walked now"
}
if r.Stored != nil {
return "recorded " + r.Stored.AttemptedAt.Format(time.RFC3339)
return "recorded " + formatAttemptedAt(r.Stored.AttemptedAt)
}
return "not recorded"
}
// formatAttemptedAt matches the CLI's formatTime: a zero time reads as unset, not as year 1.
func formatAttemptedAt(t time.Time) string {
if t.IsZero() {
return "-"
}
return t.Format(time.RFC3339)
}
// LastAttemptFailed decodes why the queued row's last attempt failed, if there is one queued.
func (r ExplainReport) LastAttemptFailed() []TraceStep {
if r.Queued == nil {

View file

@ -133,6 +133,11 @@ var _ = Describe("Explain", func() {
Expect(rep.ChainOrigin()).To(ContainSubstring("recorded"))
})
It("renders a zero attempted-at the same way the CLI's formatTime does", func() {
rep := artwork.ExplainReport{Stored: &model.ItemArtwork{Source: "folder"}}
Expect(rep.ChainOrigin()).To(Equal("recorded -"))
})
It("reports nothing recorded when there is no stored state", func() {
rep, err := artwork.Explain(ctx, ds, nil, model.KindArtistArtwork, "ar-1", artwork.ExplainOptions{})
Expect(err).ToNot(HaveOccurred())
@ -166,4 +171,28 @@ var _ = Describe("Explain", func() {
Expect(rep.Stored).To(BeNil())
Expect(rep.Steps).To(BeEmpty())
})
It("performs a fresh walk and reports its outcome, including a failed one, when a walker is supplied", func() {
// GetAll erroring mid-walk is a deterministic way to force ResolveErr without a real library.
ds.Album(ctx).(*tests.MockAlbumRepo).SetError(true)
opts := artwork.ExplainOptions{Walk: func(t *artwork.ChainTrace) *artwork.TracingResolver {
return artwork.NewTracingResolver(ds, nil, nil, t, false)
}}
rep, err := artwork.Explain(ctx, ds, nil, model.KindArtistArtwork, "ar-1", opts)
Expect(err).ToNot(HaveOccurred())
Expect(rep.Walked).To(BeTrue())
Expect(rep.ResolveErr).To(HaveOccurred())
})
It("still reports Walked for a kind with no chain to walk, when a walker is supplied", func() {
Expect(ds.Playlist(ctx).Put(&model.Playlist{ID: "pl-1", Name: "Favorites"})).To(Succeed())
opts := artwork.ExplainOptions{Walk: func(*artwork.ChainTrace) *artwork.TracingResolver {
panic("a kind that cannot walk must never be handed to the walker")
}}
rep, err := artwork.Explain(ctx, ds, nil, model.KindPlaylistArtwork, "pl-1", opts)
Expect(err).ToNot(HaveOccurred())
Expect(rep.Walked).To(BeTrue())
})
})