Files
vw-api/internal/vaultwarden/client_test.go
T
Jethro Young 76b82d4dba Filtering of synchronized secrets on collection, organization, and/or folder (#12)
* Filtering feature, minor logic changes
+: Add IP validation (to properly log IP even when trusted proxy is missing)
+: Added synchronization of decrypted organization, collection, and folder names (for filtering purposes)
+: Changed sync behavior to prevent collapsing multiple logins with the same name
+: Added possibility to filter logins based on organization, collection, and folder name/id in GetSecret
+: Added support for space character in secret name validation (it is also properly URL-decoded)

* Update README

* Fixes related to issues reported by CodeRabbit:
+: Changed british use of organisations in README
+: Reduced line count in parseSecretFilters by including helper function for filter dimensions resolution
+: Added comment clarifying absence of existence checking on dimension IDs as opposed to names
+: Removed efensive comma split logic on IPs (redundant due to IPValidation setting on Fiber)
+: Changed Query validator to align with name validator (mainly removing hard-coded values)
+: Added comments to decryptVaultLabel for clarity
+: Change empty SyncNameMaps instance with emptySyncNameMaps helper for consistency
+: Add lexical sort for lookupIDByName for deterministic result in case of multiple matches

* Bumped logging to warn and added entity metadata in log

* Unit tests:
+: Added unit tests for every newly introduced or changed function
+: Rewrote NewClient constructor to accept optional preloaded data (so it can be used in test scenarios)

* Changes requested by CodeRabbit
2026-05-20 23:26:29 +02:00

78 lines
2.2 KiB
Go

package vaultwarden
import (
"strings"
"testing"
)
func TestNewClient_withState(t *testing.T) {
items := map[string]DecryptedItem{
"c1": {ID: "c1", Name: "db-password", Password: "pw"},
}
nameMaps := SyncNameMaps{
Organizations: map[string]string{testOrgID: "Acme"},
Folders: map[string]string{},
Collections: map[string]string{},
}
c := NewClient(nil, 0, 0, WithState(items, nameMaps))
val, err := c.GetSecret("db-password", SecretFilter{})
if err != nil || val != "pw" {
t.Fatalf("GetSecret() = (%q, %v), want (pw, nil)", val, err)
}
if got := c.NameMaps().Organizations[testOrgID]; got != "Acme" {
t.Errorf("NameMaps org = %q, want Acme", got)
}
}
func TestMatchesSecretFilter(t *testing.T) {
t.Parallel()
base := DecryptedItem{
ID: "item-1",
OrganizationID: testOrgID,
CollectionIDs: []string{testCollectionID, "66666666-6666-4666-8666-666666666666"},
FolderID: testFolderID,
}
tests := []struct {
name string
filter SecretFilter
want bool
}{
{"empty filter", SecretFilter{}, true},
{"org match", SecretFilter{OrganizationID: testOrgID}, true},
{"org case insensitive", SecretFilter{OrganizationID: strings.ToUpper(testOrgID)}, true},
{"org mismatch", SecretFilter{OrganizationID: testOrgID2}, false},
{"collection match", SecretFilter{CollectionID: testCollectionID}, true},
{"collection case insensitive", SecretFilter{CollectionID: strings.ToUpper(testCollectionID)}, true},
{"collection missing", SecretFilter{CollectionID: "77777777-7777-4777-8777-777777777777"}, false},
{"folder match", SecretFilter{FolderID: testFolderID}, true},
{"folder mismatch", SecretFilter{FolderID: "88888888-8888-4888-8888-888888888888"}, false},
{
"all dimensions match",
SecretFilter{
OrganizationID: testOrgID,
CollectionID: testCollectionID,
FolderID: testFolderID,
},
true,
},
{
"org ok collection fail",
SecretFilter{OrganizationID: testOrgID, CollectionID: "77777777-7777-4777-8777-777777777777"},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := matchesSecretFilter(base, tt.filter); got != tt.want {
t.Errorf("matchesSecretFilter() = %v, want %v", got, tt.want)
}
})
}
}