Compare commits

..

3 Commits

Author SHA1 Message Date
Joe Julian 332ab58f58 Implement local-first remote sync flow 2026-04-06 21:49:56 -07:00
joejulian 8433d536f6 Merge pull request 'Sync Arch package version metadata' (#1) from feature/archlinux-pkgver-sync into main
ci / lint-test (push) Successful in 1m15s
ci / build (push) Successful in 2m29s
Reviewed-on: #1
2026-04-06 18:49:12 +00:00
Joe Julian 21b2e60df4 Sync Arch package version metadata 2026-04-06 11:38:57 -07:00
6 changed files with 318 additions and 315 deletions
+3
View File
@@ -95,6 +95,9 @@ These features are product requirements, not “nice to have” ideas.
- Phone should optimize for low tap count, not purity of mobile patterns. - Phone should optimize for low tap count, not purity of mobile patterns.
- The stacked phone layout is the current preferred phone direction. - The stacked phone layout is the current preferred phone direction.
- Do not reintroduce the abandoned phone flow mode unless explicitly requested. - Do not reintroduce the abandoned phone flow mode unless explicitly requested.
- Make all test strings `Heist Movie` themed. Use characters, crews, casinos,
vaults, and locations from heist movies so test fixtures stay obviously fake
and consistent with the product theme.
## Architecture ## Architecture
+22 -22
View File
@@ -15,10 +15,10 @@ func TestRemoteBindingResolveUsesVaultProfileAndCredentialEntry(t *testing.T) {
model := vault.Model{ model := vault.Model{
Entries: []vault.Entry{ Entries: []vault.Entry{
{ {
ID: "tjulian-webdav", ID: "linuscaldwell-webdav",
Title: "WebDAV Sign-In", Title: "Bellagio WebDAV Sign-In",
Username: "tjulian", Username: "linuscaldwell",
Password: "token-1", Password: "bellagio-pass-1",
Path: []string{"Crew", "Internet"}, Path: []string{"Crew", "Internet"},
}, },
}, },
@@ -36,7 +36,7 @@ func TestRemoteBindingResolveUsesVaultProfileAndCredentialEntry(t *testing.T) {
binding := RemoteBinding{ binding := RemoteBinding{
LocalVaultPath: "/tmp/family.kdbx", LocalVaultPath: "/tmp/family.kdbx",
RemoteProfileID: "family-webdav", RemoteProfileID: "family-webdav",
CredentialEntryID: "tjulian-webdav", CredentialEntryID: "linuscaldwell-webdav",
SyncMode: SyncModeAutomaticOnOpenSave, SyncMode: SyncModeAutomaticOnOpenSave,
} }
@@ -50,11 +50,11 @@ func TestRemoteBindingResolveUsesVaultProfileAndCredentialEntry(t *testing.T) {
if got := resolved.Profile.Path; got != "files/family/keepass.kdbx" { if got := resolved.Profile.Path; got != "files/family/keepass.kdbx" {
t.Fatalf("resolved profile path = %q, want files/family/keepass.kdbx", got) t.Fatalf("resolved profile path = %q, want files/family/keepass.kdbx", got)
} }
if got := resolved.Credentials.Username; got != "tjulian" { if got := resolved.Credentials.Username; got != "linuscaldwell" {
t.Fatalf("resolved credentials username = %q, want tjulian", got) t.Fatalf("resolved credentials username = %q, want linuscaldwell", got)
} }
if got := resolved.Credentials.Password; got != "token-1" { if got := resolved.Credentials.Password; got != "bellagio-pass-1" {
t.Fatalf("resolved credentials password = %q, want token-1", got) t.Fatalf("resolved credentials password = %q, want bellagio-pass-1", got)
} }
} }
@@ -63,7 +63,7 @@ func TestRemoteBindingResolveFailsWhenVaultReferenceIsMissing(t *testing.T) {
model := vault.Model{ model := vault.Model{
Entries: []vault.Entry{ Entries: []vault.Entry{
{ID: "tjulian-webdav", Title: "WebDAV Sign-In"}, {ID: "linuscaldwell-webdav", Title: "Bellagio WebDAV Sign-In"},
}, },
} }
@@ -108,7 +108,7 @@ func TestRemoteBindingJSONStoresOnlyNonSecretReferences(t *testing.T) {
} }
text := string(content) text := string(content)
for _, disallowed := range []string{"token-1", "password", "username", "baseUrl"} { for _, disallowed := range []string{"bellagio-pass-1", "password", "username", "baseUrl"} {
if strings.Contains(text, disallowed) { if strings.Contains(text, disallowed) {
t.Fatalf("binding JSON %q unexpectedly contains %q", text, disallowed) t.Fatalf("binding JSON %q unexpectedly contains %q", text, disallowed)
} }
@@ -127,9 +127,9 @@ func TestConfigureRemoteBindingStoresProfileAndCredentialsInVault(t *testing.T)
BaseURL: "https://dav.example.invalid/remote.php/dav", BaseURL: "https://dav.example.invalid/remote.php/dav",
RemotePath: "files/family/keepass.kdbx", RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1", CredentialEntryID: "remote-creds-1",
CredentialTitle: "WebDAV Sign-In", CredentialTitle: "Bellagio WebDAV Sign-In",
Username: "tjulian", Username: "linuscaldwell",
Password: "token-1", Password: "bellagio-pass-1",
CredentialPath: []string{"Crew", "Internet"}, CredentialPath: []string{"Crew", "Internet"},
SyncMode: SyncModeAutomaticOnOpenSave, SyncMode: SyncModeAutomaticOnOpenSave,
}) })
@@ -148,8 +148,8 @@ func TestConfigureRemoteBindingStoresProfileAndCredentialsInVault(t *testing.T)
if err != nil { if err != nil {
t.Fatalf("EntryByID(remote-creds-1) error = %v", err) t.Fatalf("EntryByID(remote-creds-1) error = %v", err)
} }
if credentials.Username != "tjulian" || credentials.Password != "token-1" { if credentials.Username != "linuscaldwell" || credentials.Password != "bellagio-pass-1" {
t.Fatalf("stored credential entry = %#v, want tjulian/token-1", credentials) t.Fatalf("stored credential entry = %#v, want linuscaldwell/bellagio-pass-1", credentials)
} }
if credentials.URL != "https://dav.example.invalid/remote.php/dav" { if credentials.URL != "https://dav.example.invalid/remote.php/dav" {
t.Fatalf("stored credential entry URL = %q, want remote.php/dav URL", credentials.URL) t.Fatalf("stored credential entry URL = %q, want remote.php/dav URL", credentials.URL)
@@ -177,7 +177,7 @@ func TestConfigureRemoteBindingRejectsIncompleteInput(t *testing.T) {
BaseURL: "https://dav.example.invalid/remote.php/dav", BaseURL: "https://dav.example.invalid/remote.php/dav",
RemotePath: "files/family/keepass.kdbx", RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1", CredentialEntryID: "remote-creds-1",
Password: "token-1", Password: "bellagio-pass-1",
}, },
}, },
{ {
@@ -187,7 +187,7 @@ func TestConfigureRemoteBindingRejectsIncompleteInput(t *testing.T) {
RemoteProfileID: "family-webdav", RemoteProfileID: "family-webdav",
RemotePath: "files/family/keepass.kdbx", RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1", CredentialEntryID: "remote-creds-1",
Password: "token-1", Password: "bellagio-pass-1",
}, },
}, },
{ {
@@ -197,7 +197,7 @@ func TestConfigureRemoteBindingRejectsIncompleteInput(t *testing.T) {
RemoteProfileID: "family-webdav", RemoteProfileID: "family-webdav",
BaseURL: "https://dav.example.invalid/remote.php/dav", BaseURL: "https://dav.example.invalid/remote.php/dav",
RemotePath: "files/family/keepass.kdbx", RemotePath: "files/family/keepass.kdbx",
Password: "token-1", Password: "bellagio-pass-1",
}, },
}, },
} { } {
@@ -219,9 +219,9 @@ func TestRemoveRemoteBindingRemovesProfileAndCredentialsFromVault(t *testing.T)
model := vault.Model{ model := vault.Model{
Entries: []vault.Entry{{ Entries: []vault.Entry{{
ID: "remote-creds-1", ID: "remote-creds-1",
Title: "WebDAV Sign-In", Title: "Bellagio WebDAV Sign-In",
Username: "tjulian", Username: "linuscaldwell",
Password: "token-1", Password: "bellagio-pass-1",
}}, }},
RemoteProfiles: []vault.RemoteProfile{{ RemoteProfiles: []vault.RemoteProfile{{
ID: "family-webdav", ID: "family-webdav",
+40 -40
View File
@@ -22,7 +22,7 @@ func TestVisibleEntriesFollowsCurrentPathWithoutSearch(t *testing.T) {
Entries: []vault.Entry{ Entries: []vault.Entry{
{ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}}, {ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}},
{ID: "vault-console", Title: "Vault Console", Path: []string{"Crew", "Internet"}}, {ID: "vault-console", Title: "Vault Console", Path: []string{"Crew", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Home Assistant"}}, {ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Security Office"}},
}, },
}, },
}, },
@@ -54,7 +54,7 @@ func TestVisibleEntriesAtParentGroupOnlyShowsDirectEntries(t *testing.T) {
{ID: "joe-note", Title: "Crew Note", Path: []string{"Crew"}}, {ID: "joe-note", Title: "Crew Note", Path: []string{"Crew"}},
{ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}}, {ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}},
{ID: "vault-console", Title: "Vault Console", Path: []string{"Crew", "Internet"}}, {ID: "vault-console", Title: "Vault Console", Path: []string{"Crew", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Home Assistant"}}, {ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Security Office"}},
}, },
}, },
}, },
@@ -211,7 +211,7 @@ func TestRemoteCredentialEntriesReturnsSortedVaultEntries(t *testing.T) {
Entries: []vault.Entry{ Entries: []vault.Entry{
{ID: "cred-2", Title: "Zulu Sign-In", Username: "zuser", Path: []string{"Crew", "Internet"}}, {ID: "cred-2", Title: "Zulu Sign-In", Username: "zuser", Path: []string{"Crew", "Internet"}},
{ID: "cred-1", Title: "Alpha Sign-In", Username: "auser", Path: []string{"Crew", "Internet"}}, {ID: "cred-1", Title: "Alpha Sign-In", Username: "auser", Path: []string{"Crew", "Internet"}},
{ID: "cred-3", Title: "Beta Sign-In", Username: "buser", Path: []string{"Crew", "Home"}}, {ID: "cred-3", Title: "Mint Sign-In", Username: "frankcatton", Path: []string{"Crew", "Safe House"}},
}, },
}, },
}, },
@@ -238,7 +238,7 @@ func TestVisibleEntriesUsesGlobalSearchWhenQueryPresent(t *testing.T) {
Entries: []vault.Entry{ Entries: []vault.Entry{
{ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}}, {ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}},
{ID: "vault-console", Title: "Vault Console", URL: "https://vault.crew.example.invalid", Path: []string{"Crew", "Internet"}}, {ID: "vault-console", Title: "Vault Console", URL: "https://vault.crew.example.invalid", Path: []string{"Crew", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", URL: "https://surveillance.crew.example.invalid", Path: []string{"Crew", "Home Assistant"}}, {ID: "surveillance-console", Title: "Surveillance Console", URL: "https://surveillance.crew.example.invalid", Path: []string{"Crew", "Security Office"}},
}, },
}, },
}, },
@@ -252,7 +252,7 @@ func TestVisibleEntriesUsesGlobalSearchWhenQueryPresent(t *testing.T) {
} }
if len(got) != 1 || got[0].Title != "Surveillance Console" { if len(got) != 1 || got[0].Title != "Surveillance Console" {
t.Fatalf("VisibleEntries() = %#v, want Home Assistant search match", got) t.Fatalf("VisibleEntries() = %#v, want Security Office search match", got)
} }
} }
@@ -265,7 +265,7 @@ func TestVisibleEntriesReturnsDescendantsAfterClearingSearch(t *testing.T) {
Entries: []vault.Entry{ Entries: []vault.Entry{
{ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}}, {ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}},
{ID: "vault-console", Title: "Vault Console", URL: "https://vault.crew.example.invalid", Path: []string{"Crew", "Internet"}}, {ID: "vault-console", Title: "Vault Console", URL: "https://vault.crew.example.invalid", Path: []string{"Crew", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", URL: "https://surveillance.crew.example.invalid", Path: []string{"Crew", "Home Assistant"}}, {ID: "surveillance-console", Title: "Surveillance Console", URL: "https://surveillance.crew.example.invalid", Path: []string{"Crew", "Security Office"}},
}, },
}, },
}, },
@@ -415,7 +415,7 @@ func TestVisibleEntriesUsesGlobalSearchWithinRecycleBin(t *testing.T) {
model: vault.Model{ model: vault.Model{
RecycleBin: []vault.Entry{ RecycleBin: []vault.Entry{
{ID: "deleted-1", Title: "Deleted Bellagio", Path: []string{"Root", "Internet"}}, {ID: "deleted-1", Title: "Deleted Bellagio", Path: []string{"Root", "Internet"}},
{ID: "deleted-2", Title: "Deleted HVAC", URL: "https://climate.example.com", Path: []string{"Root", "Home"}}, {ID: "deleted-2", Title: "Deleted Vault Vent", URL: "https://climate.example.com", Path: []string{"Root", "Safe House"}},
}, },
}, },
}, },
@@ -484,7 +484,7 @@ func TestChildGroupsUsesCurrentModelAndCurrentPath(t *testing.T) {
model: vault.Model{ model: vault.Model{
Entries: []vault.Entry{ Entries: []vault.Entry{
{ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}}, {ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Home Assistant"}}, {ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Security Office"}},
{ID: "alma", Title: "Alma (WA Prep)", Path: []string{"Tricia", "School"}}, {ID: "alma", Title: "Alma (WA Prep)", Path: []string{"Tricia", "School"}},
}, },
}, },
@@ -497,8 +497,8 @@ func TestChildGroupsUsesCurrentModelAndCurrentPath(t *testing.T) {
t.Fatalf("ChildGroups() error = %v", err) t.Fatalf("ChildGroups() error = %v", err)
} }
if !slices.Equal(got, []string{"Home Assistant", "Internet"}) { if !slices.Equal(got, []string{"Internet", "Security Office"}) {
t.Fatalf("ChildGroups() = %v, want [Home Assistant Internet]", got) t.Fatalf("ChildGroups() = %v, want [Internet Security Office]", got)
} }
} }
@@ -668,7 +668,7 @@ func TestUpsertEntryPersistsEntryAndSelectsIt(t *testing.T) {
ID: "vault-console", ID: "vault-console",
Title: "Vault Console", Title: "Vault Console",
Username: "dannyocean", Username: "dannyocean",
Password: "token-1", Password: "bellagio-pass-1",
URL: "https://vault.crew.example.invalid", URL: "https://vault.crew.example.invalid",
Path: []string{"Root", "Internet"}, Path: []string{"Root", "Internet"},
} }
@@ -684,7 +684,7 @@ func TestUpsertEntryPersistsEntryAndSelectsIt(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("VisibleEntries() error = %v", err) t.Fatalf("VisibleEntries() error = %v", err)
} }
if len(got) != 1 || got[0].Password != "token-1" { if len(got) != 1 || got[0].Password != "bellagio-pass-1" {
t.Fatalf("VisibleEntries() = %#v, want persisted vault-console entry", got) t.Fatalf("VisibleEntries() = %#v, want persisted vault-console entry", got)
} }
@@ -1037,9 +1037,9 @@ func TestOpenBoundRemoteVaultResolvesClientFromVaultBinding(t *testing.T) {
Entries: []vault.Entry{ Entries: []vault.Entry{
{ {
ID: "remote-creds-1", ID: "remote-creds-1",
Title: "WebDAV Sign-In", Title: "Bellagio WebDAV Sign-In",
Username: "tjulian", Username: "linuscaldwell",
Password: "token-1", Password: "bellagio-pass-1",
Path: []string{"Crew", "Internet"}, Path: []string{"Crew", "Internet"},
}, },
}, },
@@ -1074,11 +1074,11 @@ func TestOpenBoundRemoteVaultResolvesClientFromVaultBinding(t *testing.T) {
if got := sess.remoteClient.BaseURL; got != "https://dav.example.invalid/remote.php/dav" { if got := sess.remoteClient.BaseURL; got != "https://dav.example.invalid/remote.php/dav" {
t.Fatalf("remote client base URL = %q, want remote.php/dav URL", got) t.Fatalf("remote client base URL = %q, want remote.php/dav URL", got)
} }
if got := sess.remoteClient.Username; got != "tjulian" { if got := sess.remoteClient.Username; got != "linuscaldwell" {
t.Fatalf("remote client username = %q, want tjulian", got) t.Fatalf("remote client username = %q, want linuscaldwell", got)
} }
if got := sess.remoteClient.Password; got != "token-1" { if got := sess.remoteClient.Password; got != "bellagio-pass-1" {
t.Fatalf("remote client password = %q, want token-1", got) t.Fatalf("remote client password = %q, want bellagio-pass-1", got)
} }
if got := sess.remotePath; got != "files/family/keepass.kdbx" { if got := sess.remotePath; got != "files/family/keepass.kdbx" {
t.Fatalf("remotePath = %q, want files/family/keepass.kdbx", got) t.Fatalf("remotePath = %q, want files/family/keepass.kdbx", got)
@@ -1123,9 +1123,9 @@ func TestConfigureRemoteBindingPersistsIntoCurrentVaultAndMarksDirty(t *testing.
BaseURL: "https://dav.example.invalid/remote.php/dav", BaseURL: "https://dav.example.invalid/remote.php/dav",
RemotePath: "files/family/keepass.kdbx", RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1", CredentialEntryID: "remote-creds-1",
CredentialTitle: "WebDAV Sign-In", CredentialTitle: "Bellagio WebDAV Sign-In",
Username: "tjulian", Username: "linuscaldwell",
Password: "token-1", Password: "bellagio-pass-1",
CredentialPath: []string{"Crew", "Internet"}, CredentialPath: []string{"Crew", "Internet"},
SyncMode: SyncModeAutomaticOnOpenSave, SyncMode: SyncModeAutomaticOnOpenSave,
}) })
@@ -1146,8 +1146,8 @@ func TestConfigureRemoteBindingPersistsIntoCurrentVaultAndMarksDirty(t *testing.
if err != nil { if err != nil {
t.Fatalf("EntryByID(remote-creds-1) error = %v", err) t.Fatalf("EntryByID(remote-creds-1) error = %v", err)
} }
if credentials.Username != "tjulian" || credentials.Password != "token-1" { if credentials.Username != "linuscaldwell" || credentials.Password != "bellagio-pass-1" {
t.Fatalf("stored credential entry = %#v, want tjulian/token-1", credentials) t.Fatalf("stored credential entry = %#v, want linuscaldwell/bellagio-pass-1", credentials)
} }
} }
@@ -1161,7 +1161,7 @@ func TestConfigureRemoteBindingRequiresMutableSession(t *testing.T) {
BaseURL: "https://dav.example.invalid/remote.php/dav", BaseURL: "https://dav.example.invalid/remote.php/dav",
RemotePath: "files/family/keepass.kdbx", RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1", CredentialEntryID: "remote-creds-1",
Password: "token-1", Password: "bellagio-pass-1",
}) })
if err == nil { if err == nil {
t.Fatal("ConfigureRemoteBinding() error = nil, want mutability error") t.Fatal("ConfigureRemoteBinding() error = nil, want mutability error")
@@ -1174,9 +1174,9 @@ func TestRemoveRemoteBindingRemovesVaultDataAndMarksDirty(t *testing.T) {
sess := &mutableStubSession{model: vault.Model{ sess := &mutableStubSession{model: vault.Model{
Entries: []vault.Entry{{ Entries: []vault.Entry{{
ID: "remote-creds-1", ID: "remote-creds-1",
Title: "WebDAV Sign-In", Title: "Bellagio WebDAV Sign-In",
Username: "tjulian", Username: "linuscaldwell",
Password: "token-1", Password: "bellagio-pass-1",
}}, }},
RemoteProfiles: []vault.RemoteProfile{{ RemoteProfiles: []vault.RemoteProfile{{
ID: "family-webdav", ID: "family-webdav",
@@ -1393,10 +1393,10 @@ func TestNavigateToPathReplacesPathAndClearsSelection(t *testing.T) {
SelectedEntryID: "vault-console", SelectedEntryID: "vault-console",
} }
state.NavigateToPath([]string{"Root", "Home Assistant"}) state.NavigateToPath([]string{"Root", "Security Office"})
if !slices.Equal(state.CurrentPath, []string{"Root", "Home Assistant"}) { if !slices.Equal(state.CurrentPath, []string{"Root", "Security Office"}) {
t.Fatalf("CurrentPath = %v, want [Root Home Assistant]", state.CurrentPath) t.Fatalf("CurrentPath = %v, want [Root Security Office]", state.CurrentPath)
} }
if got := state.SelectedEntryID; got != "" { if got := state.SelectedEntryID; got != "" {
t.Fatalf("SelectedEntryID = %q, want empty", got) t.Fatalf("SelectedEntryID = %q, want empty", got)
@@ -1429,8 +1429,8 @@ func TestDeleteCurrentGroupMovesToParentAndMarksDirty(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("ChildGroups() error = %v", err) t.Fatalf("ChildGroups() error = %v", err)
} }
if !slices.Equal(got, []string{"Home Assistant", "Internet"}) { if !slices.Equal(got, []string{"Internet", "Security Office"}) {
t.Fatalf("ChildGroups() = %v, want [Home Assistant Internet]", got) t.Fatalf("ChildGroups() = %v, want [Internet Security Office]", got)
} }
} }
@@ -1452,8 +1452,8 @@ func TestCreateGroupPersistsGroupAndMarksDirty(t *testing.T) {
t.Fatalf("ChildGroups() error = %v", err) t.Fatalf("ChildGroups() error = %v", err)
} }
if !slices.Equal(got, []string{"Finance", "Home Assistant", "Internet"}) { if !slices.Equal(got, []string{"Finance", "Internet", "Security Office"}) {
t.Fatalf("ChildGroups() = %v, want Finance, Home Assistant, Internet", got) t.Fatalf("ChildGroups() = %v, want Finance, Internet, Security Office", got)
} }
if !state.Dirty { if !state.Dirty {
t.Fatal("Dirty = false, want true after CreateGroup") t.Fatal("Dirty = false, want true after CreateGroup")
@@ -1526,8 +1526,8 @@ func TestDeleteCurrentGroupRemovesItNavigatesToParentAndMarksDirty(t *testing.T)
t.Fatalf("ChildGroups() error = %v", err) t.Fatalf("ChildGroups() error = %v", err)
} }
if !slices.Equal(got, []string{"Home Assistant", "Internet"}) { if !slices.Equal(got, []string{"Internet", "Security Office"}) {
t.Fatalf("ChildGroups() = %v, want [Home Assistant Internet]", got) t.Fatalf("ChildGroups() = %v, want [Internet Security Office]", got)
} }
if !state.Dirty { if !state.Dirty {
t.Fatal("Dirty = false, want true after DeleteCurrentGroup") t.Fatal("Dirty = false, want true after DeleteCurrentGroup")
@@ -1544,11 +1544,11 @@ func TestMoveSelectedEntryPersistsPathChangeAndMarksDirty(t *testing.T) {
SelectedEntryID: "bellagio", SelectedEntryID: "bellagio",
} }
if err := state.MoveSelectedEntry([]string{"Root", "Home Assistant"}); err != nil { if err := state.MoveSelectedEntry([]string{"Root", "Security Office"}); err != nil {
t.Fatalf("MoveSelectedEntry() error = %v", err) t.Fatalf("MoveSelectedEntry() error = %v", err)
} }
state.NavigateToPath([]string{"Root", "Home Assistant"}) state.NavigateToPath([]string{"Root", "Security Office"})
got, err := state.VisibleEntries() got, err := state.VisibleEntries()
if err != nil { if err != nil {
t.Fatalf("VisibleEntries() error = %v", err) t.Fatalf("VisibleEntries() error = %v", err)
@@ -1756,7 +1756,7 @@ func testVaultModel() vault.Model {
return vault.Model{ return vault.Model{
Entries: []vault.Entry{ Entries: []vault.Entry{
{ID: "bellagio", Title: "Bellagio", Path: []string{"Root", "Internet"}}, {ID: "bellagio", Title: "Bellagio", Path: []string{"Root", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Root", "Home Assistant"}}, {ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Root", "Security Office"}},
}, },
} }
} }
+211 -211
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
pkgbase = keepassgo-git pkgbase = keepassgo-git
pkgdesc = KeePass-compatible password manager written in Go pkgdesc = KeePass-compatible password manager written in Go
pkgver = r160.5fa79bd pkgver = r165.1c72a50
pkgrel = 1 pkgrel = 1
url = https://git.julianfamily.org/joejulian/keepassgo url = https://git.julianfamily.org/joejulian/keepassgo
arch = x86_64 arch = x86_64
+41 -41
View File
@@ -23,10 +23,10 @@ func TestLoadKDBXBuildsModelFromNestedGroups(t *testing.T) {
mustGroup("Root", mustGroup("Root",
mustGroup("Internet", mustGroup("Internet",
mustEntry("Bellagio", "rustyryan", "https://bellagio.example.invalid", "hunter2"), mustEntry("Bellagio", "rustyryan", "https://bellagio.example.invalid", "hunter2"),
mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "token-1"), mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "bellagio-pass-1"),
), ),
mustGroup("Home Assistant", mustGroup("Security Office",
mustEntry("Surveillance Console", "codex", "https://surveillance.crew.example.invalid", "token-2"), mustEntry("Surveillance Console", "bashertarr", "https://surveillance.crew.example.invalid", "bellagio-pass-2"),
), ),
), ),
}, },
@@ -62,15 +62,15 @@ func TestLoadKDBXBuildsModelFromNestedGroups(t *testing.T) {
} }
groups := model.ChildGroups([]string{"Root"}) groups := model.ChildGroups([]string{"Root"})
if len(groups) != 2 || groups[0] != "Home Assistant" || groups[1] != "Internet" { if len(groups) != 2 || groups[0] != "Internet" || groups[1] != "Security Office" {
t.Fatalf("ChildGroups() = %v, want [Home Assistant Internet]", groups) t.Fatalf("ChildGroups() = %v, want [Internet Security Office]", groups)
} }
} }
func TestLoadKDBXPreservesEntryDetails(t *testing.T) { func TestLoadKDBXPreservesEntryDetails(t *testing.T) {
t.Parallel() t.Parallel()
entry := mustEntry("Surveillance Console", "codex", "https://surveillance.crew.example.invalid", "token-2") entry := mustEntry("Surveillance Console", "bashertarr", "https://surveillance.crew.example.invalid", "bellagio-pass-2")
entry.Tags = "automation; home" entry.Tags = "automation; home"
entry.Values = append(entry.Values, entry.Values = append(entry.Values,
mkValue("Notes", "Long-lived token used by Codex for home automation tasks."), mkValue("Notes", "Long-lived token used by Codex for home automation tasks."),
@@ -84,7 +84,7 @@ func TestLoadKDBXPreservesEntryDetails(t *testing.T) {
Meta: gokeepasslib.NewMetaData(), Meta: gokeepasslib.NewMetaData(),
Root: &gokeepasslib.RootData{ Root: &gokeepasslib.RootData{
Groups: []gokeepasslib.Group{ Groups: []gokeepasslib.Group{
mustGroup("Root", mustGroup("Home Assistant", entry)), mustGroup("Root", mustGroup("Security Office", entry)),
}, },
}, },
}, },
@@ -104,13 +104,13 @@ func TestLoadKDBXPreservesEntryDetails(t *testing.T) {
t.Fatalf("LoadKDBX failed: %v", err) t.Fatalf("LoadKDBX failed: %v", err)
} }
got := model.EntriesInPath([]string{"Root", "Home Assistant"}) got := model.EntriesInPath([]string{"Root", "Security Office"})
if len(got) != 1 { if len(got) != 1 {
t.Fatalf("len(EntriesInPath()) = %d, want 1", len(got)) t.Fatalf("len(EntriesInPath()) = %d, want 1", len(got))
} }
if got[0].Password != "token-2" { if got[0].Password != "bellagio-pass-2" {
t.Fatalf("Entry.Password = %q, want %q", got[0].Password, "token-2") t.Fatalf("Entry.Password = %q, want %q", got[0].Password, "bellagio-pass-2")
} }
if got[0].Notes != "Long-lived token used by Codex for home automation tasks." { if got[0].Notes != "Long-lived token used by Codex for home automation tasks." {
@@ -135,7 +135,7 @@ func TestSaveKDBXRoundTripsModel(t *testing.T) {
ID: "entry-1", ID: "entry-1",
Title: "Vault Console", Title: "Vault Console",
Username: "dannyocean", Username: "dannyocean",
Password: "token-1", Password: "bellagio-pass-1",
URL: "https://vault.crew.example.invalid", URL: "https://vault.crew.example.invalid",
Notes: "Personal git server token entry used for automation and CLI auth.", Notes: "Personal git server token entry used for automation and CLI auth.",
Tags: []string{"git", "infra"}, Tags: []string{"git", "infra"},
@@ -147,12 +147,12 @@ func TestSaveKDBXRoundTripsModel(t *testing.T) {
{ {
ID: "entry-2", ID: "entry-2",
Title: "Surveillance Console", Title: "Surveillance Console",
Username: "codex", Username: "bashertarr",
Password: "token-2", Password: "bellagio-pass-2",
URL: "https://surveillance.crew.example.invalid", URL: "https://surveillance.crew.example.invalid",
Notes: "Long-lived token used by Codex for home automation tasks.", Notes: "Long-lived token used by Codex for home automation tasks.",
Tags: []string{"automation", "home"}, Tags: []string{"automation", "home"},
Path: []string{"Root", "Home Assistant"}, Path: []string{"Root", "Security Office"},
}, },
}, },
} }
@@ -180,13 +180,13 @@ func TestSaveKDBXRoundTripsModel(t *testing.T) {
t.Fatalf("Search(\"git\") X-Role = %q, want %q", got[0].Entry.Fields["X-Role"], "automation") t.Fatalf("Search(\"git\") X-Role = %q, want %q", got[0].Entry.Fields["X-Role"], "automation")
} }
homeAssistant := loaded.EntriesInPath([]string{"Root", "Home Assistant"}) homeAssistant := loaded.EntriesInPath([]string{"Root", "Security Office"})
if len(homeAssistant) != 1 { if len(homeAssistant) != 1 {
t.Fatalf("len(EntriesInPath(Home Assistant)) = %d, want 1", len(homeAssistant)) t.Fatalf("len(EntriesInPath(Security Office)) = %d, want 1", len(homeAssistant))
} }
if homeAssistant[0].Password != "token-2" { if homeAssistant[0].Password != "bellagio-pass-2" {
t.Fatalf("Home Assistant password = %q, want %q", homeAssistant[0].Password, "token-2") t.Fatalf("Security Office password = %q, want %q", homeAssistant[0].Password, "bellagio-pass-2")
} }
} }
@@ -341,10 +341,10 @@ func TestSaveKDBXRoundTripsRecycleBinEntries(t *testing.T) {
{ {
ID: "entry-1", ID: "entry-1",
Title: "Surveillance Console", Title: "Surveillance Console",
Username: "codex", Username: "bashertarr",
Password: "token-2", Password: "bellagio-pass-2",
URL: "https://surveillance.crew.example.invalid", URL: "https://surveillance.crew.example.invalid",
Path: []string{"Root", "Home Assistant"}, Path: []string{"Root", "Security Office"},
}, },
}, },
} }
@@ -367,8 +367,8 @@ func TestSaveKDBXRoundTripsRecycleBinEntries(t *testing.T) {
t.Fatalf("RecycleBin[0].Title = %q, want %q", loaded.RecycleBin[0].Title, "Surveillance Console") t.Fatalf("RecycleBin[0].Title = %q, want %q", loaded.RecycleBin[0].Title, "Surveillance Console")
} }
if len(loaded.RecycleBin[0].Path) != 2 || loaded.RecycleBin[0].Path[0] != "Root" || loaded.RecycleBin[0].Path[1] != "Home Assistant" { if len(loaded.RecycleBin[0].Path) != 2 || loaded.RecycleBin[0].Path[0] != "Root" || loaded.RecycleBin[0].Path[1] != "Security Office" {
t.Fatalf("RecycleBin[0].Path = %v, want [Root Home Assistant]", loaded.RecycleBin[0].Path) t.Fatalf("RecycleBin[0].Path = %v, want [Root Security Office]", loaded.RecycleBin[0].Path)
} }
if len(loaded.Entries) != 0 { if len(loaded.Entries) != 0 {
@@ -402,7 +402,7 @@ func TestLoadKDBXWithKeyFileCredentials(t *testing.T) {
Meta: gokeepasslib.NewMetaData(), Meta: gokeepasslib.NewMetaData(),
Root: &gokeepasslib.RootData{ Root: &gokeepasslib.RootData{
Groups: []gokeepasslib.Group{ Groups: []gokeepasslib.Group{
mustGroup("Root", mustGroup("Internet", mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "token-1"))), mustGroup("Root", mustGroup("Internet", mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "bellagio-pass-1"))),
}, },
}, },
}, },
@@ -423,7 +423,7 @@ func TestLoadKDBXWithKeyFileCredentials(t *testing.T) {
} }
got := model.Search("vault") got := model.Search("vault")
if len(got) != 1 || got[0].Entry.Password != "token-1" { if len(got) != 1 || got[0].Entry.Password != "bellagio-pass-1" {
t.Fatalf("LoadKDBXWithKey() = %#v, want password-preserving vault entry", got) t.Fatalf("LoadKDBXWithKey() = %#v, want password-preserving vault entry", got)
} }
} }
@@ -457,7 +457,7 @@ func TestLoadKDBXWithCompositeCredentials(t *testing.T) {
Meta: gokeepasslib.NewMetaData(), Meta: gokeepasslib.NewMetaData(),
Root: &gokeepasslib.RootData{ Root: &gokeepasslib.RootData{
Groups: []gokeepasslib.Group{ Groups: []gokeepasslib.Group{
mustGroup("Root", mustGroup("Home Assistant", mustEntry("Surveillance Console", "codex", "https://surveillance.crew.example.invalid", "token-2"))), mustGroup("Root", mustGroup("Security Office", mustEntry("Surveillance Console", "bashertarr", "https://surveillance.crew.example.invalid", "bellagio-pass-2"))),
}, },
}, },
}, },
@@ -480,9 +480,9 @@ func TestLoadKDBXWithCompositeCredentials(t *testing.T) {
t.Fatalf("LoadKDBXWithKey() error = %v", err) t.Fatalf("LoadKDBXWithKey() error = %v", err)
} }
got := model.EntriesInPath([]string{"Root", "Home Assistant"}) got := model.EntriesInPath([]string{"Root", "Security Office"})
if len(got) != 1 || got[0].Password != "token-2" { if len(got) != 1 || got[0].Password != "bellagio-pass-2" {
t.Fatalf("LoadKDBXWithKey() = %#v, want Home Assistant entry with password", got) t.Fatalf("LoadKDBXWithKey() = %#v, want Security Office entry with password", got)
} }
} }
@@ -496,7 +496,7 @@ func TestLoadKDBXReturnsInvalidCredentialsError(t *testing.T) {
Meta: gokeepasslib.NewMetaData(), Meta: gokeepasslib.NewMetaData(),
Root: &gokeepasslib.RootData{ Root: &gokeepasslib.RootData{
Groups: []gokeepasslib.Group{ Groups: []gokeepasslib.Group{
mustGroup("Root", mustGroup("Internet", mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "token-1"))), mustGroup("Root", mustGroup("Internet", mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "bellagio-pass-1"))),
}, },
}, },
}, },
@@ -537,7 +537,7 @@ func TestSaveKDBXWithKeyRoundTripsModel(t *testing.T) {
ID: "vault-console", ID: "vault-console",
Title: "Vault Console", Title: "Vault Console",
Username: "dannyocean", Username: "dannyocean",
Password: "token-1", Password: "bellagio-pass-1",
URL: "https://vault.crew.example.invalid", URL: "https://vault.crew.example.invalid",
Path: []string{"Root", "Internet"}, Path: []string{"Root", "Internet"},
}, },
@@ -555,7 +555,7 @@ func TestSaveKDBXWithKeyRoundTripsModel(t *testing.T) {
} }
got := loaded.Search("vault") got := loaded.Search("vault")
if len(got) != 1 || got[0].Entry.Password != "token-1" { if len(got) != 1 || got[0].Entry.Password != "bellagio-pass-1" {
t.Fatalf("round-trip with key file = %#v, want vault entry with password", got) t.Fatalf("round-trip with key file = %#v, want vault entry with password", got)
} }
} }
@@ -579,10 +579,10 @@ func TestSaveKDBXWithCompositeKeyRoundTripsModel(t *testing.T) {
{ {
ID: "surveillance-console", ID: "surveillance-console",
Title: "Surveillance Console", Title: "Surveillance Console",
Username: "codex", Username: "bashertarr",
Password: "token-2", Password: "bellagio-pass-2",
URL: "https://surveillance.crew.example.invalid", URL: "https://surveillance.crew.example.invalid",
Path: []string{"Root", "Home Assistant"}, Path: []string{"Root", "Security Office"},
}, },
}, },
} }
@@ -602,9 +602,9 @@ func TestSaveKDBXWithCompositeKeyRoundTripsModel(t *testing.T) {
t.Fatalf("LoadKDBXWithKey() error = %v", err) t.Fatalf("LoadKDBXWithKey() error = %v", err)
} }
got := loaded.EntriesInPath([]string{"Root", "Home Assistant"}) got := loaded.EntriesInPath([]string{"Root", "Security Office"})
if len(got) != 1 || got[0].Password != "token-2" { if len(got) != 1 || got[0].Password != "bellagio-pass-2" {
t.Fatalf("composite key round-trip = %#v, want Home Assistant entry with password", got) t.Fatalf("composite key round-trip = %#v, want Security Office entry with password", got)
} }
} }
@@ -617,7 +617,7 @@ func TestKDBXRoundTripsEntryAttachments(t *testing.T) {
ID: "vault-console", ID: "vault-console",
Title: "Vault Console", Title: "Vault Console",
Username: "dannyocean", Username: "dannyocean",
Password: "token-1", Password: "bellagio-pass-1",
URL: "https://vault.crew.example.invalid", URL: "https://vault.crew.example.invalid",
Path: []string{"Root", "Internet"}, Path: []string{"Root", "Internet"},
Attachments: map[string][]byte{ Attachments: map[string][]byte{
@@ -656,7 +656,7 @@ func TestKDBXReopenCyclesPreserveStableIDsAndCrossFeatureState(t *testing.T) {
ID: "entry-1", ID: "entry-1",
Title: "Vault Console", Title: "Vault Console",
Username: "dannyocean", Username: "dannyocean",
Password: "token-2", Password: "bellagio-pass-2",
URL: "https://vault.crew.example.invalid", URL: "https://vault.crew.example.invalid",
Notes: "Current credential", Notes: "Current credential",
Path: []string{"Root", "Internet"}, Path: []string{"Root", "Internet"},
@@ -668,7 +668,7 @@ func TestKDBXReopenCyclesPreserveStableIDsAndCrossFeatureState(t *testing.T) {
ID: "entry-1-history-1", ID: "entry-1-history-1",
Title: "Vault Console", Title: "Vault Console",
Username: "dannyocean", Username: "dannyocean",
Password: "token-1", Password: "bellagio-pass-1",
URL: "https://vault.crew.example.invalid", URL: "https://vault.crew.example.invalid",
Notes: "Original credential", Notes: "Original credential",
Path: []string{"Root", "Internet"}, Path: []string{"Root", "Internet"},