package api
import (
"context"
"errors"
"testing"
"craftdesign.group/quarry/internal/ci"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/zitcli"
)
// A run and its log come back by id or short prefix: one try per step,
// a retried step's failing first try included, with tail keeping the
// last lines of each.
func TestARunAndItsLogComeBackByPrefix(t *testing.T) {
runs := map[string]CIRun{
"1b82c372a6e6aaaa": {RunID: "1b82c372a6e6aaaa", Checkin: "c1", Status: "failed"},
"1b82c372ffff0000": {RunID: "1b82c372ffff0000", Checkin: "c2", Status: "completed"},
}
log := []ci.LogChunk{
{Step: "test", Attempt: 1, Stream: "out", Data: "=== RUN TestA"},
{Step: "test", Attempt: 1, Stream: "out", Data: "--- FAIL: TestA\nFAIL\n"},
{Step: "test", Attempt: 1, Stream: "marker", Data: "failed"},
{Step: "test", Attempt: 2, Stream: "out", Data: "ok\n"},
{Step: "build", Attempt: 1, Stream: "out", Data: "built\n"},
}
d := Deps{Version: "v",
Repos: NewRepos(map[string]*Repo{"test": {Zit: &zitcli.Client{}}}),
CIRunByPrefix: func(repo, prefix string) (CIRun, bool, bool, error) {
var found []CIRun
for id, r := range runs {
if len(prefix) <= len(id) && id[:len(prefix)] == prefix {
found = append(found, r)
}
}
if len(found) != 1 {
return CIRun{}, false, len(found) > 1, nil
}
return found[0], true, false, nil
},
CILog: func(repo, runID string) ([]ci.LogChunk, bool, error) {
if runID != "1b82c372a6e6aaaa" {
return nil, false, nil
}
return log, true, nil
},
}
r := ops.NewRegistry()
if err := RegisterAll(r, d); err != nil {
t.Fatal(err)
}
ctx := ops.WithUser(context.Background(), "alice")
out, err := r.Invoke(ctx, "ci.run.get", ciRunIn{Repo: "test", RunID: "1B82C372A6E6"})
if err != nil || out.(ciRunOut).Run.RunID != "1b82c372a6e6aaaa" {
t.Errorf("FAIL: a run by a prefix read %+v, %v", out, err)
}
if _, err := r.Invoke(ctx, "ci.run.get", ciRunIn{Repo: "test", RunID: "1b82c372"}); !errors.Is(err, ops.ErrInvalid) {
t.Errorf("FAIL: a prefix naming two runs answered %v", err)
}
if _, err := r.Invoke(ctx, "ci.run.get", ciRunIn{Repo: "test", RunID: "dead"}); !errors.Is(err, ops.ErrNotFound) {
t.Errorf("FAIL: no such run answered %v", err)
}
out, err = r.Invoke(ctx, "ci.run.log", ciRunLogIn{Repo: "test", RunID: "1b82c372a6e6"})
if err != nil {
t.Fatal(err)
}
lo := out.(ciRunLogOut)
if !lo.Held || len(lo.Steps) != 3 {
t.Fatalf("FAIL: the log held %v with %d tries: %+v", lo.Held, len(lo.Steps), lo.Steps)
}
if s := lo.Steps[0]; s.Step != "test" || s.Attempt != 1 || !s.Failed || s.Lines != 3 {
t.Errorf("FAIL: the failing first try reads %+v", s)
}
if s := lo.Steps[1]; s.Attempt != 2 || s.Failed {
t.Errorf("FAIL: the passing retry reads %+v", s)
}
out, _ = r.Invoke(ctx, "ci.run.log", ciRunLogIn{Repo: "test", RunID: "1b82c372a6e6", Step: "test", Tail: 2})
lo = out.(ciRunLogOut)
if len(lo.Steps) != 2 || lo.Steps[0].Text != "--- FAIL: TestA\nFAIL\n" || lo.Steps[0].Lines != 3 {
t.Errorf("FAIL: step and tail read %+v", lo.Steps)
}
out, _ = r.Invoke(ctx, "ci.run.log", ciRunLogIn{Repo: "test", RunID: "1b82c372ffff"})
if lo := out.(ciRunLogOut); lo.Held || lo.Steps == nil || len(lo.Steps) != 0 {
t.Errorf("FAIL: a run with no log read %+v", lo)
}
}