diff --git a/cmd/artwork.go b/cmd/artwork.go index afea5ebf6..0d688ec81 100644 --- a/cmd/artwork.go +++ b/cmd/artwork.go @@ -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)" } } diff --git a/core/artwork/agent_images.go b/core/artwork/agent_images.go index f0ee6840f..1c5c5844d 100644 --- a/core/artwork/agent_images.go +++ b/core/artwork/agent_images.go @@ -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 } diff --git a/core/artwork/agent_images_test.go b/core/artwork/agent_images_test.go index b9216a587..88e0b47b5 100644 --- a/core/artwork/agent_images_test.go +++ b/core/artwork/agent_images_test.go @@ -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") }) diff --git a/core/artwork/disc.go b/core/artwork/disc.go index 21f596b60..cfc372142 100644 --- a/core/artwork/disc.go +++ b/core/artwork/disc.go @@ -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 != "" { diff --git a/core/artwork/resolve.go b/core/artwork/resolve.go index b115c23a8..74a6b8acb 100644 --- a/core/artwork/resolve.go +++ b/core/artwork/resolve.go @@ -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) } diff --git a/core/artwork/trace.go b/core/artwork/trace.go index 4b9f2eb3f..b86e67a89 100644 --- a/core/artwork/trace.go +++ b/core/artwork/trace.go @@ -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 } } diff --git a/core/artwork/trace_test.go b/core/artwork/trace_test.go index 885ca2b12..3d04e8ed5 100644 --- a/core/artwork/trace_test.go +++ b/core/artwork/trace_test.go @@ -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})) }) })