Compare commits

..

46 Commits

Author SHA1 Message Date
Joe Julian 4b7a898a7f Fix Arch PKGBUILD generation target 2026-04-06 21:25:55 -07:00
Joe Julian 5bf1bdb5f3 Ignore local keepassgo binary 2026-04-06 21:22:17 -07:00
Joe Julian c09ae8a5fb Template Arch PKGBUILD generation 2026-04-06 21:21:17 -07:00
Joe Julian e4e64504b8 Add cross-mode UI parity tests 2026-04-06 21:00:29 -07:00
Joe Julian 3afe9871e7 Clarify remote cache bootstrap labels 2026-04-06 20:58:01 -07:00
Joe Julian 60172d8c6d Show sync mode in saved remote summary 2026-04-06 20:55:46 -07:00
Joe Julian 84512172f3 Add remote sync removal flow 2026-04-06 20:53:14 -07:00
Joe Julian 26748d4b84 Add remote sync settings shortcut 2026-04-06 20:47:57 -07:00
Joe Julian 554d4f760d Add sync mode choice to remote setup 2026-04-06 20:41:21 -07:00
Joe Julian 94ee7fd4c0 Cover remote setup handoff 2026-04-06 20:37:01 -07:00
Joe Julian 009c4ecbf6 Clarify remote setup feedback 2026-04-06 20:35:09 -07:00
Joe Julian 400278ce0c Save remote setup bindings to disk 2026-04-06 20:32:51 -07:00
Joe Julian f6aff3e255 Persist remote binding after setup push 2026-04-06 20:30:04 -07:00
Joe Julian 53f7a702fe Tailor remote sync setup flow 2026-04-06 20:27:46 -07:00
Joe Julian 57d6a304db Unify open flow around local vaults 2026-04-06 20:24:41 -07:00
Joe Julian 99ff89de32 Use saved bindings in Advanced Sync lookup 2026-04-06 19:36:38 -07:00
Joe Julian fc59fb4869 Match Advanced Sync credentials by URL 2026-04-06 18:13:30 -07:00
Joe Julian 8866d9c06f Handle local-open remote sync fallback 2026-04-06 18:01:21 -07:00
Joe Julian b57a671819 Implement local-first remote sync policy 2026-04-06 17:41:44 -07:00
Joe Julian 03ea224773 Add legacy remote credential migration notices 2026-04-06 17:31:58 -07:00
Joe Julian 8b4f3f91be Add direct remote sync shortcut 2026-04-06 17:29:23 -07:00
Joe Julian 839cc6cc1c Label sole saved remote as sync path 2026-04-06 17:24:43 -07:00
Joe Julian 574e2a2762 Select saved remote bindings after local open 2026-04-06 17:21:17 -07:00
Joe Julian 71a0d75886 Align remote setup copy with vault bindings 2026-04-06 17:19:04 -07:00
Joe Julian 73eea0a066 Simplify single-choice saved remote sync UI 2026-04-06 17:17:40 -07:00
Joe Julian 30cab83eaa Default to sole saved remote binding 2026-04-06 17:15:21 -07:00
Joe Julian f1ac24efb9 Harden Android shared vault imports 2026-04-06 17:11:07 -07:00
Joe Julian fe9f3ae112 Use Gio JVM bridge for Android sharing 2026-04-06 17:09:28 -07:00
Joe Julian d037393a16 Share local vaults from Android 2026-04-06 17:02:30 -07:00
Joe Julian 4f5465dfff Receive shared Android vault imports 2026-04-06 16:54:14 -07:00
Joe Julian 05345559ee Add Android shared vault import flow 2026-04-06 16:48:04 -07:00
Joe Julian af44810a1c Show cached vault summary for bound remotes 2026-04-06 16:42:56 -07:00
Joe Julian 48a21c2c59 Shift remote open copy to local-first flow 2026-04-06 16:40:43 -07:00
Joe Julian fc921ca30f Materialize local cache on first remote open 2026-04-06 16:38:15 -07:00
Joe Julian db74a0a198 Bootstrap remote opens from local cache vaults 2026-04-06 16:12:53 -07:00
Joe Julian b27e2f168c Persist vault binding refs in recent remotes 2026-04-06 16:05:12 -07:00
Joe Julian adf91445c3 Save remote bindings from the sync menu 2026-04-06 16:02:19 -07:00
Joe Julian 37297856c4 Add saved remote selectors to sync menu 2026-04-06 15:58:00 -07:00
Joe Julian dc03d6a0a5 Use vault bindings for remote opens 2026-04-06 15:54:52 -07:00
Joe Julian 4da9c07b32 Expose vault-backed remote choices in UI 2026-04-06 12:20:19 -07:00
Joe Julian ef01e33990 Expose vault-backed remote profile discovery 2026-04-06 12:19:16 -07:00
Joe Julian 66d105d2a9 Persist remote bindings through app state 2026-04-06 12:13:07 -07:00
Joe Julian 677464dc9a Open remote vaults from vault bindings 2026-04-06 12:09:31 -07:00
Joe Julian 79c5176487 Stop storing remote credentials in recent state 2026-04-06 11:53:16 -07:00
Joe Julian 5e1e9f82fb Store remote sync bindings in vault data 2026-04-06 11:32:04 -07:00
Joe Julian 32c7b49f4f Add local-first remote binding model 2026-04-06 11:28:50 -07:00
6 changed files with 315 additions and 318 deletions
-3
View File
@@ -95,9 +95,6 @@ These features are product requirements, not “nice to have” ideas.
- Phone should optimize for low tap count, not purity of mobile patterns.
- The stacked phone layout is the current preferred phone direction.
- 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
+22 -22
View File
@@ -15,10 +15,10 @@ func TestRemoteBindingResolveUsesVaultProfileAndCredentialEntry(t *testing.T) {
model := vault.Model{
Entries: []vault.Entry{
{
ID: "linuscaldwell-webdav",
Title: "Bellagio WebDAV Sign-In",
Username: "linuscaldwell",
Password: "bellagio-pass-1",
ID: "tjulian-webdav",
Title: "WebDAV Sign-In",
Username: "tjulian",
Password: "token-1",
Path: []string{"Crew", "Internet"},
},
},
@@ -36,7 +36,7 @@ func TestRemoteBindingResolveUsesVaultProfileAndCredentialEntry(t *testing.T) {
binding := RemoteBinding{
LocalVaultPath: "/tmp/family.kdbx",
RemoteProfileID: "family-webdav",
CredentialEntryID: "linuscaldwell-webdav",
CredentialEntryID: "tjulian-webdav",
SyncMode: SyncModeAutomaticOnOpenSave,
}
@@ -50,11 +50,11 @@ func TestRemoteBindingResolveUsesVaultProfileAndCredentialEntry(t *testing.T) {
if got := resolved.Profile.Path; got != "files/family/keepass.kdbx" {
t.Fatalf("resolved profile path = %q, want files/family/keepass.kdbx", got)
}
if got := resolved.Credentials.Username; got != "linuscaldwell" {
t.Fatalf("resolved credentials username = %q, want linuscaldwell", got)
if got := resolved.Credentials.Username; got != "tjulian" {
t.Fatalf("resolved credentials username = %q, want tjulian", got)
}
if got := resolved.Credentials.Password; got != "bellagio-pass-1" {
t.Fatalf("resolved credentials password = %q, want bellagio-pass-1", got)
if got := resolved.Credentials.Password; got != "token-1" {
t.Fatalf("resolved credentials password = %q, want token-1", got)
}
}
@@ -63,7 +63,7 @@ func TestRemoteBindingResolveFailsWhenVaultReferenceIsMissing(t *testing.T) {
model := vault.Model{
Entries: []vault.Entry{
{ID: "linuscaldwell-webdav", Title: "Bellagio WebDAV Sign-In"},
{ID: "tjulian-webdav", Title: "WebDAV Sign-In"},
},
}
@@ -108,7 +108,7 @@ func TestRemoteBindingJSONStoresOnlyNonSecretReferences(t *testing.T) {
}
text := string(content)
for _, disallowed := range []string{"bellagio-pass-1", "password", "username", "baseUrl"} {
for _, disallowed := range []string{"token-1", "password", "username", "baseUrl"} {
if strings.Contains(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",
RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1",
CredentialTitle: "Bellagio WebDAV Sign-In",
Username: "linuscaldwell",
Password: "bellagio-pass-1",
CredentialTitle: "WebDAV Sign-In",
Username: "tjulian",
Password: "token-1",
CredentialPath: []string{"Crew", "Internet"},
SyncMode: SyncModeAutomaticOnOpenSave,
})
@@ -148,8 +148,8 @@ func TestConfigureRemoteBindingStoresProfileAndCredentialsInVault(t *testing.T)
if err != nil {
t.Fatalf("EntryByID(remote-creds-1) error = %v", err)
}
if credentials.Username != "linuscaldwell" || credentials.Password != "bellagio-pass-1" {
t.Fatalf("stored credential entry = %#v, want linuscaldwell/bellagio-pass-1", credentials)
if credentials.Username != "tjulian" || credentials.Password != "token-1" {
t.Fatalf("stored credential entry = %#v, want tjulian/token-1", credentials)
}
if credentials.URL != "https://dav.example.invalid/remote.php/dav" {
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",
RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1",
Password: "bellagio-pass-1",
Password: "token-1",
},
},
{
@@ -187,7 +187,7 @@ func TestConfigureRemoteBindingRejectsIncompleteInput(t *testing.T) {
RemoteProfileID: "family-webdav",
RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1",
Password: "bellagio-pass-1",
Password: "token-1",
},
},
{
@@ -197,7 +197,7 @@ func TestConfigureRemoteBindingRejectsIncompleteInput(t *testing.T) {
RemoteProfileID: "family-webdav",
BaseURL: "https://dav.example.invalid/remote.php/dav",
RemotePath: "files/family/keepass.kdbx",
Password: "bellagio-pass-1",
Password: "token-1",
},
},
} {
@@ -219,9 +219,9 @@ func TestRemoveRemoteBindingRemovesProfileAndCredentialsFromVault(t *testing.T)
model := vault.Model{
Entries: []vault.Entry{{
ID: "remote-creds-1",
Title: "Bellagio WebDAV Sign-In",
Username: "linuscaldwell",
Password: "bellagio-pass-1",
Title: "WebDAV Sign-In",
Username: "tjulian",
Password: "token-1",
}},
RemoteProfiles: []vault.RemoteProfile{{
ID: "family-webdav",
+40 -40
View File
@@ -22,7 +22,7 @@ func TestVisibleEntriesFollowsCurrentPathWithoutSearch(t *testing.T) {
Entries: []vault.Entry{
{ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}},
{ID: "vault-console", Title: "Vault Console", Path: []string{"Crew", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Security Office"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Home Assistant"}},
},
},
},
@@ -54,7 +54,7 @@ func TestVisibleEntriesAtParentGroupOnlyShowsDirectEntries(t *testing.T) {
{ID: "joe-note", Title: "Crew Note", Path: []string{"Crew"}},
{ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}},
{ID: "vault-console", Title: "Vault Console", Path: []string{"Crew", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Security Office"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Home Assistant"}},
},
},
},
@@ -211,7 +211,7 @@ func TestRemoteCredentialEntriesReturnsSortedVaultEntries(t *testing.T) {
Entries: []vault.Entry{
{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-3", Title: "Mint Sign-In", Username: "frankcatton", Path: []string{"Crew", "Safe House"}},
{ID: "cred-3", Title: "Beta Sign-In", Username: "buser", Path: []string{"Crew", "Home"}},
},
},
},
@@ -238,7 +238,7 @@ func TestVisibleEntriesUsesGlobalSearchWhenQueryPresent(t *testing.T) {
Entries: []vault.Entry{
{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: "surveillance-console", Title: "Surveillance Console", URL: "https://surveillance.crew.example.invalid", Path: []string{"Crew", "Security Office"}},
{ID: "surveillance-console", Title: "Surveillance Console", URL: "https://surveillance.crew.example.invalid", Path: []string{"Crew", "Home Assistant"}},
},
},
},
@@ -252,7 +252,7 @@ func TestVisibleEntriesUsesGlobalSearchWhenQueryPresent(t *testing.T) {
}
if len(got) != 1 || got[0].Title != "Surveillance Console" {
t.Fatalf("VisibleEntries() = %#v, want Security Office search match", got)
t.Fatalf("VisibleEntries() = %#v, want Home Assistant search match", got)
}
}
@@ -265,7 +265,7 @@ func TestVisibleEntriesReturnsDescendantsAfterClearingSearch(t *testing.T) {
Entries: []vault.Entry{
{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: "surveillance-console", Title: "Surveillance Console", URL: "https://surveillance.crew.example.invalid", Path: []string{"Crew", "Security Office"}},
{ID: "surveillance-console", Title: "Surveillance Console", URL: "https://surveillance.crew.example.invalid", Path: []string{"Crew", "Home Assistant"}},
},
},
},
@@ -415,7 +415,7 @@ func TestVisibleEntriesUsesGlobalSearchWithinRecycleBin(t *testing.T) {
model: vault.Model{
RecycleBin: []vault.Entry{
{ID: "deleted-1", Title: "Deleted Bellagio", Path: []string{"Root", "Internet"}},
{ID: "deleted-2", Title: "Deleted Vault Vent", URL: "https://climate.example.com", Path: []string{"Root", "Safe House"}},
{ID: "deleted-2", Title: "Deleted HVAC", URL: "https://climate.example.com", Path: []string{"Root", "Home"}},
},
},
},
@@ -484,7 +484,7 @@ func TestChildGroupsUsesCurrentModelAndCurrentPath(t *testing.T) {
model: vault.Model{
Entries: []vault.Entry{
{ID: "bellagio", Title: "Bellagio", Path: []string{"Crew", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Security Office"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Crew", "Home Assistant"}},
{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)
}
if !slices.Equal(got, []string{"Internet", "Security Office"}) {
t.Fatalf("ChildGroups() = %v, want [Internet Security Office]", got)
if !slices.Equal(got, []string{"Home Assistant", "Internet"}) {
t.Fatalf("ChildGroups() = %v, want [Home Assistant Internet]", got)
}
}
@@ -668,7 +668,7 @@ func TestUpsertEntryPersistsEntryAndSelectsIt(t *testing.T) {
ID: "vault-console",
Title: "Vault Console",
Username: "dannyocean",
Password: "bellagio-pass-1",
Password: "token-1",
URL: "https://vault.crew.example.invalid",
Path: []string{"Root", "Internet"},
}
@@ -684,7 +684,7 @@ func TestUpsertEntryPersistsEntryAndSelectsIt(t *testing.T) {
if err != nil {
t.Fatalf("VisibleEntries() error = %v", err)
}
if len(got) != 1 || got[0].Password != "bellagio-pass-1" {
if len(got) != 1 || got[0].Password != "token-1" {
t.Fatalf("VisibleEntries() = %#v, want persisted vault-console entry", got)
}
@@ -1037,9 +1037,9 @@ func TestOpenBoundRemoteVaultResolvesClientFromVaultBinding(t *testing.T) {
Entries: []vault.Entry{
{
ID: "remote-creds-1",
Title: "Bellagio WebDAV Sign-In",
Username: "linuscaldwell",
Password: "bellagio-pass-1",
Title: "WebDAV Sign-In",
Username: "tjulian",
Password: "token-1",
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" {
t.Fatalf("remote client base URL = %q, want remote.php/dav URL", got)
}
if got := sess.remoteClient.Username; got != "linuscaldwell" {
t.Fatalf("remote client username = %q, want linuscaldwell", got)
if got := sess.remoteClient.Username; got != "tjulian" {
t.Fatalf("remote client username = %q, want tjulian", got)
}
if got := sess.remoteClient.Password; got != "bellagio-pass-1" {
t.Fatalf("remote client password = %q, want bellagio-pass-1", got)
if got := sess.remoteClient.Password; got != "token-1" {
t.Fatalf("remote client password = %q, want token-1", got)
}
if got := sess.remotePath; got != "files/family/keepass.kdbx" {
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",
RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1",
CredentialTitle: "Bellagio WebDAV Sign-In",
Username: "linuscaldwell",
Password: "bellagio-pass-1",
CredentialTitle: "WebDAV Sign-In",
Username: "tjulian",
Password: "token-1",
CredentialPath: []string{"Crew", "Internet"},
SyncMode: SyncModeAutomaticOnOpenSave,
})
@@ -1146,8 +1146,8 @@ func TestConfigureRemoteBindingPersistsIntoCurrentVaultAndMarksDirty(t *testing.
if err != nil {
t.Fatalf("EntryByID(remote-creds-1) error = %v", err)
}
if credentials.Username != "linuscaldwell" || credentials.Password != "bellagio-pass-1" {
t.Fatalf("stored credential entry = %#v, want linuscaldwell/bellagio-pass-1", credentials)
if credentials.Username != "tjulian" || credentials.Password != "token-1" {
t.Fatalf("stored credential entry = %#v, want tjulian/token-1", credentials)
}
}
@@ -1161,7 +1161,7 @@ func TestConfigureRemoteBindingRequiresMutableSession(t *testing.T) {
BaseURL: "https://dav.example.invalid/remote.php/dav",
RemotePath: "files/family/keepass.kdbx",
CredentialEntryID: "remote-creds-1",
Password: "bellagio-pass-1",
Password: "token-1",
})
if err == nil {
t.Fatal("ConfigureRemoteBinding() error = nil, want mutability error")
@@ -1174,9 +1174,9 @@ func TestRemoveRemoteBindingRemovesVaultDataAndMarksDirty(t *testing.T) {
sess := &mutableStubSession{model: vault.Model{
Entries: []vault.Entry{{
ID: "remote-creds-1",
Title: "Bellagio WebDAV Sign-In",
Username: "linuscaldwell",
Password: "bellagio-pass-1",
Title: "WebDAV Sign-In",
Username: "tjulian",
Password: "token-1",
}},
RemoteProfiles: []vault.RemoteProfile{{
ID: "family-webdav",
@@ -1393,10 +1393,10 @@ func TestNavigateToPathReplacesPathAndClearsSelection(t *testing.T) {
SelectedEntryID: "vault-console",
}
state.NavigateToPath([]string{"Root", "Security Office"})
state.NavigateToPath([]string{"Root", "Home Assistant"})
if !slices.Equal(state.CurrentPath, []string{"Root", "Security Office"}) {
t.Fatalf("CurrentPath = %v, want [Root Security Office]", state.CurrentPath)
if !slices.Equal(state.CurrentPath, []string{"Root", "Home Assistant"}) {
t.Fatalf("CurrentPath = %v, want [Root Home Assistant]", state.CurrentPath)
}
if got := state.SelectedEntryID; got != "" {
t.Fatalf("SelectedEntryID = %q, want empty", got)
@@ -1429,8 +1429,8 @@ func TestDeleteCurrentGroupMovesToParentAndMarksDirty(t *testing.T) {
if err != nil {
t.Fatalf("ChildGroups() error = %v", err)
}
if !slices.Equal(got, []string{"Internet", "Security Office"}) {
t.Fatalf("ChildGroups() = %v, want [Internet Security Office]", got)
if !slices.Equal(got, []string{"Home Assistant", "Internet"}) {
t.Fatalf("ChildGroups() = %v, want [Home Assistant Internet]", got)
}
}
@@ -1452,8 +1452,8 @@ func TestCreateGroupPersistsGroupAndMarksDirty(t *testing.T) {
t.Fatalf("ChildGroups() error = %v", err)
}
if !slices.Equal(got, []string{"Finance", "Internet", "Security Office"}) {
t.Fatalf("ChildGroups() = %v, want Finance, Internet, Security Office", got)
if !slices.Equal(got, []string{"Finance", "Home Assistant", "Internet"}) {
t.Fatalf("ChildGroups() = %v, want Finance, Home Assistant, Internet", got)
}
if !state.Dirty {
t.Fatal("Dirty = false, want true after CreateGroup")
@@ -1526,8 +1526,8 @@ func TestDeleteCurrentGroupRemovesItNavigatesToParentAndMarksDirty(t *testing.T)
t.Fatalf("ChildGroups() error = %v", err)
}
if !slices.Equal(got, []string{"Internet", "Security Office"}) {
t.Fatalf("ChildGroups() = %v, want [Internet Security Office]", got)
if !slices.Equal(got, []string{"Home Assistant", "Internet"}) {
t.Fatalf("ChildGroups() = %v, want [Home Assistant Internet]", got)
}
if !state.Dirty {
t.Fatal("Dirty = false, want true after DeleteCurrentGroup")
@@ -1544,11 +1544,11 @@ func TestMoveSelectedEntryPersistsPathChangeAndMarksDirty(t *testing.T) {
SelectedEntryID: "bellagio",
}
if err := state.MoveSelectedEntry([]string{"Root", "Security Office"}); err != nil {
if err := state.MoveSelectedEntry([]string{"Root", "Home Assistant"}); err != nil {
t.Fatalf("MoveSelectedEntry() error = %v", err)
}
state.NavigateToPath([]string{"Root", "Security Office"})
state.NavigateToPath([]string{"Root", "Home Assistant"})
got, err := state.VisibleEntries()
if err != nil {
t.Fatalf("VisibleEntries() error = %v", err)
@@ -1756,7 +1756,7 @@ func testVaultModel() vault.Model {
return vault.Model{
Entries: []vault.Entry{
{ID: "bellagio", Title: "Bellagio", Path: []string{"Root", "Internet"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Root", "Security Office"}},
{ID: "surveillance-console", Title: "Surveillance Console", Path: []string{"Root", "Home Assistant"}},
},
}
}
+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
pkgdesc = KeePass-compatible password manager written in Go
pkgver = r165.1c72a50
pkgver = r160.5fa79bd
pkgrel = 1
url = https://git.julianfamily.org/joejulian/keepassgo
arch = x86_64
+41 -41
View File
@@ -23,10 +23,10 @@ func TestLoadKDBXBuildsModelFromNestedGroups(t *testing.T) {
mustGroup("Root",
mustGroup("Internet",
mustEntry("Bellagio", "rustyryan", "https://bellagio.example.invalid", "hunter2"),
mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "bellagio-pass-1"),
mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "token-1"),
),
mustGroup("Security Office",
mustEntry("Surveillance Console", "bashertarr", "https://surveillance.crew.example.invalid", "bellagio-pass-2"),
mustGroup("Home Assistant",
mustEntry("Surveillance Console", "codex", "https://surveillance.crew.example.invalid", "token-2"),
),
),
},
@@ -62,15 +62,15 @@ func TestLoadKDBXBuildsModelFromNestedGroups(t *testing.T) {
}
groups := model.ChildGroups([]string{"Root"})
if len(groups) != 2 || groups[0] != "Internet" || groups[1] != "Security Office" {
t.Fatalf("ChildGroups() = %v, want [Internet Security Office]", groups)
if len(groups) != 2 || groups[0] != "Home Assistant" || groups[1] != "Internet" {
t.Fatalf("ChildGroups() = %v, want [Home Assistant Internet]", groups)
}
}
func TestLoadKDBXPreservesEntryDetails(t *testing.T) {
t.Parallel()
entry := mustEntry("Surveillance Console", "bashertarr", "https://surveillance.crew.example.invalid", "bellagio-pass-2")
entry := mustEntry("Surveillance Console", "codex", "https://surveillance.crew.example.invalid", "token-2")
entry.Tags = "automation; home"
entry.Values = append(entry.Values,
mkValue("Notes", "Long-lived token used by Codex for home automation tasks."),
@@ -84,7 +84,7 @@ func TestLoadKDBXPreservesEntryDetails(t *testing.T) {
Meta: gokeepasslib.NewMetaData(),
Root: &gokeepasslib.RootData{
Groups: []gokeepasslib.Group{
mustGroup("Root", mustGroup("Security Office", entry)),
mustGroup("Root", mustGroup("Home Assistant", entry)),
},
},
},
@@ -104,13 +104,13 @@ func TestLoadKDBXPreservesEntryDetails(t *testing.T) {
t.Fatalf("LoadKDBX failed: %v", err)
}
got := model.EntriesInPath([]string{"Root", "Security Office"})
got := model.EntriesInPath([]string{"Root", "Home Assistant"})
if len(got) != 1 {
t.Fatalf("len(EntriesInPath()) = %d, want 1", len(got))
}
if got[0].Password != "bellagio-pass-2" {
t.Fatalf("Entry.Password = %q, want %q", got[0].Password, "bellagio-pass-2")
if got[0].Password != "token-2" {
t.Fatalf("Entry.Password = %q, want %q", got[0].Password, "token-2")
}
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",
Title: "Vault Console",
Username: "dannyocean",
Password: "bellagio-pass-1",
Password: "token-1",
URL: "https://vault.crew.example.invalid",
Notes: "Personal git server token entry used for automation and CLI auth.",
Tags: []string{"git", "infra"},
@@ -147,12 +147,12 @@ func TestSaveKDBXRoundTripsModel(t *testing.T) {
{
ID: "entry-2",
Title: "Surveillance Console",
Username: "bashertarr",
Password: "bellagio-pass-2",
Username: "codex",
Password: "token-2",
URL: "https://surveillance.crew.example.invalid",
Notes: "Long-lived token used by Codex for home automation tasks.",
Tags: []string{"automation", "home"},
Path: []string{"Root", "Security Office"},
Path: []string{"Root", "Home Assistant"},
},
},
}
@@ -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")
}
homeAssistant := loaded.EntriesInPath([]string{"Root", "Security Office"})
homeAssistant := loaded.EntriesInPath([]string{"Root", "Home Assistant"})
if len(homeAssistant) != 1 {
t.Fatalf("len(EntriesInPath(Security Office)) = %d, want 1", len(homeAssistant))
t.Fatalf("len(EntriesInPath(Home Assistant)) = %d, want 1", len(homeAssistant))
}
if homeAssistant[0].Password != "bellagio-pass-2" {
t.Fatalf("Security Office password = %q, want %q", homeAssistant[0].Password, "bellagio-pass-2")
if homeAssistant[0].Password != "token-2" {
t.Fatalf("Home Assistant password = %q, want %q", homeAssistant[0].Password, "token-2")
}
}
@@ -341,10 +341,10 @@ func TestSaveKDBXRoundTripsRecycleBinEntries(t *testing.T) {
{
ID: "entry-1",
Title: "Surveillance Console",
Username: "bashertarr",
Password: "bellagio-pass-2",
Username: "codex",
Password: "token-2",
URL: "https://surveillance.crew.example.invalid",
Path: []string{"Root", "Security Office"},
Path: []string{"Root", "Home Assistant"},
},
},
}
@@ -367,8 +367,8 @@ func TestSaveKDBXRoundTripsRecycleBinEntries(t *testing.T) {
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] != "Security Office" {
t.Fatalf("RecycleBin[0].Path = %v, want [Root Security Office]", loaded.RecycleBin[0].Path)
if len(loaded.RecycleBin[0].Path) != 2 || loaded.RecycleBin[0].Path[0] != "Root" || loaded.RecycleBin[0].Path[1] != "Home Assistant" {
t.Fatalf("RecycleBin[0].Path = %v, want [Root Home Assistant]", loaded.RecycleBin[0].Path)
}
if len(loaded.Entries) != 0 {
@@ -402,7 +402,7 @@ func TestLoadKDBXWithKeyFileCredentials(t *testing.T) {
Meta: gokeepasslib.NewMetaData(),
Root: &gokeepasslib.RootData{
Groups: []gokeepasslib.Group{
mustGroup("Root", mustGroup("Internet", mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "bellagio-pass-1"))),
mustGroup("Root", mustGroup("Internet", mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "token-1"))),
},
},
},
@@ -423,7 +423,7 @@ func TestLoadKDBXWithKeyFileCredentials(t *testing.T) {
}
got := model.Search("vault")
if len(got) != 1 || got[0].Entry.Password != "bellagio-pass-1" {
if len(got) != 1 || got[0].Entry.Password != "token-1" {
t.Fatalf("LoadKDBXWithKey() = %#v, want password-preserving vault entry", got)
}
}
@@ -457,7 +457,7 @@ func TestLoadKDBXWithCompositeCredentials(t *testing.T) {
Meta: gokeepasslib.NewMetaData(),
Root: &gokeepasslib.RootData{
Groups: []gokeepasslib.Group{
mustGroup("Root", mustGroup("Security Office", mustEntry("Surveillance Console", "bashertarr", "https://surveillance.crew.example.invalid", "bellagio-pass-2"))),
mustGroup("Root", mustGroup("Home Assistant", mustEntry("Surveillance Console", "codex", "https://surveillance.crew.example.invalid", "token-2"))),
},
},
},
@@ -480,9 +480,9 @@ func TestLoadKDBXWithCompositeCredentials(t *testing.T) {
t.Fatalf("LoadKDBXWithKey() error = %v", err)
}
got := model.EntriesInPath([]string{"Root", "Security Office"})
if len(got) != 1 || got[0].Password != "bellagio-pass-2" {
t.Fatalf("LoadKDBXWithKey() = %#v, want Security Office entry with password", got)
got := model.EntriesInPath([]string{"Root", "Home Assistant"})
if len(got) != 1 || got[0].Password != "token-2" {
t.Fatalf("LoadKDBXWithKey() = %#v, want Home Assistant entry with password", got)
}
}
@@ -496,7 +496,7 @@ func TestLoadKDBXReturnsInvalidCredentialsError(t *testing.T) {
Meta: gokeepasslib.NewMetaData(),
Root: &gokeepasslib.RootData{
Groups: []gokeepasslib.Group{
mustGroup("Root", mustGroup("Internet", mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "bellagio-pass-1"))),
mustGroup("Root", mustGroup("Internet", mustEntry("Vault Console", "dannyocean", "https://vault.crew.example.invalid", "token-1"))),
},
},
},
@@ -537,7 +537,7 @@ func TestSaveKDBXWithKeyRoundTripsModel(t *testing.T) {
ID: "vault-console",
Title: "Vault Console",
Username: "dannyocean",
Password: "bellagio-pass-1",
Password: "token-1",
URL: "https://vault.crew.example.invalid",
Path: []string{"Root", "Internet"},
},
@@ -555,7 +555,7 @@ func TestSaveKDBXWithKeyRoundTripsModel(t *testing.T) {
}
got := loaded.Search("vault")
if len(got) != 1 || got[0].Entry.Password != "bellagio-pass-1" {
if len(got) != 1 || got[0].Entry.Password != "token-1" {
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",
Title: "Surveillance Console",
Username: "bashertarr",
Password: "bellagio-pass-2",
Username: "codex",
Password: "token-2",
URL: "https://surveillance.crew.example.invalid",
Path: []string{"Root", "Security Office"},
Path: []string{"Root", "Home Assistant"},
},
},
}
@@ -602,9 +602,9 @@ func TestSaveKDBXWithCompositeKeyRoundTripsModel(t *testing.T) {
t.Fatalf("LoadKDBXWithKey() error = %v", err)
}
got := loaded.EntriesInPath([]string{"Root", "Security Office"})
if len(got) != 1 || got[0].Password != "bellagio-pass-2" {
t.Fatalf("composite key round-trip = %#v, want Security Office entry with password", got)
got := loaded.EntriesInPath([]string{"Root", "Home Assistant"})
if len(got) != 1 || got[0].Password != "token-2" {
t.Fatalf("composite key round-trip = %#v, want Home Assistant entry with password", got)
}
}
@@ -617,7 +617,7 @@ func TestKDBXRoundTripsEntryAttachments(t *testing.T) {
ID: "vault-console",
Title: "Vault Console",
Username: "dannyocean",
Password: "bellagio-pass-1",
Password: "token-1",
URL: "https://vault.crew.example.invalid",
Path: []string{"Root", "Internet"},
Attachments: map[string][]byte{
@@ -656,7 +656,7 @@ func TestKDBXReopenCyclesPreserveStableIDsAndCrossFeatureState(t *testing.T) {
ID: "entry-1",
Title: "Vault Console",
Username: "dannyocean",
Password: "bellagio-pass-2",
Password: "token-2",
URL: "https://vault.crew.example.invalid",
Notes: "Current credential",
Path: []string{"Root", "Internet"},
@@ -668,7 +668,7 @@ func TestKDBXReopenCyclesPreserveStableIDsAndCrossFeatureState(t *testing.T) {
ID: "entry-1-history-1",
Title: "Vault Console",
Username: "dannyocean",
Password: "bellagio-pass-1",
Password: "token-1",
URL: "https://vault.crew.example.invalid",
Notes: "Original credential",
Path: []string{"Root", "Internet"},