1package keyring23import (4 "context"5 "encoding/hex"6 "errors"7 "strings"8 "testing"9)1011// These tests never run security or secret-tool: a fake runner records12// what would run, so the real keychain is never touched.1314const secret = "ts-secret-key-123"1516type call struct {17 name string18 args []string19 stdin string20}2122type fakeRunner struct {23 calls []call24 out string25 err error26}2728func (f *fakeRunner) run(_ context.Context, name string, args []string, stdin string) (string, error) {29 f.calls = append(f.calls, call{name, args, stdin})30 return f.out, f.err31}3233// noSecretInArgs fails if the secret is in any argument, where other34// users could see it in the process list.35func noSecretInArgs(t *testing.T, calls []call) {36 t.Helper()37 for _, c := range calls {38 for _, a := range c.args {39 if strings.Contains(a, secret) || strings.Contains(a, hex.EncodeToString([]byte(secret))) {40 t.Errorf("secret in argv: %s %q", c.name, c.args)41 }42 }43 }44}4546func TestKeychain(t *testing.T) {47 ctx := context.Background()48 f := &fakeRunner{}49 k := keychain{run: f.run}50 if err := k.Set(ctx, secret); err != nil {51 t.Fatal(err)52 }53 noSecretInArgs(t, f.calls)54 c := f.calls[0]55 if c.name != "/usr/bin/security" || len(c.args) != 1 || c.args[0] != "-i" {56 t.Errorf("set ran %s %q", c.name, c.args)57 }58 if !strings.Contains(c.stdin, "add-generic-password -U -s 012 -a jev-api-key") || !strings.Contains(c.stdin, "-X "+hex.EncodeToString([]byte(secret))) {59 t.Errorf("set wrote %q", c.stdin)60 }6162 f.out = secret + "\n"63 if got, err := k.Get(ctx); err != nil || got != secret {64 t.Errorf("get: %q %v", got, err)65 }66 f.out, f.err = "", &exitError{code: 44, stderr: "The specified item could not be found in the keychain."}67 if _, err := k.Get(ctx); !errors.Is(err, ErrNotFound) {68 t.Errorf("missing item: %v", err)69 }70 if err := k.Delete(ctx); !errors.Is(err, ErrNotFound) {71 t.Errorf("delete missing: %v", err)72 }73 f.err = &exitError{code: 51, stderr: "user interaction is not allowed"}74 if _, err := k.Get(ctx); err == nil || errors.Is(err, ErrNotFound) || !strings.Contains(err.Error(), "interaction") {75 t.Errorf("other failure: %v", err)76 }77}7879func TestSecretService(t *testing.T) {80 ctx := context.Background()81 f := &fakeRunner{}82 s := secretService{run: f.run}83 if err := s.Set(ctx, secret); err != nil {84 t.Fatal(err)85 }86 noSecretInArgs(t, f.calls)87 c := f.calls[0]88 if c.name != "secret-tool" || c.args[0] != "store" || c.stdin != secret {89 t.Errorf("set ran %s %q with stdin %q", c.name, c.args, c.stdin)90 }91 if strings.Join(c.args[2:], " ") != "service 012 account jev-api-key" {92 t.Errorf("attributes %q", c.args)93 }9495 f.out = secret96 if got, err := s.Get(ctx); err != nil || got != secret {97 t.Errorf("get: %q %v", got, err)98 }99 f.out, f.err = "", &exitError{code: 1}100 if _, err := s.Get(ctx); !errors.Is(err, ErrNotFound) {101 t.Errorf("missing: %v", err)102 }103 f.err = &exitError{code: 1, stderr: "Cannot autolaunch D-Bus without X11 $DISPLAY"}104 if _, err := s.Get(ctx); err == nil || errors.Is(err, ErrNotFound) {105 t.Errorf("no D-Bus should be an error, not a missing key: %v", err)106 }107}108109func TestMemory(t *testing.T) {110 ctx := context.Background()111 m := &Memory{}112 if _, err := m.Get(ctx); !errors.Is(err, ErrNotFound) {113 t.Error(err)114 }115 m.Set(ctx, "k")116 if got, _ := m.Get(ctx); got != "k" {117 t.Error(got)118 }119 if err := m.Delete(ctx); err != nil {120 t.Error(err)121 }122}