package bonfire import ( "bytes" "context" "errors" "strings" "testing" ) func TestCall(t *testing.T) { var gotArgs []string var gotFlag string cmds := []Command{{ Name: "acme:echo", Flags: []Flag{{Name: "loud", Bare: true}}, Args: []Arg{{Name: "word"}}, Run: func(ctx context.Context, in Input, out Output) error { gotArgs = in.Args() gotFlag, _ = in.Flag("loud") out.Printf("echo %s\n", strings.Join(in.Args(), " ")) return nil }, }} var out bytes.Buffer if err := Call(t.Context(), cmds, "acme:echo", []string{"--loud", "hello", "world"}, &out); err != nil { t.Fatal(err) } if strings.Join(gotArgs, ",") != "hello,world" || gotFlag != "true" { t.Fatalf("args = %v, loud = %q", gotArgs, gotFlag) } if out.String() != "echo hello world\n" { t.Fatalf("out = %q", out.String()) } err := Call(t.Context(), cmds, "acme:missing", nil, &out) if !errors.Is(err, ErrUnknownCommand) || !strings.Contains(err.Error(), `"acme:missing"`) { t.Fatalf("err = %v, want ErrUnknownCommand naming the command", err) } cat := NewCatalog(cmds) cmds[0].Name = "acme:renamed" if !cat.Has("acme:echo") || cat.Has("acme:renamed") { t.Fatal("catalog does not hold a copy of the commands") } out.Reset() if err := cat.Call(t.Context(), "acme:echo", []string{"x"}, &out); err != nil || out.String() != "echo x\n" { t.Fatalf("catalog call: out %q, err %v", out.String(), err) } var nilCat *Catalog if nilCat.Has("acme:echo") || !errors.Is(nilCat.Call(t.Context(), "acme:echo", nil, &out), ErrUnknownCommand) { t.Fatal("nil catalog must hold no commands") } } // TestCallEdges covers the in-process run edges the scheduler relies on: // empty stdin gives prompt defaults, a nil ctx and a nil writer are // allowed, and a command error is returned as is. func TestCallEdges(t *testing.T) { boom := errors.New("boom") var answer string var confirmed bool var sawCtx bool cmds := []Command{ {Name: "acme:ask", Run: func(ctx context.Context, in Input, out Output) error { sawCtx = ctx != nil var err error if answer, err = out.Ask("Name?", "default-name"); err != nil { return err } confirmed, err = out.Confirm("Sure?", true) return err }}, {Name: "acme:fail", Run: func(context.Context, Input, Output) error { return boom }}, } cases := []struct { name string ctx context.Context command string wantErr error }{ {"prompt_defaults", t.Context(), "acme:ask", nil}, {"nil_ctx", nil, "acme:ask", nil}, {"command_error", t.Context(), "acme:fail", boom}, {"unknown", t.Context(), "acme:nope", ErrUnknownCommand}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { answer, confirmed, sawCtx = "", false, false err := Call(c.ctx, cmds, c.command, nil, nil) if c.wantErr == nil && err != nil { t.Fatalf("err = %v", err) } if c.wantErr != nil && !errors.Is(err, c.wantErr) { t.Fatalf("err = %v, want %v", err, c.wantErr) } if c.command == "acme:ask" && (answer != "default-name" || !confirmed || !sawCtx) { t.Fatalf("answer %q, confirmed %v, ctx %v; want the prompt defaults and a ctx", answer, confirmed, sawCtx) } }) } }