Give the command an answer verb, so the operator answers a hold or restore ask from a terminal as in the page (issue 1297 B9b)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// Command mesh-delivery-cli is the operator's command for the delivery view (novox/hq ADR 0429 §3). It asks the
|
||||
// view's local socket for the rows and prints them; it holds no credential and changes nothing.
|
||||
// view's local socket for the rows and prints them; it holds no credential. `answer <ask key> <option> [words]` asks the view
|
||||
// to write the operator's answer to an ask that needs no proof of who answered (the view refuses the merge's Approve).
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"mesh-delivery-view/internal/view"
|
||||
)
|
||||
@@ -30,8 +32,17 @@ func run(args []string, socket string, out, errOut io.Writer) int {
|
||||
return 2
|
||||
}
|
||||
if fs.NArg() > 0 {
|
||||
fmt.Fprintln(errOut, "mesh-delivery-cli takes no argument: it only shows")
|
||||
return 2
|
||||
if fs.Arg(0) != "answer" || fs.NArg() < 3 {
|
||||
fmt.Fprintln(errOut, "mesh-delivery-cli shows the rows, or answers: mesh-delivery-cli answer <ask key> <option> [words]")
|
||||
return 2
|
||||
}
|
||||
key, option := fs.Arg(1), fs.Arg(2)
|
||||
if err := view.AnswerAsk(socket, key, option, strings.Join(fs.Args()[3:], " ")); err != nil {
|
||||
fmt.Fprintln(errOut, "mesh-delivery-cli:", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintf(out, "answered %s with %s; the teller fills the ask from it\n", key, option)
|
||||
return 0
|
||||
}
|
||||
s, err := view.Ask(socket)
|
||||
if err != nil {
|
||||
|
||||
@@ -167,3 +167,76 @@ func TestPrintsTheOpenAsksInTheTableAndTheJSON(t *testing.T) { // 1345/AC2
|
||||
t.Errorf("json = %s (%v)", out.String(), err)
|
||||
}
|
||||
}
|
||||
|
||||
type putter struct{ puts map[string][]byte }
|
||||
|
||||
func (p *putter) Put(key string, value []byte) error { p.puts[key] = value; return nil }
|
||||
|
||||
func serveWith(t *testing.T, m *view.Model) string {
|
||||
t.Helper()
|
||||
dir, err := os.MkdirTemp(os.Getenv("TMPDIR"), "c")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { os.RemoveAll(dir) })
|
||||
l, err := view.ListenSocket(filepath.Join(dir, "cli.sock"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go view.ServeSocket(ctx, l, m)
|
||||
t.Cleanup(cancel)
|
||||
return filepath.Join(dir, "cli.sock")
|
||||
}
|
||||
|
||||
// TestTheAnswerCommandAnswersAnAskNeedingNoProof: `answer <ask key> <option> [words]` writes the view's answer; the merge's
|
||||
// ask is refused with the phone as the reason (1297/B9b).
|
||||
func TestTheAnswerCommandAnswersAnAskNeedingNoProof(t *testing.T) { // 1297/B9b AC
|
||||
const fp = "src2:2222222222222222222222222222222222222222222222222222222222222222"
|
||||
key, err := delivery.HoldAskKey("plex", fp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hold := asks.Ask{ID: delivery.AskID(key), Headline: "plex is held", Explanation: "ace failed the check.", Who: asks.Operator, Expires: time.Now().Add(time.Hour), OnExpiry: "stays held",
|
||||
Options: []asks.Option{{ID: "release", Label: "Release", Does: "goes on", Level: asks.Approve}, {ID: "keep", Label: "Keep", Does: "stays", Level: asks.Approve}}}
|
||||
value, _ := json.Marshal(map[string]any{"v": 1, "ask": hold})
|
||||
heads := []delivery.Head{{Repository: "novox/mesh-delivery", Number: 12, Head: "0123456789abcdef0123456789abcdef01234567"}}
|
||||
mkey, err := delivery.MergeAskKey("feat/x", heads)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
merge := asks.Ask{ID: delivery.AskID(mkey), Headline: "Merge", Explanation: "Merges.", Who: asks.Operator, Expires: time.Now().Add(time.Hour), OnExpiry: "held",
|
||||
Options: []asks.Option{{ID: "approve", Label: "Approve", Does: "merges", Level: asks.Approve, Binds: "sha256:00"}}}
|
||||
mv, err := delivery.EncodeAsk(delivery.Ask{Group: "feat/x", Heads: heads, Ask: merge})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m, w := view.New(), &putter{puts: map[string][]byte{}}
|
||||
m.SetWriter(w)
|
||||
m.Apply(view.Entry{Bucket: delivery.AssignerAsksBucket, Key: key, Value: value})
|
||||
m.Apply(view.Entry{Bucket: delivery.AsksBucket, Key: mkey, Value: mv})
|
||||
socket := serveWith(t, m)
|
||||
|
||||
var out, errOut bytes.Buffer
|
||||
if code := run([]string{"answer", key, "release", "the", "check", "was", "a", "flake"}, socket, &out, &errOut); code != 0 {
|
||||
t.Fatalf("exit %d: %s", code, errOut.String())
|
||||
}
|
||||
a, err := delivery.DecodeViewAnswer(w.puts[key+".view"])
|
||||
if err != nil || a.Option != "release" || a.Words != "the check was a flake" || !strings.Contains(out.String(), key) {
|
||||
t.Errorf("answer = %+v, %v; out %q", a, err, out.String())
|
||||
}
|
||||
out.Reset()
|
||||
errOut.Reset()
|
||||
if code := run([]string{"answer", mkey, "approve"}, socket, &out, &errOut); code != 1 || !strings.Contains(errOut.String(), "answer it on the phone (issue 953)") {
|
||||
t.Errorf("merge: exit %d, stderr %q", code, errOut.String())
|
||||
}
|
||||
if len(w.puts) != 1 {
|
||||
t.Errorf("puts = %v, want only the hold's", w.puts)
|
||||
}
|
||||
for _, args := range [][]string{{"answer"}, {"answer", key}, {"settle", key, "release"}} {
|
||||
errOut.Reset()
|
||||
if code := run(args, socket, &out, &errOut); code != 2 || errOut.Len() == 0 {
|
||||
t.Errorf("%v: exit %d, stderr %q", args, code, errOut.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user