Skip to content
quarry Artifact [378a030be9]
Login

Artifact [378a030be9]

Artifact 378a030be9349d93b965290ea4db17ab2914f86871986dd9a2672dca8038f6ce:


package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/ops"
)

func agentStep(name string) planStepIn {
	return planStepIn{Op: "agent.new", Input: json.RawMessage(fmt.Sprintf(`{"name":%q}`, name))}
}

// A plan is one request: approved once, its steps run in order as the
// person, each with its own outcome.
func TestAPlanRunsItsStepsInOrderOnOneApproval(t *testing.T) {
	r, _, _, _, _ := signOffRig(t)
	out, err := r.Invoke(asUser("fbot"), planOp, planIn{Title: "Set up two testers", Reason: "QA",
		Steps: []planStepIn{agentStep("alpha"), agentStep("beta")}})
	if err != nil {
		t.Fatalf("FAIL: asking: %v", err)
	}
	asked := out.(planOut)
	if asked.SignOff.State != "pending" || asked.SignOff.Owner != "dmestas" || len(asked.Steps) != 2 {
		t.Fatalf("FAIL: the request: %+v", asked)
	}
	if asked.Steps[0].State != "" || !strings.Contains(asked.Steps[1].Sentence, "beta") {
		t.Errorf("FAIL: a waiting plan's steps: %+v", asked.Steps)
	}
	if _, err := r.Invoke(asUser("dmestas"), "signoff.decide", signOffDecideIn{ID: asked.SignOff.ID, Approve: true}); err != nil {
		t.Fatalf("FAIL: approving: %v", err)
	}
	got, err := r.Invoke(asUser("fbot"), "signoff.get", signOffGetIn{ID: asked.SignOff.ID})
	if err != nil {
		t.Fatal(err)
	}
	g := got.(signOffGetOut)
	if g.Request.State != "approved" || len(g.Steps) != 2 || g.Steps[0].State != "done" || g.Steps[1].State != "done" {
		t.Errorf("FAIL: after approval: state=%s steps=%+v", g.Request.State, g.Steps)
	}
	if !strings.Contains(string(g.Result), "alpha") || !strings.Contains(string(g.Result), "beta") {
		t.Errorf("FAIL: the steps' answers: %s", g.Result)
	}
}

// The first step that fails stops the plan: earlier steps stand, later
// ones do not run, and the request says which step it stopped at.
func TestAPlanStopsAtTheFirstStepThatFails(t *testing.T) {
	r, _, _, _, _ := signOffRig(t)
	out, err := r.Invoke(asUser("fbot"), planOp, planIn{Title: "Three", Steps: []planStepIn{
		agentStep("alpha"), agentStep("alpha"), agentStep("gamma")}})
	if err != nil {
		t.Fatalf("FAIL: asking: %v", err)
	}
	id := out.(planOut).SignOff.ID
	if _, err := r.Invoke(asUser("dmestas"), "signoff.decide", signOffDecideIn{ID: id, Approve: true}); err == nil {
		t.Fatal("FAIL: a plan with a failing step approved cleanly")
	}
	got, err := r.Invoke(asUser("fbot"), "signoff.get", signOffGetIn{ID: id})
	if err != nil {
		t.Fatal(err)
	}
	g := got.(signOffGetOut)
	if g.Request.State != "failed" || !strings.Contains(g.Request.Err, "step 2") {
		t.Errorf("FAIL: the request: state=%s err=%q", g.Request.State, g.Request.Err)
	}
	want := []string{"done", "failed", "not run"}
	for i, st := range g.Steps {
		if st.State != want[i] {
			t.Errorf("FAIL: step %d is %q, want %q", i+1, st.State, want[i])
		}
	}
	if strings.Contains(string(g.Result), "gamma") {
		t.Errorf("FAIL: a step after the failure ran: %s", g.Result)
	}
}

// A plan is refused whole, naming the step, when any step could not be
// asked for on its own.
func TestAPlanIsRefusedWholeNamingTheStep(t *testing.T) {
	r, _, _, _, _ := signOffRig(t)
	tooMany := make([]planStepIn, maxPlanSteps+1)
	for i := range tooMany {
		tooMany[i] = agentStep(fmt.Sprintf("a%d", i))
	}
	for name, c := range map[string]struct {
		steps []planStepIn
		step  string
	}{
		"unknown op":    {[]planStepIn{agentStep("a"), {Op: "no.such"}}, "step 2"},
		"not askable":   {[]planStepIn{{Op: "signoff.decide", Input: json.RawMessage(`{"id":"x"}`)}}, "step 1"},
		"bad input":     {[]planStepIn{agentStep("a"), {Op: "agent.new", Input: json.RawMessage(`{"nope":1}`)}}, "step 2"},
		"a read":        {[]planStepIn{{Op: "signoff.list"}}, "step 1"},
		"no steps":      {nil, ""},
		"too many":      {tooMany, ""},
		"a nested plan": {[]planStepIn{{Op: planOp, Input: json.RawMessage(`{"title":"x"}`)}}, "step 1"},
	} {
		_, err := r.Invoke(asUser("fbot"), planOp, planIn{Title: "t", Steps: c.steps})
		if err == nil || !strings.Contains(err.Error(), c.step) {
			t.Errorf("FAIL: %s: %v", name, err)
		}
	}
	if _, err := r.Invoke(asUser("dmestas"), planOp, planIn{Title: "t", Steps: []planStepIn{agentStep("a")}}); !errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: a person asked for a plan: %v", err)
	}
}