package kwallet import ( "context" "errors" "testing" "email-mcp/internal/secretstore" ) type walletClientStub struct { availableErr error openErr error writeErr error readErr error readValue []byte isAvailableCalled bool openCalled bool writeCalled bool readCalled bool writeKey string writeValue []byte readKey string } func (c *walletClientStub) IsAvailable(context.Context) error { c.isAvailableCalled = true return c.availableErr } func (c *walletClientStub) Open(context.Context) error { c.openCalled = true return c.openErr } func (c *walletClientStub) WriteEntry(_ context.Context, key string, value []byte) error { c.writeCalled = true c.writeKey = key c.writeValue = value return c.writeErr } func (c *walletClientStub) ReadEntry(_ context.Context, key string) ([]byte, error) { c.readCalled = true c.readKey = key return c.readValue, c.readErr } func TestStoreSaveReturnsErrorWhenClientIsNil(t *testing.T) { store := NewStore(nil) err := store.Save(context.Background(), secretstore.DefaultAccountKey, secretstore.Credential{ Host: "imap.example.com", Username: "alice", Password: "secret", }) if err == nil { t.Fatal("expected error when client is nil") } } func TestStoreLoadReturnsErrorWhenClientIsNil(t *testing.T) { store := NewStore(nil) _, err := store.Load(context.Background(), secretstore.DefaultAccountKey) if err == nil { t.Fatal("expected error when client is nil") } } func TestStoreSaveWritesSerializedCredential(t *testing.T) { client := &walletClientStub{} store := NewStore(client) cred := secretstore.Credential{ Host: "imap.example.com", Username: "alice", Password: "secret", } if err := store.Save(context.Background(), secretstore.DefaultAccountKey, cred); err != nil { t.Fatalf("Save returned error: %v", err) } if !client.openCalled { t.Fatal("expected wallet Open to be called") } if client.writeKey != secretstore.DefaultAccountKey { t.Fatalf("unexpected key: %s", client.writeKey) } if len(client.writeValue) == 0 { t.Fatal("expected serialized credential payload") } } func TestStoreSaveReturnsAvailabilityErrorWithoutOpeningWallet(t *testing.T) { wantErr := errors.New("kwallet unavailable") client := &walletClientStub{availableErr: wantErr} store := NewStore(client) err := store.Save(context.Background(), secretstore.DefaultAccountKey, secretstore.Credential{ Host: "imap.example.com", Username: "alice", Password: "secret", }) if !errors.Is(err, wantErr) { t.Fatalf("expected availability error, got %v", err) } if client.openCalled { t.Fatal("did not expect wallet Open when availability check fails") } } func TestStoreSaveValidatesBeforeCheckingWalletAvailability(t *testing.T) { client := &walletClientStub{} store := NewStore(client) err := store.Save(context.Background(), secretstore.DefaultAccountKey, secretstore.Credential{ Host: "imap.example.com", Username: "alice", }) if err == nil { t.Fatal("expected validation error") } if client.isAvailableCalled { t.Fatal("did not expect availability check for invalid credential") } if client.openCalled || client.writeCalled { t.Fatal("did not expect wallet calls for invalid credential") } } func TestStoreSaveReturnsOpenError(t *testing.T) { wantErr := errors.New("open failed") client := &walletClientStub{openErr: wantErr} store := NewStore(client) err := store.Save(context.Background(), secretstore.DefaultAccountKey, secretstore.Credential{ Host: "imap.example.com", Username: "alice", Password: "secret", }) if !errors.Is(err, wantErr) { t.Fatalf("expected open error, got %v", err) } if client.writeCalled { t.Fatal("did not expect write when open fails") } } func TestStoreSaveReturnsWriteError(t *testing.T) { wantErr := errors.New("write failed") client := &walletClientStub{writeErr: wantErr} store := NewStore(client) err := store.Save(context.Background(), secretstore.DefaultAccountKey, secretstore.Credential{ Host: "imap.example.com", Username: "alice", Password: "secret", }) if !errors.Is(err, wantErr) { t.Fatalf("expected write error, got %v", err) } } func TestStoreLoadReadsAndDecodesCredential(t *testing.T) { payload, err := secretstore.MarshalCredential(secretstore.Credential{ Host: "imap.example.com", Username: "alice", Password: "secret", }) if err != nil { t.Fatalf("MarshalCredential returned error: %v", err) } client := &walletClientStub{readValue: payload} store := NewStore(client) cred, err := store.Load(context.Background(), secretstore.DefaultAccountKey) if err != nil { t.Fatalf("Load returned error: %v", err) } if !client.openCalled { t.Fatal("expected wallet Open to be called") } if client.readKey != secretstore.DefaultAccountKey { t.Fatalf("unexpected key: %s", client.readKey) } if cred.Host != "imap.example.com" || cred.Username != "alice" || cred.Password != "secret" { t.Fatalf("unexpected credential: %#v", cred) } } func TestStoreLoadReturnsOpenError(t *testing.T) { wantErr := errors.New("open failed") client := &walletClientStub{openErr: wantErr} store := NewStore(client) _, err := store.Load(context.Background(), secretstore.DefaultAccountKey) if !errors.Is(err, wantErr) { t.Fatalf("expected open error, got %v", err) } if client.readCalled { t.Fatal("did not expect read when open fails") } } func TestStoreLoadReturnsReadError(t *testing.T) { wantErr := errors.New("read failed") client := &walletClientStub{readErr: wantErr} store := NewStore(client) _, err := store.Load(context.Background(), secretstore.DefaultAccountKey) if !errors.Is(err, wantErr) { t.Fatalf("expected read error, got %v", err) } } func TestStoreLoadReturnsDecodeError(t *testing.T) { client := &walletClientStub{readValue: []byte("not-json")} store := NewStore(client) _, err := store.Load(context.Background(), secretstore.DefaultAccountKey) if err == nil { t.Fatal("expected decode error") } }