mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 10:27:08 +02:00
refactor(artwork): export the trace outcome vocabulary
The CLI hardcoded the outcome literals and the "external:" prefix, so renaming a constant's value in core/artwork left cmd compiling and the suite green while `artwork explain` silently degraded its verdict. Renaming a value now fails the golden vocabulary test in core/artwork and the explainResult tests in cmd.
This commit is contained in:
parent
3ca194c887
commit
a1b9976521
7 changed files with 92 additions and 72 deletions
|
|
@ -517,10 +517,10 @@ func walksPriorityChain(kind model.Kind) bool {
|
|||
func explainResult(source string, steps []artwork.TraceStep) string {
|
||||
if source != "" {
|
||||
for _, s := range steps {
|
||||
if s.Outcome == "hit" {
|
||||
if s.Outcome == artwork.OutcomeHit {
|
||||
break
|
||||
}
|
||||
if s.Outcome == "would-try" {
|
||||
if s.Outcome == artwork.OutcomeWouldTry {
|
||||
return "resolved from " + source +
|
||||
" (offline: a higher-priority external candidate was not tried; re-run with --live)"
|
||||
}
|
||||
|
|
@ -529,9 +529,9 @@ func explainResult(source string, steps []artwork.TraceStep) string {
|
|||
}
|
||||
for _, s := range steps {
|
||||
switch {
|
||||
case s.Outcome == "would-try":
|
||||
case s.Outcome == artwork.OutcomeWouldTry:
|
||||
return "indeterminate (external agents not called; re-run with --live)"
|
||||
case s.Outcome == "error" && strings.HasPrefix(s.Candidate, "external:"):
|
||||
case s.Outcome == artwork.OutcomeError && strings.HasPrefix(s.Candidate, artwork.ExternalPrefix):
|
||||
return "indeterminate (an external lookup failed; the item may resolve on a later attempt)"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,13 +47,13 @@ func fetchArtistImage(ctx context.Context, ag *agents.Agents, gate gateFunc, ar
|
|||
// Synthetic artists would otherwise get an unrelated agent result assigned to them.
|
||||
switch ar.ID {
|
||||
case consts.UnknownArtistID, consts.VariousArtistsID:
|
||||
traceFrom(ctx).add(traceStep{Candidate: externalCandidate, Outcome: outcomeSkipped, Detail: "synthetic artist"})
|
||||
traceFrom(ctx).add(traceStep{Candidate: ExternalCandidate, Outcome: OutcomeSkipped, Detail: "synthetic artist"})
|
||||
return nil, "", false
|
||||
}
|
||||
name := externalName(ar.Name)
|
||||
imageAgents := ag.ArtistImageAgents()
|
||||
if len(imageAgents) == 0 {
|
||||
traceFrom(ctx).add(traceStep{Candidate: externalCandidate, Outcome: outcomeSkipped,
|
||||
traceFrom(ctx).add(traceStep{Candidate: ExternalCandidate, Outcome: OutcomeSkipped,
|
||||
Detail: "no enabled agent provides artist images"})
|
||||
return nil, "", false
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ func fetchAlbumImage(ctx context.Context, ag *agents.Agents, gate gateFunc, al m
|
|||
name, artist := externalName(al.Name), externalName(al.AlbumArtist)
|
||||
imageAgents := ag.AlbumImageAgents()
|
||||
if len(imageAgents) == 0 {
|
||||
traceFrom(ctx).add(traceStep{Candidate: externalCandidate, Outcome: outcomeSkipped,
|
||||
traceFrom(ctx).add(traceStep{Candidate: ExternalCandidate, Outcome: OutcomeSkipped,
|
||||
Detail: "no enabled agent provides album images"})
|
||||
return nil, "", false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ var _ = Describe("agent images", func() {
|
|||
r, _, extErr := fetchArtistImage(withTrace(ctx, t), ag, passthroughGate, model.Artist{ID: "ar1"})
|
||||
Expect(r).To(BeNil())
|
||||
Expect(extErr).To(BeFalse())
|
||||
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "external", Outcome: outcomeSkipped,
|
||||
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "external", Outcome: OutcomeSkipped,
|
||||
Detail: "no enabled agent provides artist images"}}),
|
||||
"a configured external token must never be silently absent from the chain")
|
||||
})
|
||||
|
|
@ -193,7 +193,7 @@ var _ = Describe("agent images", func() {
|
|||
_, _, _ = fetchArtistImage(withTrace(ctx, t), ag, passthroughGate,
|
||||
model.Artist{ID: consts.VariousArtistsID, Name: "Various Artists"})
|
||||
Expect(t.Steps()).To(HaveLen(1))
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(outcomeSkipped))
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(OutcomeSkipped))
|
||||
Expect(t.Steps()[0].Detail).To(ContainSubstring("synthetic"))
|
||||
})
|
||||
|
||||
|
|
@ -262,7 +262,7 @@ var _ = Describe("agent images", func() {
|
|||
r, _, extErr := fetchAlbumImage(withTrace(ctx, t), ag, passthroughGate, model.Album{Name: "Album"})
|
||||
Expect(r).To(BeNil())
|
||||
Expect(extErr).To(BeFalse())
|
||||
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "external", Outcome: outcomeSkipped,
|
||||
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "external", Outcome: OutcomeSkipped,
|
||||
Detail: "no enabled agent provides album images"}}),
|
||||
"a configured external token must never be silently absent from the chain")
|
||||
})
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ func (d *discArtworkReader) fromDiscArtPriority(ctx context.Context, ffmpeg ffmp
|
|||
fromTag(ctx, d.lib.FS, d.firstTrackRel),
|
||||
fromFFmpegTag(ctx, ffmpeg, d.lib.Abs(d.firstTrackRel)),
|
||||
)
|
||||
case pattern == "external":
|
||||
case pattern == ExternalCandidate:
|
||||
// Not supported for disc art, silently ignore
|
||||
case pattern == "discsubtitle":
|
||||
if subtitle := strings.TrimSpace(d.album.Discs[d.discNumber]); subtitle != "" {
|
||||
|
|
|
|||
|
|
@ -43,21 +43,21 @@ type chainState struct {
|
|||
func (c *chainState) try(candidate string, res resolution, ok bool) (resolution, bool) {
|
||||
if ok {
|
||||
res.extError = c.extErr
|
||||
c.record(candidate, outcomeHit, res.sourcePath)
|
||||
c.record(candidate, OutcomeHit, res.sourcePath)
|
||||
return res, true
|
||||
}
|
||||
c.localErr = c.localErr || res.localError
|
||||
if res.localError {
|
||||
c.record(candidate, outcomeUnreadable, "")
|
||||
c.record(candidate, OutcomeUnreadable, "")
|
||||
} else {
|
||||
c.record(candidate, outcomeMiss, "")
|
||||
c.record(candidate, OutcomeMiss, "")
|
||||
}
|
||||
return resolution{}, false
|
||||
}
|
||||
|
||||
func (c *chainState) record(candidate, outcome, detail string) {
|
||||
func (c *chainState) record(candidate string, out Outcome, detail string) {
|
||||
if c.trace != nil {
|
||||
c.trace.add(traceStep{Candidate: candidate, Outcome: outcome, Detail: detail})
|
||||
c.trace.add(traceStep{Candidate: candidate, Outcome: out, Detail: detail})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -153,9 +153,9 @@ func (r *resolver) resolveAlbum(ctx context.Context, albumID string) (resolution
|
|||
if res, ok = chain.try(pattern, res, ok); ok {
|
||||
return res, nil
|
||||
}
|
||||
case pattern == "external":
|
||||
case pattern == ExternalCandidate:
|
||||
if rd, name, isErr := r.fetchExternalAlbum(ctx, *al); rd != nil {
|
||||
return resolution{reader: rd, source: "external:" + name}, nil
|
||||
return resolution{reader: rd, source: ExternalPrefix + name}, nil
|
||||
} else if isErr {
|
||||
chain.extErr = true
|
||||
}
|
||||
|
|
@ -165,7 +165,7 @@ func (r *resolver) resolveAlbum(ctx context.Context, albumID string) (resolution
|
|||
return res, nil
|
||||
}
|
||||
default:
|
||||
chain.record(pattern, outcomeSkipped, "no images in album folder")
|
||||
chain.record(pattern, OutcomeSkipped, "no images in album folder")
|
||||
}
|
||||
}
|
||||
return chain.exhausted(), nil
|
||||
|
|
@ -220,9 +220,9 @@ func (r *resolver) resolveArtist(ctx context.Context, artistID string) (resoluti
|
|||
continue
|
||||
}
|
||||
switch {
|
||||
case pattern == "external":
|
||||
case pattern == ExternalCandidate:
|
||||
if rd, name, isErr := r.fetchExternalArtist(ctx, *ar); rd != nil {
|
||||
return resolution{reader: rd, source: "external:" + name}, nil
|
||||
return resolution{reader: rd, source: ExternalPrefix + name}, nil
|
||||
} else if isErr {
|
||||
chain.extErr = true
|
||||
}
|
||||
|
|
@ -233,7 +233,7 @@ func (r *resolver) resolveArtist(ctx context.Context, artistID string) (resoluti
|
|||
}
|
||||
case strings.HasPrefix(pattern, "album/"):
|
||||
if lib.FS == nil {
|
||||
chain.record(pattern, outcomeSkipped, "artist has no albums")
|
||||
chain.record(pattern, OutcomeSkipped, "artist has no albums")
|
||||
continue
|
||||
}
|
||||
res, ok := resolveFolderFile(ctx, lib, imgFiles, strings.TrimPrefix(pattern, "album/"))
|
||||
|
|
@ -242,11 +242,11 @@ func (r *resolver) resolveArtist(ctx context.Context, artistID string) (resoluti
|
|||
}
|
||||
default:
|
||||
if lib.FS == nil {
|
||||
chain.record(pattern, outcomeSkipped, "artist has no albums")
|
||||
chain.record(pattern, OutcomeSkipped, "artist has no albums")
|
||||
continue
|
||||
}
|
||||
if artistFolder == "" {
|
||||
chain.record(pattern, outcomeSkipped, "no artist folder")
|
||||
chain.record(pattern, OutcomeSkipped, "no artist folder")
|
||||
continue
|
||||
}
|
||||
res, ok := resolveArtistFolderPattern(ctx, lib, artistFolder, pattern)
|
||||
|
|
@ -387,7 +387,7 @@ func (r *resolver) resolveMediaFile(ctx context.Context, id string) (resolution,
|
|||
func resolveExternalStep(gate gateFunc, name string, sf sourceFunc) (res resolution, ok bool, extErr bool) {
|
||||
r, path, err := gate(name, sf)
|
||||
if r != nil {
|
||||
return resolution{reader: r, source: "external", sourcePath: path}, true, false
|
||||
return resolution{reader: r, source: ExternalCandidate, sourcePath: path}, true, false
|
||||
}
|
||||
return resolution{}, false, err != nil && !errors.Is(err, model.ErrNotFound)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,23 +8,31 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
// Outcome is what the priority chain observed for one candidate. The CLI branches on these,
|
||||
// so they are part of the package's API, not private labels.
|
||||
type Outcome string
|
||||
|
||||
const (
|
||||
outcomeHit = "hit"
|
||||
outcomeMiss = "miss"
|
||||
outcomeUnreadable = "unreadable"
|
||||
outcomeSkipped = "skipped"
|
||||
outcomeWouldTry = "would-try"
|
||||
outcomeError = "error"
|
||||
outcomeNotReached = "not-reached"
|
||||
OutcomeHit Outcome = "hit"
|
||||
OutcomeMiss Outcome = "miss"
|
||||
OutcomeUnreadable Outcome = "unreadable"
|
||||
OutcomeSkipped Outcome = "skipped"
|
||||
OutcomeWouldTry Outcome = "would-try"
|
||||
OutcomeError Outcome = "error"
|
||||
outcomeNotReached Outcome = "not-reached"
|
||||
)
|
||||
|
||||
// externalCandidate labels the external tier itself, for the cases that never reach an agent.
|
||||
const externalCandidate = "external"
|
||||
const (
|
||||
// ExternalCandidate labels the external tier itself, for the cases that never reach an agent.
|
||||
ExternalCandidate = "external"
|
||||
// ExternalPrefix qualifies a candidate or a stored source with the agent that produced it.
|
||||
ExternalPrefix = ExternalCandidate + ":"
|
||||
)
|
||||
|
||||
// traceStep is one candidate the priority chain considered.
|
||||
type traceStep struct {
|
||||
Candidate string
|
||||
Outcome string
|
||||
Outcome Outcome
|
||||
Detail string
|
||||
}
|
||||
|
||||
|
|
@ -67,16 +75,16 @@ var errOfflineSkipped = errors.New("artwork: external lookup skipped (offline)")
|
|||
func tracingGate(t *chainTrace, inner gateFunc) gateFunc {
|
||||
return func(name string, f func() (io.ReadCloser, string, error)) (io.ReadCloser, string, error) {
|
||||
r, path, err := inner(name, f)
|
||||
candidate := "external:" + name
|
||||
candidate := ExternalPrefix + name
|
||||
switch {
|
||||
case r != nil:
|
||||
t.add(traceStep{Candidate: candidate, Outcome: outcomeHit, Detail: path})
|
||||
t.add(traceStep{Candidate: candidate, Outcome: OutcomeHit, Detail: path})
|
||||
case errors.Is(err, errBreakerOpen):
|
||||
t.add(traceStep{Candidate: candidate, Outcome: outcomeSkipped, Detail: "circuit breaker open"})
|
||||
t.add(traceStep{Candidate: candidate, Outcome: OutcomeSkipped, Detail: "circuit breaker open"})
|
||||
case isTransientExternal(err):
|
||||
t.add(traceStep{Candidate: candidate, Outcome: outcomeError, Detail: err.Error()})
|
||||
t.add(traceStep{Candidate: candidate, Outcome: OutcomeError, Detail: err.Error()})
|
||||
default:
|
||||
t.add(traceStep{Candidate: candidate, Outcome: outcomeMiss})
|
||||
t.add(traceStep{Candidate: candidate, Outcome: OutcomeMiss})
|
||||
}
|
||||
return r, path, err
|
||||
}
|
||||
|
|
@ -86,7 +94,7 @@ func tracingGate(t *chainTrace, inner gateFunc) gateFunc {
|
|||
// command cannot add load to a provider that is already rate-limiting us.
|
||||
func offlineGate(t *chainTrace) gateFunc {
|
||||
return func(name string, _ func() (io.ReadCloser, string, error)) (io.ReadCloser, string, error) {
|
||||
t.add(traceStep{Candidate: "external:" + name, Outcome: outcomeWouldTry})
|
||||
t.add(traceStep{Candidate: ExternalPrefix + name, Outcome: OutcomeWouldTry})
|
||||
return nil, "", errOfflineSkipped
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,18 @@ import (
|
|||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("trace vocabulary", func() {
|
||||
// The CLI renders these verbatim and branches on them; a value change is a change to
|
||||
// what `artwork explain` tells an operator, so it must be made deliberately.
|
||||
It("pins the wire values the CLI reads", func() {
|
||||
Expect([]Outcome{
|
||||
OutcomeHit, OutcomeMiss, OutcomeUnreadable, OutcomeSkipped, OutcomeWouldTry, OutcomeError,
|
||||
}).To(Equal([]Outcome{"hit", "miss", "unreadable", "skipped", "would-try", "error"}))
|
||||
Expect(ExternalCandidate).To(Equal("external"))
|
||||
Expect(ExternalPrefix).To(Equal("external:"))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("chainTrace", func() {
|
||||
It("returns nil when no trace is attached", func() {
|
||||
Expect(traceFrom(context.Background())).To(BeNil())
|
||||
|
|
@ -28,12 +40,12 @@ var _ = Describe("chainTrace", func() {
|
|||
t := &chainTrace{}
|
||||
ctx := withTrace(context.Background(), t)
|
||||
|
||||
traceFrom(ctx).add(traceStep{Candidate: "cover.*", Outcome: outcomeMiss})
|
||||
traceFrom(ctx).add(traceStep{Candidate: "embedded", Outcome: outcomeHit, Detail: "/music/a.flac"})
|
||||
traceFrom(ctx).add(traceStep{Candidate: "cover.*", Outcome: OutcomeMiss})
|
||||
traceFrom(ctx).add(traceStep{Candidate: "embedded", Outcome: OutcomeHit, Detail: "/music/a.flac"})
|
||||
|
||||
Expect(t.Steps()).To(Equal([]traceStep{
|
||||
{Candidate: "cover.*", Outcome: outcomeMiss},
|
||||
{Candidate: "embedded", Outcome: outcomeHit, Detail: "/music/a.flac"},
|
||||
{Candidate: "cover.*", Outcome: OutcomeMiss},
|
||||
{Candidate: "embedded", Outcome: OutcomeHit, Detail: "/music/a.flac"},
|
||||
}))
|
||||
|
||||
s := t.Steps()
|
||||
|
|
@ -43,7 +55,7 @@ var _ = Describe("chainTrace", func() {
|
|||
|
||||
It("does not panic when the trace is nil", func() {
|
||||
var t *chainTrace
|
||||
Expect(func() { t.add(traceStep{Candidate: "cover.*", Outcome: outcomeMiss}) }).ToNot(Panic())
|
||||
Expect(func() { t.add(traceStep{Candidate: "cover.*", Outcome: OutcomeMiss}) }).ToNot(Panic())
|
||||
})
|
||||
|
||||
It("is safe to use concurrently", func() {
|
||||
|
|
@ -52,7 +64,7 @@ var _ = Describe("chainTrace", func() {
|
|||
for range 10 {
|
||||
go func() {
|
||||
defer GinkgoRecover()
|
||||
t.add(traceStep{Candidate: "x", Outcome: outcomeMiss})
|
||||
t.add(traceStep{Candidate: "x", Outcome: OutcomeMiss})
|
||||
done <- struct{}{}
|
||||
}()
|
||||
}
|
||||
|
|
@ -71,7 +83,7 @@ var _ = Describe("chainState tracing", func() {
|
|||
_, ok := c.try("cover.*", resolution{}, false)
|
||||
|
||||
Expect(ok).To(BeFalse())
|
||||
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "cover.*", Outcome: outcomeMiss}}))
|
||||
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "cover.*", Outcome: OutcomeMiss}}))
|
||||
})
|
||||
|
||||
It("records unreadable when the candidate existed but could not be read", func() {
|
||||
|
|
@ -82,7 +94,7 @@ var _ = Describe("chainState tracing", func() {
|
|||
|
||||
Expect(ok).To(BeFalse())
|
||||
Expect(t.Steps()).To(HaveLen(1))
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(outcomeUnreadable),
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(OutcomeUnreadable),
|
||||
"a candidate that existed and failed to decode must be distinguishable from one that was absent")
|
||||
})
|
||||
|
||||
|
|
@ -95,7 +107,7 @@ var _ = Describe("chainState tracing", func() {
|
|||
Expect(ok).To(BeTrue())
|
||||
Expect(res.source).To(Equal("embedded"))
|
||||
Expect(t.Steps()).To(Equal([]traceStep{
|
||||
{Candidate: "embedded", Outcome: outcomeHit, Detail: "/music/a.flac"},
|
||||
{Candidate: "embedded", Outcome: OutcomeHit, Detail: "/music/a.flac"},
|
||||
}))
|
||||
})
|
||||
|
||||
|
|
@ -121,28 +133,28 @@ var _ = Describe("external gate tracing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(r).ToNot(BeNil())
|
||||
Expect(t.Steps()).To(Equal([]traceStep{
|
||||
{Candidate: "external:deezer", Outcome: outcomeHit, Detail: "http://img"},
|
||||
{Candidate: "external:deezer", Outcome: OutcomeHit, Detail: "http://img"},
|
||||
}))
|
||||
})
|
||||
|
||||
It("records a miss for a not-found", func() {
|
||||
t := &chainTrace{}
|
||||
_, _, _ = tracingGate(t, passthroughGate)("deezer", miss)
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(outcomeMiss))
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(OutcomeMiss))
|
||||
})
|
||||
|
||||
It("records a miss for a model not-found", func() {
|
||||
t := &chainTrace{}
|
||||
notFound := func() (io.ReadCloser, string, error) { return nil, "", model.ErrNotFound }
|
||||
_, _, _ = tracingGate(t, passthroughGate)("deezer", notFound)
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(outcomeMiss),
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(OutcomeMiss),
|
||||
"both not-found flavours are definitive answers, not faults")
|
||||
})
|
||||
|
||||
It("records an error with its reason", func() {
|
||||
t := &chainTrace{}
|
||||
_, _, _ = tracingGate(t, passthroughGate)("apple-music", boom)
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(outcomeError))
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(OutcomeError))
|
||||
Expect(t.Steps()[0].Detail).To(ContainSubstring("429"))
|
||||
})
|
||||
|
||||
|
|
@ -152,7 +164,7 @@ var _ = Describe("external gate tracing", func() {
|
|||
return nil, "", errBreakerOpen
|
||||
}
|
||||
_, _, _ = tracingGate(t, open)("apple-music", hit)
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(outcomeSkipped))
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(OutcomeSkipped))
|
||||
Expect(t.Steps()[0].Detail).To(ContainSubstring("circuit breaker"))
|
||||
})
|
||||
|
||||
|
|
@ -169,7 +181,7 @@ var _ = Describe("external gate tracing", func() {
|
|||
Expect(called).To(BeFalse(), "offline mode must not perform external requests")
|
||||
Expect(err).To(MatchError(errOfflineSkipped))
|
||||
Expect(t.Steps()).To(Equal([]traceStep{
|
||||
{Candidate: "external:deezer", Outcome: outcomeWouldTry},
|
||||
{Candidate: "external:deezer", Outcome: OutcomeWouldTry},
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
|
@ -216,10 +228,10 @@ var _ = Describe("resolveAlbum tracing", func() {
|
|||
defer res.reader.Close()
|
||||
Expect(t.Steps()).To(HaveLen(2), "a configured pattern must appear even when the chain never evaluated it")
|
||||
Expect(t.Steps()[0]).To(Equal(traceStep{
|
||||
Candidate: "cover.jpg", Outcome: outcomeSkipped, Detail: "no images in album folder",
|
||||
Candidate: "cover.jpg", Outcome: OutcomeSkipped, Detail: "no images in album folder",
|
||||
}))
|
||||
Expect(t.Steps()[1].Candidate).To(Equal("embedded"))
|
||||
Expect(t.Steps()[1].Outcome).To(Equal(outcomeHit))
|
||||
Expect(t.Steps()[1].Outcome).To(Equal(OutcomeHit))
|
||||
})
|
||||
|
||||
It("records an evaluated pattern that matched nothing as a miss, not a skip", func() {
|
||||
|
|
@ -235,7 +247,7 @@ var _ = Describe("resolveAlbum tracing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(res.reader).ToNot(BeNil())
|
||||
defer res.reader.Close()
|
||||
Expect(t.Steps()[0]).To(Equal(traceStep{Candidate: "cover.jpg", Outcome: outcomeMiss}),
|
||||
Expect(t.Steps()[0]).To(Equal(traceStep{Candidate: "cover.jpg", Outcome: OutcomeMiss}),
|
||||
"the folder was searched and held no cover.jpg, which is not the same as never looking")
|
||||
})
|
||||
|
||||
|
|
@ -246,7 +258,7 @@ var _ = Describe("resolveAlbum tracing", func() {
|
|||
_, err := newResolver(ds, ag, ffm, nil).resolve(ctx, model.ArtworkQueueItem{ItemKind: "al", ItemID: "al2"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(t.Steps()).To(Equal([]traceStep{
|
||||
{Candidate: "cover.jpg", Outcome: outcomeSkipped, Detail: "no images in album folder"},
|
||||
{Candidate: "cover.jpg", Outcome: OutcomeSkipped, Detail: "no images in album folder"},
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
|
@ -303,7 +315,7 @@ var _ = Describe("resolveArtist tracing", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(res.reader).ToNot(BeNil())
|
||||
defer res.reader.Close()
|
||||
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "upload", Outcome: outcomeHit, Detail: path}}))
|
||||
Expect(t.Steps()).To(Equal([]traceStep{{Candidate: "upload", Outcome: OutcomeHit, Detail: path}}))
|
||||
})
|
||||
|
||||
It("records an upload miss before walking the chain", func() {
|
||||
|
|
@ -311,7 +323,7 @@ var _ = Describe("resolveArtist tracing", func() {
|
|||
|
||||
_, err := newResolver(ds, ag, ffm, nil).resolve(ctx, model.ArtworkQueueItem{ItemKind: "ar", ItemID: "ar2"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(t.Steps()[0]).To(Equal(traceStep{Candidate: "upload", Outcome: outcomeMiss}))
|
||||
Expect(t.Steps()[0]).To(Equal(traceStep{Candidate: "upload", Outcome: OutcomeMiss}))
|
||||
})
|
||||
|
||||
It("labels each step with the configured priority token", func() {
|
||||
|
|
@ -330,7 +342,7 @@ var _ = Describe("resolveArtist tracing", func() {
|
|||
Expect(t.Steps()).To(HaveLen(2))
|
||||
Expect(t.Steps()[1].Candidate).To(Equal("album/artist.*"),
|
||||
"the step must be labelled with the priority token, not the pattern it was rewritten into")
|
||||
Expect(t.Steps()[1].Outcome).To(Equal(outcomeHit))
|
||||
Expect(t.Steps()[1].Outcome).To(Equal(OutcomeHit))
|
||||
Expect(filepath.ToSlash(t.Steps()[1].Detail)).To(HaveSuffix("tests/fixtures/artist/an-album/artist.png"))
|
||||
})
|
||||
|
||||
|
|
@ -340,8 +352,8 @@ var _ = Describe("resolveArtist tracing", func() {
|
|||
_, err := newResolver(ds, ag, ffm, nil).resolve(ctx, model.ArtworkQueueItem{ItemKind: "ar", ItemID: "ar5"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(t.Steps()).To(Equal([]traceStep{
|
||||
{Candidate: "upload", Outcome: outcomeMiss},
|
||||
{Candidate: "album/artist.*", Outcome: outcomeSkipped, Detail: "artist has no albums"},
|
||||
{Candidate: "upload", Outcome: OutcomeMiss},
|
||||
{Candidate: "album/artist.*", Outcome: OutcomeSkipped, Detail: "artist has no albums"},
|
||||
}), "a configured pattern that was never evaluated must still appear, and say why")
|
||||
})
|
||||
|
||||
|
|
@ -353,8 +365,8 @@ var _ = Describe("resolveArtist tracing", func() {
|
|||
_, err := newResolver(ds, ag, ffm, nil).resolve(ctx, model.ArtworkQueueItem{ItemKind: "ar", ItemID: "ar6"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(t.Steps()).To(Equal([]traceStep{
|
||||
{Candidate: "upload", Outcome: outcomeMiss},
|
||||
{Candidate: "artist.*", Outcome: outcomeSkipped, Detail: "no artist folder"},
|
||||
{Candidate: "upload", Outcome: OutcomeMiss},
|
||||
{Candidate: "artist.*", Outcome: OutcomeSkipped, Detail: "no artist folder"},
|
||||
}))
|
||||
})
|
||||
|
||||
|
|
@ -370,7 +382,7 @@ var _ = Describe("resolveArtist tracing", func() {
|
|||
_, err := newResolver(ds, ag, ffm, nil).resolve(ctx, model.ArtworkQueueItem{ItemKind: "ar", ItemID: "ar3"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(t.Steps()).To(HaveLen(1))
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(outcomeUnreadable),
|
||||
Expect(t.Steps()[0].Outcome).To(Equal(OutcomeUnreadable),
|
||||
"an upload that exists and will not open must not look like an absent upload")
|
||||
})
|
||||
})
|
||||
|
|
@ -433,7 +445,7 @@ var _ = Describe("NewTracingResolver", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(source).To(Equal("embedded"))
|
||||
Expect(fake.albumCalls).To(BeZero(), "offline mode must not add load to an external provider")
|
||||
Expect(t.Steps()).To(ContainElement(TraceStep{Candidate: "external:offline-probe", Outcome: outcomeWouldTry}))
|
||||
Expect(t.Steps()).To(ContainElement(TraceStep{Candidate: "external:offline-probe", Outcome: OutcomeWouldTry}))
|
||||
})
|
||||
|
||||
It("records the local chain steps too", func() {
|
||||
|
|
@ -442,7 +454,7 @@ var _ = Describe("NewTracingResolver", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
last := t.Steps()[len(t.Steps())-1]
|
||||
Expect(last.Candidate).To(Equal("embedded"), "the local chain must be traced, not just the external gate")
|
||||
Expect(last.Outcome).To(Equal(outcomeHit))
|
||||
Expect(last.Outcome).To(Equal(OutcomeHit))
|
||||
})
|
||||
|
||||
It("never persists artwork state", func() {
|
||||
|
|
@ -460,7 +472,7 @@ var _ = Describe("NewTracingResolver", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(source).To(BeEmpty())
|
||||
Expect(fake.artistCalls).To(BeZero())
|
||||
Expect(t.Steps()).To(ContainElement(TraceStep{Candidate: "external:offline-probe", Outcome: outcomeWouldTry}))
|
||||
Expect(t.Steps()).To(ContainElement(TraceStep{Candidate: "external:offline-probe", Outcome: OutcomeWouldTry}))
|
||||
Expect(artworkRepo.ItemData).To(BeEmpty())
|
||||
Expect(queueRepo.Data).To(BeEmpty())
|
||||
})
|
||||
|
|
@ -495,6 +507,6 @@ var _ = Describe("NewTracingResolver", func() {
|
|||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(fake.albumCalls).To(Equal(1))
|
||||
Expect(t.Steps()).To(ContainElement(TraceStep{Candidate: "external:live-probe", Outcome: outcomeMiss}))
|
||||
Expect(t.Steps()).To(ContainElement(TraceStep{Candidate: "external:live-probe", Outcome: OutcomeMiss}))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue