cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / jev / apikey_test.go
apikey_test.go 5.1K
1package jev23import (4 "context"5 "errors"6 "os"7 "path/filepath"8 "runtime"9 "slices"10 "strings"11 "testing"12 "time"1314 "src.rest/cat/012/internal/keyring"15 "src.rest/cat/012/internal/sheet"16)1718func env(vals map[string]string) func(string) string {19 return func(k string) string { return vals[k] }20}2122func storeWith(key string) *keyring.Memory {23 m := &keyring.Memory{}24 if key != "" {25 m.Set(context.Background(), key)26 }27 return m28}2930// fakeCommand records the argv it's asked to run and prints out.31type fakeCommand struct {32 argv [][]string33 out string34 err error35}3637func (f *fakeCommand) run(_ context.Context, argv []string) ([]byte, error) {38 f.argv = append(f.argv, argv)39 return []byte(f.out), f.err40}4142func TestResolveKeyOrder(t *testing.T) {43 ctx := context.Background()44 cmd := &fakeCommand{out: "from-command\nsecond line\n"}45 s := KeySources{46 Getenv: env(map[string]string{KeyEnv: " from-env "}),47 Store: storeWith("from-store"),48 Command: "pass show typesafe",49 run: cmd.run,50 }51 if k, from, err := ResolveKey(ctx, s); k != "from-env" || from != KeyEnv || err != nil {52 t.Errorf("the environment comes first: %q %q %v", k, from, err)53 }54 s.Getenv = env(nil)55 if k, from, _ := ResolveKey(ctx, s); k != "from-store" || from != "test store" {56 t.Errorf("then the credential store: %q %q", k, from)57 }58 if len(cmd.argv) != 0 {59 t.Error("the command ran although the store had a key")60 }61 s.Store = storeWith("")62 if k, from, _ := ResolveKey(ctx, s); k != "from-command" || from != "jev-api-key-command" {63 t.Errorf("then the command's first line: %q %q", k, from)64 }65 if !slices.Equal(cmd.argv[0], []string{"pass", "show", "typesafe"}) {66 t.Errorf("argv %q", cmd.argv[0])67 }68 s.Command = ""69 if k, from, err := ResolveKey(ctx, s); k != "" || from != "" || err != nil {70 t.Errorf("no key anywhere: %q %q %v", k, from, err)71 }72}7374func TestResolveKeyFailures(t *testing.T) {75 ctx := context.Background()76 broken := &keyring.Memory{Err: errors.New("keychain locked")}77 cmd := &fakeCommand{out: "k"}78 k, _, err := ResolveKey(ctx, KeySources{Store: broken, Command: "get-key", run: cmd.run})79 if k != "k" || err != nil {80 t.Errorf("a failing store falls through to the command: %q %v", k, err)81 }82 cmd = &fakeCommand{err: errors.New("exit status 1")}83 _, _, err = ResolveKey(ctx, KeySources{Store: broken, Command: "get-key", run: cmd.run})84 if err == nil || !strings.Contains(err.Error(), "keychain locked") || !strings.Contains(err.Error(), "get-key: exit status 1") {85 t.Errorf("both failures reported: %v", err)86 }87 cmd = &fakeCommand{out: "\n"}88 if _, _, err := ResolveKey(ctx, KeySources{Command: "get-key", run: cmd.run}); err == nil || !strings.Contains(err.Error(), "printed nothing") {89 t.Errorf("empty output: %v", err)90 }91 if _, _, err := ResolveKey(ctx, KeySources{Command: "get 'key"}); err == nil {92 t.Error("an unclosed quote ran")93 }94}9596// The command runs without a shell: quotes group words, and nothing is97// expanded or piped.98func TestKeyCommandNoShell(t *testing.T) {99 cmd := &fakeCommand{out: "k"}100 ResolveKey(context.Background(), KeySources{Command: `op read "op://Private/Type Safe/credential" $HOME;rm`, run: cmd.run})101 want := []string{"op", "read", "op://Private/Type Safe/credential", "$HOME;rm"}102 if !slices.Equal(cmd.argv[0], want) {103 t.Errorf("argv %q, want %q", cmd.argv[0], want)104 }105}106107func TestKeyCommandRuns(t *testing.T) {108 if runtime.GOOS == "windows" {109 t.Skip("uses sh")110 }111 k, _, err := ResolveKey(context.Background(), KeySources{Command: `sh -c 'printf "real-key\n"'`})112 if k != "real-key" || err != nil {113 t.Errorf("%q %v", k, err)114 }115}116117func TestKeyCommandTimeout(t *testing.T) {118 if runtime.GOOS == "windows" {119 t.Skip("uses sleep")120 }121 start := time.Now()122 _, _, err := ResolveKey(context.Background(), KeySources{Command: "sleep 5", Timeout: 100 * time.Millisecond})123 if err == nil || !strings.Contains(err.Error(), "didn't finish in 100ms") {124 t.Errorf("timeout: %v", err)125 }126 if d := time.Since(start); d > 3*time.Second {127 t.Errorf("took %s", d)128 }129}130131func TestDotEnvHasKey(t *testing.T) {132 dir := t.TempDir()133 if DotEnvHasKey(dir) {134 t.Error("no .env")135 }136 os.WriteFile(filepath.Join(dir, ".env"), []byte("# x\nTYPESAFE_BASE_URL=https://a\n"), 0o600)137 if DotEnvHasKey(dir) {138 t.Error("no key in .env")139 }140 os.WriteFile(filepath.Join(dir, ".env"), []byte("export TYPESAFE_API_KEY=\"x\"\n"), 0o600)141 if !DotEnvHasKey(dir) {142 t.Error("key in .env not noticed")143 }144}145146func TestNewClientChecksBaseURL(t *testing.T) {147 if _, err := NewClient(Config{APIKey: "k", BaseURL: "http://evil.example"}); err == nil {148 t.Error("http base URL accepted")149 }150 if _, err := NewClient(Config{APIKey: "k", BaseURL: "http://127.0.0.1:8799"}); err != nil {151 t.Errorf("localhost: %v", err)152 }153}154155func TestLazyConnectsOnce(t *testing.T) {156 calls := 0157 c := Lazy(func() (Client, error) { calls++; return nil, errors.New("jev-api-key-command: op: exit status 1") })158 if calls != 0 {159 t.Fatal("connected before a question")160 }161 for range 2 {162 if a := Ask(context.Background(), c, sheet.RemoteCall{Kind: "noul", Instructions: "Q", Criteria: [2]string{}}); !strings.Contains(a.Failed, "exit status 1") {163 t.Errorf("answer %+v", a)164 }165 }166 if calls != 1 {167 t.Errorf("connected %d times", calls)168 }169}