The queue verb answered only a table, so nothing outside the controller could say an ask's place in the queue. It now asks its command for JSON: each ask's state, kind, repository, commit, machine, since and position among the waiting, with the table carried beside it and answered as the verb's output, both written from one read. A repository's password, if one were ever written into it, is left out of what is shown. novox/hq issue 455
138 lines
5.6 KiB
Go
138 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// The queue answers as data as well as text (novox/hq issue 455): each ask's repository, commit, kind,
|
|
// machine, since and position among the waiting, read from the same queue the table is written from.
|
|
|
|
const aCommit = "0123456789abcdef0123456789abcdef01234567"
|
|
|
|
func aQueueOfEachKind(now time.Time) link.Queue {
|
|
asked := now.Add(-5 * time.Minute)
|
|
started := now.Add(-2 * time.Minute)
|
|
return link.Queue{Seat: link.TheBuildMachine, Delivered: 2, AckPending: 1, MaxDeliver: 5, Asks: []link.QueuedAsk{
|
|
{Seq: 1, ID: "build-dead", Repository: "ssh://forge/novox/x", AskedAt: asked, State: link.AskDead},
|
|
{Seq: 2, ID: "build-running", Repository: "ssh://forge/novox/mesh-host", Ref: aCommit, AskedAt: asked,
|
|
State: link.AskInFlight, On: "ace", Started: started,
|
|
Request: link.BuildRequest{ID: "build-running", Check: &link.CheckRequest{Owner: "novox", Repo: "mesh-host"}}},
|
|
{Seq: 3, ID: "build-first", Repository: "ssh://forge/novox/mesh-catalog", Ref: "main", AskedAt: asked,
|
|
State: link.AskWaiting},
|
|
{Seq: 4, ID: "build-second", Repository: "ssh://forge/novox/mesh-catalog", Path: "modules/gitea", Ref: aCommit,
|
|
AskedAt: now.Add(-time.Minute), State: link.AskWaiting,
|
|
Request: link.BuildRequest{ID: "build-second", Check: &link.CheckRequest{Owner: "novox", Repo: "mesh-catalog"}}},
|
|
}}
|
|
}
|
|
|
|
func TestTheQueueVerbAnswersEachAskAsData(t *testing.T) {
|
|
now := time.Date(2026, 10, 12, 12, 0, 0, 0, time.UTC)
|
|
q := aQueueOfEachKind(now)
|
|
got := queueAnswerOf(q, now)
|
|
if got.Waiting != 2 || got.InFlight != 1 || got.Dead != 1 || got.Seat != link.TheBuildMachine {
|
|
t.Fatalf("counts: %+v", got)
|
|
}
|
|
if got.Text != queueText(q, now) {
|
|
t.Fatalf("the data's text is not the table read from the same queue:\n%s", got.Text)
|
|
}
|
|
byID := map[string]queuedAnswer{}
|
|
for _, a := range got.Asks {
|
|
byID[a.ID] = a
|
|
}
|
|
running := byID["build-running"]
|
|
if running.Kind != "check" || running.Machine != "ace" || running.Commit != aCommit || running.Position != 0 ||
|
|
!running.Since.Equal(now.Add(-2*time.Minute)) || running.State != link.AskInFlight {
|
|
t.Errorf("in flight: %+v", running)
|
|
}
|
|
first, second := byID["build-first"], byID["build-second"]
|
|
if first.Position != 1 || first.Kind != "build" || first.Commit != "" || first.Ref != "main" || first.Machine != "" ||
|
|
!first.Since.Equal(now.Add(-5*time.Minute)) {
|
|
t.Errorf("first waiting: %+v", first)
|
|
}
|
|
if second.Position != 2 || second.Kind != "check" || second.Commit != aCommit || second.Path != "modules/gitea" ||
|
|
second.Repository != "ssh://forge/novox/mesh-catalog" {
|
|
t.Errorf("second waiting: %+v", second)
|
|
}
|
|
if dead := byID["build-dead"]; dead.Position != 0 || dead.State != link.AskDead {
|
|
t.Errorf("dead: %+v", dead)
|
|
}
|
|
// In the table's order: in flight, then the waiting by position, then the dead.
|
|
var order []string
|
|
for _, a := range got.Asks {
|
|
order = append(order, a.ID)
|
|
}
|
|
if want := []string{"build-running", "build-first", "build-second", "build-dead"}; !reflect.DeepEqual(order, want) {
|
|
t.Errorf("order %v, want %v", order, want)
|
|
}
|
|
}
|
|
|
|
// The verb asks its command for JSON, and its answer carries the data while its output stays the table a
|
|
// person reads, so a text caller reads what it always did.
|
|
func TestTheQueueVerbKeepsTheTableAndCarriesTheData(t *testing.T) {
|
|
argv, err := argvFor("queue", nil)
|
|
if err != nil || !reflect.DeepEqual(argv, []string{"queue", "--json"}) {
|
|
t.Fatalf("queue's command line: %v %v", argv, err)
|
|
}
|
|
now := time.Now()
|
|
q := aQueueOfEachKind(now)
|
|
body, err := json.Marshal(queueAnswerOf(q, now))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
answer := answerOf(argv, body, "", true)
|
|
if answer.Output != queueText(q, now) {
|
|
t.Fatalf("the output is not the table:\n%s", answer.Output)
|
|
}
|
|
data, ok := answer.Answer.(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("no data: %#v", answer.Answer)
|
|
}
|
|
if _, doubled := data["text"]; doubled {
|
|
t.Error("the table is said twice: once as the output and again inside the data")
|
|
}
|
|
asks, _ := data["asks"].([]any)
|
|
if len(asks) != 4 || data["waiting"] != float64(2) {
|
|
t.Fatalf("data: %v", data)
|
|
}
|
|
second := asks[2].(map[string]any)
|
|
if second["position"] != float64(2) || second["kind"] != "check" || second["commit"] != aCommit {
|
|
t.Errorf("second waiting: %v", second)
|
|
}
|
|
for _, a := range asks {
|
|
if _, held := a.(map[string]any)["held"]; held {
|
|
t.Error("an ask carries what the mesh holds")
|
|
}
|
|
}
|
|
// A failure is said as text, as before.
|
|
failed := answerOf(argv, nil, "mesh-controller: the bus cannot be reached\n", false)
|
|
if failed.Answer != nil || !strings.Contains(failed.Output, "cannot be reached") {
|
|
t.Errorf("a failure: %+v", failed)
|
|
}
|
|
}
|
|
|
|
// A repository named with a password never shows it, in the table or the data.
|
|
func TestTheQueueNeverShowsARepositorysPassword(t *testing.T) {
|
|
if got := link.WithoutPassword("https://agent:s3cret@forge.example/novox/x.git"); strings.Contains(got, "s3cret") ||
|
|
!strings.Contains(got, "agent@forge.example/novox/x.git") {
|
|
t.Errorf("redacted: %s", got)
|
|
}
|
|
now := time.Now()
|
|
q := link.Queue{Seat: link.TheBuildMachine, Asks: []link.QueuedAsk{{Seq: 1, ID: "build-1",
|
|
Repository: "https://agent:s3cret@forge.example/novox/x.git", State: link.AskWaiting}}}
|
|
body, _ := json.Marshal(queueAnswerOf(q, now))
|
|
if strings.Contains(string(body), "s3cret") {
|
|
t.Errorf("the queue said a password: %s", body)
|
|
}
|
|
for _, kept := range []string{"ssh://git@forge:222/novox/x.git", "novox/x", "git@forge:novox/x.git", ""} {
|
|
if got := link.WithoutPassword(kept); got != kept {
|
|
t.Errorf("%q became %q", kept, got)
|
|
}
|
|
}
|
|
}
|