Skip to content
quarry Artifact [68e8fed635]
Login

Artifact [68e8fed635]

Artifact 68e8fed635394e546d7bc1d3618e2b6776125f933aa1dc7c95f6557ba913a540:


package errlog

import (
	"errors"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/fault"
	"craftdesign.group/quarry/internal/work"
)

// The journal reads the whole chain, not just the outermost fault.
//
// This is the ingest-lane case exactly. work.busy() attaches lane,
// running, limit, waiting, queue and shed — "the numbers that make the
// refusal actionable", by its own comment — and states what to do about
// it. The poll loop wraps that in "a store could not be ingested" with
// the repository's name. The panel showed the name, none of the
// numbers, and no remedy: ten thousand occurrences of a capacity signal
// that never said what the capacity was.
func TestTheJournalKeepsAWrappedFaultsContext(t *testing.T) {
	inner := fault.Wrap(work.ErrBusy, "ingest lane is full").
		Cost("work is being refused rather than queued").
		Do("raise this lane's limit or queue, or add a node").
		With("lane", "ingest", "running", 3, "limit", 3, "shed", 9871)
	outer := fault.Wrap(inner, "a store could not be ingested").
		Cost("this store's new check-ins are not searchable or listed").
		With("repo", "tcl")

	r := New(10)
	r.Report("ingest", "ingest tcl", outer)
	got := r.Recent(1)
	if len(got) != 1 {
		t.Fatal("FAIL: nothing was journalled")
	}
	e := got[0]

	keys := map[string]string{}
	for _, f := range e.Fields {
		keys[f.Key] = f.Value
	}
	for _, want := range []string{"repo", "lane", "running", "limit", "shed"} {
		if _, ok := keys[want]; !ok {
			t.Errorf("FAIL: the entry lost the %q field: %v", want, keys)
		}
	}
	if keys["shed"] != "9871" {
		t.Errorf("FAIL: shed = %q, want the lane's own count", keys["shed"])
	}
	// The outer fault's sentence is the row's title; the remedy is
	// inherited, because the layer that knows what to do is the lane.
	if e.Message != "a store could not be ingested" {
		t.Errorf("FAIL: message = %q", e.Message)
	}
	if !strings.Contains(e.Fix, "raise this lane's limit") {
		t.Errorf("FAIL: the row carries no remedy: %q", e.Fix)
	}
	// The cost is the outermost one: it is the layer nearest the reader.
	if !strings.Contains(e.Why, "not searchable") {
		t.Errorf("FAIL: why = %q, want the wrapper's own cost", e.Why)
	}
}

// A collision keeps the wrapper's value: it is closer to the site the
// reader is looking at.
func TestTheOuterFaultWinsAKeyCollision(t *testing.T) {
	inner := fault.New("inner").With("repo", "from-the-inside")
	outer := fault.Wrap(inner, "outer").With("repo", "from-the-outside")
	got := fault.FieldsOf(outer)
	n := 0
	for _, f := range got {
		if f.Key == "repo" {
			n++
			if f.Value != "from-the-outside" {
				t.Errorf("FAIL: repo = %q, want the wrapper's", f.Value)
			}
		}
	}
	if n != 1 {
		t.Errorf("FAIL: %d repo fields, want one", n)
	}
}

// A plain error carries nothing, and asking must not panic.
func TestAPlainErrorHasNoContext(t *testing.T) {
	err := errors.New("just an error")
	if got := fault.FieldsOf(err); len(got) != 0 {
		t.Errorf("FAIL: %v", got)
	}
	if fault.WhyOf(err) != "" || fault.FixOf(err) != "" {
		t.Error("FAIL: a plain error invented a cost or a remedy")
	}
	if fault.FieldsOf(nil) != nil {
		t.Error("FAIL: nil gave fields")
	}
}