297 lines
6.7 KiB
Go
297 lines
6.7 KiB
Go
package autofillcache
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.julianfamily.org/keepassgo/vault"
|
|
)
|
|
|
|
func TestBuildFiltersAndNormalizesEntries(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
now := time.Date(2026, time.March, 31, 12, 0, 0, 0, time.UTC)
|
|
got := Build(vault.Model{
|
|
Entries: []vault.Entry{
|
|
{
|
|
ID: "one",
|
|
Title: "Chrome Test",
|
|
Username: "joe",
|
|
Password: "secret",
|
|
URL: "https://10.0.2.2:8443/login",
|
|
Path: []string{"Joe", "Internet"},
|
|
},
|
|
{
|
|
ID: "two",
|
|
Title: "No Password",
|
|
Username: "joe",
|
|
URL: "https://example.com",
|
|
},
|
|
{
|
|
ID: "three",
|
|
Title: "Bare Host",
|
|
Username: "user",
|
|
Password: "pass",
|
|
URL: "lights.julianfamily.org",
|
|
Fields: map[string]string{
|
|
"AndroidApp1": "androidapp://com.lights.mobile",
|
|
"KP2A_URL_1": "https://lights.julianfamily.org/account",
|
|
},
|
|
},
|
|
},
|
|
}, now)
|
|
|
|
if len(got.Entries) != 2 {
|
|
t.Fatalf("entry count = %d, want 2", len(got.Entries))
|
|
}
|
|
if got.Entries[0].Host != "10.0.2.2" {
|
|
t.Fatalf("first host = %q, want 10.0.2.2", got.Entries[0].Host)
|
|
}
|
|
if got.Entries[1].Host != "lights.julianfamily.org" {
|
|
t.Fatalf("second host = %q, want lights.julianfamily.org", got.Entries[1].Host)
|
|
}
|
|
if len(got.Entries[1].Targets) != 3 {
|
|
t.Fatalf("len(second targets) = %d, want 3", len(got.Entries[1].Targets))
|
|
}
|
|
if got.UpdatedAt != "2026-03-31T12:00:00Z" {
|
|
t.Fatalf("updatedAt = %q", got.UpdatedAt)
|
|
}
|
|
}
|
|
|
|
func TestWriteAndClear(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "autofill-cache.json")
|
|
model := vault.Model{
|
|
Entries: []vault.Entry{
|
|
{ID: "one", Title: "Chrome Test", Username: "joe", Password: "secret", URL: "https://10.0.2.2:8443/login"},
|
|
},
|
|
}
|
|
|
|
if err := Write(path, model, time.Date(2026, time.March, 31, 12, 0, 0, 0, time.UTC)); err != nil {
|
|
t.Fatalf("Write() error = %v", err)
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile() error = %v", err)
|
|
}
|
|
var got File
|
|
if err := json.Unmarshal(data, &got); err != nil {
|
|
t.Fatalf("Unmarshal() error = %v", err)
|
|
}
|
|
if len(got.Entries) != 1 || got.Entries[0].Host != "10.0.2.2" {
|
|
t.Fatalf("cache entries = %#v", got.Entries)
|
|
}
|
|
if err := Clear(path); err != nil {
|
|
t.Fatalf("Clear() error = %v", err)
|
|
}
|
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
|
t.Fatalf("cache path still exists, stat err = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMatchChoosesExactURLWhenHostsRepeat(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache := File{
|
|
Entries: []Entry{
|
|
{
|
|
ID: "one",
|
|
Title: "Primary Login",
|
|
Username: "first",
|
|
Password: "secret1",
|
|
URL: "https://10.0.2.2:8443/login/",
|
|
Host: "10.0.2.2",
|
|
},
|
|
{
|
|
ID: "two",
|
|
Title: "Alt Login",
|
|
Username: "second",
|
|
Password: "secret2",
|
|
URL: "https://10.0.2.2:8443/alt/",
|
|
Host: "10.0.2.2",
|
|
},
|
|
},
|
|
}
|
|
|
|
got, ok := Match(cache, "https://10.0.2.2:8443/alt/")
|
|
if !ok {
|
|
t.Fatalf("Match() found no entry")
|
|
}
|
|
if got.ID != "two" {
|
|
t.Fatalf("Match() entry = %q, want two", got.ID)
|
|
}
|
|
}
|
|
|
|
func TestMatchRejectsAmbiguousSharedHost(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache := File{
|
|
Entries: []Entry{
|
|
{
|
|
ID: "one",
|
|
Title: "Host A",
|
|
Username: "first",
|
|
Password: "secret1",
|
|
URL: "https://lights.julianfamily.org/",
|
|
Host: "lights.julianfamily.org",
|
|
},
|
|
{
|
|
ID: "two",
|
|
Title: "Host B",
|
|
Username: "second",
|
|
Password: "secret2",
|
|
URL: "https://lights.julianfamily.org/",
|
|
Host: "lights.julianfamily.org",
|
|
},
|
|
},
|
|
}
|
|
|
|
if _, ok := Match(cache, "https://lights.julianfamily.org/"); ok {
|
|
t.Fatalf("Match() unexpectedly resolved ambiguous shared host")
|
|
}
|
|
}
|
|
|
|
func TestMatchChoosesLongestPathPrefix(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache := File{
|
|
Entries: []Entry{
|
|
{
|
|
ID: "one",
|
|
Title: "Generic Login",
|
|
Username: "generic",
|
|
Password: "secret1",
|
|
URL: "https://example.com/",
|
|
Host: "example.com",
|
|
},
|
|
{
|
|
ID: "two",
|
|
Title: "Admin Login",
|
|
Username: "admin",
|
|
Password: "secret2",
|
|
URL: "https://example.com/admin",
|
|
Host: "example.com",
|
|
},
|
|
},
|
|
}
|
|
|
|
got, ok := Match(cache, "https://example.com/admin/login")
|
|
if !ok {
|
|
t.Fatalf("Match() found no entry")
|
|
}
|
|
if got.ID != "two" {
|
|
t.Fatalf("Match() entry = %q, want two", got.ID)
|
|
}
|
|
}
|
|
|
|
func TestMatchSupportsAndroidAppPackageTargets(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache := File{
|
|
Entries: []Entry{
|
|
{
|
|
ID: "one",
|
|
Title: "Thunderbird",
|
|
Username: "mail-user",
|
|
Password: "secret1",
|
|
URL: "androidapp://org.mozilla.thunderbird/login",
|
|
Host: "org.mozilla.thunderbird",
|
|
},
|
|
},
|
|
}
|
|
|
|
got, ok := Match(cache, "androidapp://org.mozilla.thunderbird")
|
|
if !ok {
|
|
t.Fatalf("Match() found no entry")
|
|
}
|
|
if got.ID != "one" {
|
|
t.Fatalf("Match() entry = %q, want one", got.ID)
|
|
}
|
|
}
|
|
|
|
func TestMatchRejectsAmbiguousAndroidAppPackageTargets(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache := File{
|
|
Entries: []Entry{
|
|
{
|
|
ID: "one",
|
|
Title: "Thunderbird Primary",
|
|
Username: "mail-user",
|
|
Password: "secret1",
|
|
URL: "androidapp://org.mozilla.thunderbird",
|
|
Host: "org.mozilla.thunderbird",
|
|
},
|
|
{
|
|
ID: "two",
|
|
Title: "Thunderbird Secondary",
|
|
Username: "other-user",
|
|
Password: "secret2",
|
|
URL: "androidapp://org.mozilla.thunderbird",
|
|
Host: "org.mozilla.thunderbird",
|
|
},
|
|
},
|
|
}
|
|
|
|
if _, ok := Match(cache, "androidapp://org.mozilla.thunderbird"); ok {
|
|
t.Fatalf("Match() unexpectedly resolved ambiguous android app package target")
|
|
}
|
|
}
|
|
|
|
func TestMatchUsesAndroidAppCustomFieldTarget(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache := File{
|
|
Entries: []Entry{
|
|
{
|
|
ID: "one",
|
|
Title: "Blink",
|
|
Username: "blink-user",
|
|
Password: "secret1",
|
|
URL: "https://account.blinknetwork.com",
|
|
Host: "account.blinknetwork.com",
|
|
Targets: []string{"https://account.blinknetwork.com", "androidapp://com.blinknetwork.mobile2"},
|
|
},
|
|
},
|
|
}
|
|
|
|
got, ok := Match(cache, "androidapp://com.blinknetwork.mobile2")
|
|
if !ok {
|
|
t.Fatalf("Match() found no entry")
|
|
}
|
|
if got.ID != "one" {
|
|
t.Fatalf("Match() entry = %q, want one", got.ID)
|
|
}
|
|
}
|
|
|
|
func TestMatchUsesKP2AURLCustomFieldTarget(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
cache := File{
|
|
Entries: []Entry{
|
|
{
|
|
ID: "one",
|
|
Title: "Blink",
|
|
Username: "blink-user",
|
|
Password: "secret1",
|
|
URL: "https://blinknetwork.com",
|
|
Host: "blinknetwork.com",
|
|
Targets: []string{"https://blinknetwork.com", "https://account.blinknetwork.com"},
|
|
},
|
|
},
|
|
}
|
|
|
|
got, ok := Match(cache, "https://account.blinknetwork.com")
|
|
if !ok {
|
|
t.Fatalf("Match() found no entry")
|
|
}
|
|
if got.ID != "one" {
|
|
t.Fatalf("Match() entry = %q, want one", got.ID)
|
|
}
|
|
}
|