Add secret-safe copy and reveal UX coverage
This commit is contained in:
+143
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.julianfamily.org/keepassgo/clipboard"
|
||||
@@ -891,3 +892,145 @@ func TestUIUsesKeePassGOProductCopy(t *testing.T) {
|
||||
t.Fatalf("desktopSubtitle = %q, want updated product subtitle", desktopSubtitle)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUICopyActionsWriteExpectedClipboardContentsAndSanitizedFeedback(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
model := vault.Model{
|
||||
Entries: []vault.Entry{
|
||||
{
|
||||
ID: "git-server",
|
||||
Title: "Git Server",
|
||||
Username: "joejulian",
|
||||
Password: "token-1",
|
||||
URL: "https://git.julianfamily.org",
|
||||
Path: []string{"Root", "Internet"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
target clipboard.Target
|
||||
label string
|
||||
want string
|
||||
}{
|
||||
{name: "username", target: clipboard.TargetUsername, label: "copy username", want: "joejulian"},
|
||||
{name: "password", target: clipboard.TargetPassword, label: "copy password", want: "token-1"},
|
||||
{name: "url", target: clipboard.TargetURL, label: "copy URL", want: "https://git.julianfamily.org"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
u := newUIWithModel("desktop", model)
|
||||
writer := &memoryClipboardWriter{}
|
||||
u.clipboardWriter = writer
|
||||
u.showEntriesSection()
|
||||
u.currentPath = []string{"Root", "Internet"}
|
||||
u.filter()
|
||||
u.state.SelectedEntryID = "git-server"
|
||||
|
||||
u.runAction(tt.label, func() error { return u.copySelectedFieldAction(tt.target) })
|
||||
|
||||
if writer.content != tt.want {
|
||||
t.Fatalf("clipboard content = %q, want %q", writer.content, tt.want)
|
||||
}
|
||||
if u.statusMessage != tt.label+" complete" {
|
||||
t.Fatalf("statusMessage = %q, want %q", u.statusMessage, tt.label+" complete")
|
||||
}
|
||||
if u.errorMessage != "" {
|
||||
t.Fatalf("errorMessage = %q, want empty", u.errorMessage)
|
||||
}
|
||||
if strings.Contains(u.statusMessage, tt.want) {
|
||||
t.Fatalf("statusMessage = %q, must not contain copied secret or field value %q", u.statusMessage, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUICopyActionSanitizesClipboardBackendErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithModel("desktop", vault.Model{
|
||||
Entries: []vault.Entry{
|
||||
{
|
||||
ID: "git-server",
|
||||
Title: "Git Server",
|
||||
Username: "joejulian",
|
||||
Password: "token-1",
|
||||
URL: "https://git.julianfamily.org",
|
||||
Path: []string{"Root", "Internet"},
|
||||
},
|
||||
},
|
||||
})
|
||||
u.clipboardWriter = failingClipboardWriter{err: os.ErrPermission}
|
||||
u.showEntriesSection()
|
||||
u.currentPath = []string{"Root", "Internet"}
|
||||
u.filter()
|
||||
u.state.SelectedEntryID = "git-server"
|
||||
|
||||
u.runAction("copy password", func() error { return u.copySelectedFieldAction(clipboard.TargetPassword) })
|
||||
|
||||
if u.errorMessage != clipboard.ErrWriteFailed.Error() {
|
||||
t.Fatalf("errorMessage = %q, want %q", u.errorMessage, clipboard.ErrWriteFailed.Error())
|
||||
}
|
||||
if strings.Contains(u.errorMessage, "token-1") {
|
||||
t.Fatalf("errorMessage = %q, must not contain copied password", u.errorMessage)
|
||||
}
|
||||
if u.statusMessage != "" {
|
||||
t.Fatalf("statusMessage = %q, want empty on copy failure", u.statusMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIPasswordRevealTogglesDisplayedPasswordAndLockResetsIt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
u := newUIWithModel("desktop", vault.Model{
|
||||
Entries: []vault.Entry{
|
||||
{
|
||||
ID: "git-server",
|
||||
Title: "Git Server",
|
||||
Username: "joejulian",
|
||||
Password: "token-1",
|
||||
Path: []string{"Root", "Internet"},
|
||||
},
|
||||
},
|
||||
})
|
||||
u.showEntriesSection()
|
||||
u.currentPath = []string{"Root", "Internet"}
|
||||
u.filter()
|
||||
u.state.SelectedEntryID = "git-server"
|
||||
|
||||
if got := u.detailPasswordValue(); got != "••••••••" {
|
||||
t.Fatalf("detailPasswordValue() hidden = %q, want %q", got, "••••••••")
|
||||
}
|
||||
|
||||
u.showPassword = true
|
||||
if got := u.detailPasswordValue(); got != "token-1" {
|
||||
t.Fatalf("detailPasswordValue() revealed = %q, want %q", got, "token-1")
|
||||
}
|
||||
|
||||
if err := u.lockAction(); err != nil {
|
||||
t.Fatalf("lockAction() error = %v", err)
|
||||
}
|
||||
if u.showPassword {
|
||||
t.Fatal("showPassword = true after lockAction(), want false")
|
||||
}
|
||||
}
|
||||
|
||||
type memoryClipboardWriter struct {
|
||||
content string
|
||||
}
|
||||
|
||||
func (w *memoryClipboardWriter) WriteText(text string) error {
|
||||
w.content = text
|
||||
return nil
|
||||
}
|
||||
|
||||
type failingClipboardWriter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (w failingClipboardWriter) WriteText(string) error {
|
||||
return w.err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user