Files
vw-api/internal/vaultwarden/client_test.go
T
2026-05-28 11:45:16 +02:00

121 lines
3.8 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,
},
// Server-side scope (plural fields).
{"scope org in set", SecretFilter{OrganizationIDs: []string{testOrgID2, testOrgID}}, true},
{"scope org not in set", SecretFilter{OrganizationIDs: []string{testOrgID2}}, false},
{"scope collection intersects", SecretFilter{CollectionIDs: []string{testCollectionID}}, true},
{
"scope collection disjoint",
SecretFilter{CollectionIDs: []string{"77777777-7777-4777-8777-777777777777"}},
false,
},
{
"scope org and collection both match",
SecretFilter{OrganizationIDs: []string{testOrgID}, CollectionIDs: []string{testCollectionID}},
true,
},
{
"scope org ok but collection disjoint",
SecretFilter{OrganizationIDs: []string{testOrgID}, CollectionIDs: []string{"77777777-7777-4777-8777-777777777777"}},
false,
},
{
"scope narrowed further by client filter",
SecretFilter{OrganizationID: testOrgID, OrganizationIDs: []string{testOrgID}},
true,
},
{
"client filter cannot widen beyond scope",
SecretFilter{OrganizationID: testOrgID, OrganizationIDs: []string{testOrgID2}},
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)
}
})
}
}
func TestMatchesSecretFilter_PersonalItemExcludedByOrgScope(t *testing.T) {
t.Parallel()
// A personal (no-org) item must be excluded once a key scopes to organizations.
personal := DecryptedItem{ID: "personal-1", Name: "personal-secret"}
if matchesSecretFilter(personal, SecretFilter{OrganizationIDs: []string{testOrgID}}) {
t.Error("personal item should be excluded by an org scope")
}
if !matchesSecretFilter(personal, SecretFilter{}) {
t.Error("personal item should match an empty (full-access) scope")
}
}