zitadel/internal/api/scim/resources/user_query_builder_test.go
Lars 1915d35605
feat: list users scim v2 endpoint (#9187)
# Which Problems Are Solved
- Adds support for the list users SCIM v2 endpoint

# How the Problems Are Solved
- Adds support for the list users SCIM v2 endpoints under `GET
/scim/v2/{orgID}/Users` and `POST /scim/v2/{orgID}/Users/.search`

# Additional Changes
- adds a new function `SearchUserMetadataForUsers` to the query layer to
query a metadata keyset for given user ids
- adds a new function `NewUserMetadataExistsQuery` to the query layer to
query a given metadata key value pair exists
- adds a new function `CountUsers` to the query layer to count users
without reading any rows
- handle `ErrorAlreadyExists` as scim errors `uniqueness`
- adds `NumberLessOrEqual` and `NumberGreaterOrEqual` query comparison
methods
- adds `BytesQuery` with `BytesEquals` and `BytesNotEquals` query
comparison methods

# Additional Context
Part of #8140
Supported fields for scim filters:
* `meta.created`
* `meta.lastModified`
* `id`
* `username`
* `name.familyName`
* `name.givenName`
* `emails` and `emails.value`
* `active` only eq and ne
* `externalId` only eq and ne
2025-01-21 13:31:54 +01:00

145 lines
4.4 KiB
Go

package resources
import (
"context"
"reflect"
"testing"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/api/scim/metadata"
"github.com/zitadel/zitadel/internal/api/scim/resources/filter"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/test"
)
func Test_buildMetadataQuery(t *testing.T) {
tests := []struct {
name string
key metadata.Key
value *filter.CompValue
op *filter.CompareOp
want query.SearchQuery
wantErr bool
}{
{
name: "equals",
key: "foo",
value: &filter.CompValue{StringValue: gu.Ptr("bar")},
op: &filter.CompareOp{Equal: true},
want: test.Must(query.NewUserMetadataExistsQuery("foo", []byte("bar"), query.TextEquals, query.BytesEquals)),
wantErr: false,
},
{
name: "not equals",
key: "foo",
value: &filter.CompValue{StringValue: gu.Ptr("bar")},
op: &filter.CompareOp{NotEqual: true},
want: test.Must(query.NewUserMetadataExistsQuery("foo", []byte("bar"), query.TextEquals, query.BytesNotEquals)),
wantErr: false,
},
{
name: "unsupported operator",
key: "foo",
value: &filter.CompValue{StringValue: gu.Ptr("bar")},
op: &filter.CompareOp{StartsWith: true},
wantErr: true,
},
{
name: "unsupported comparison value",
key: "foo",
value: &filter.CompValue{Int: gu.Ptr(10)},
op: &filter.CompareOp{Equal: true},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := buildMetadataQuery(context.Background(), tt.key, tt.value, tt.op)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("buildMetadataQuery() got = %#v, want %#v", got, tt.want)
}
})
}
}
func Test_buildActiveUserStateQuery(t *testing.T) {
tests := []struct {
name string
compareValue *filter.CompValue
compOp *filter.CompareOp
want query.SearchQuery
wantErr bool
}{
{
name: "eq true",
compareValue: &filter.CompValue{BooleanTrue: true},
compOp: &filter.CompareOp{Equal: true},
want: test.Must(query.NewOrQuery(
test.Must(query.NewNumberQuery(query.UserStateCol, int32(domain.UserStateInitial), query.NumberEquals)),
test.Must(query.NewNumberQuery(query.UserStateCol, int32(domain.UserStateActive), query.NumberEquals)),
)),
},
{
name: "eq false",
compareValue: &filter.CompValue{BooleanFalse: true},
compOp: &filter.CompareOp{Equal: true},
want: test.Must(query.NewAndQuery(
test.Must(query.NewNumberQuery(query.UserStateCol, int32(domain.UserStateInitial), query.NumberNotEquals)),
test.Must(query.NewNumberQuery(query.UserStateCol, int32(domain.UserStateActive), query.NumberNotEquals)),
)),
},
{
name: "ne true",
compareValue: &filter.CompValue{BooleanTrue: true},
compOp: &filter.CompareOp{NotEqual: true},
want: test.Must(query.NewAndQuery(
test.Must(query.NewNumberQuery(query.UserStateCol, int32(domain.UserStateInitial), query.NumberNotEquals)),
test.Must(query.NewNumberQuery(query.UserStateCol, int32(domain.UserStateActive), query.NumberNotEquals)),
)),
},
{
name: "ne false",
compareValue: &filter.CompValue{BooleanTrue: true},
compOp: &filter.CompareOp{Equal: true},
want: test.Must(query.NewOrQuery(
test.Must(query.NewNumberQuery(query.UserStateCol, int32(domain.UserStateInitial), query.NumberEquals)),
test.Must(query.NewNumberQuery(query.UserStateCol, int32(domain.UserStateActive), query.NumberEquals)),
)),
},
{
name: "invalid operator",
compareValue: &filter.CompValue{BooleanTrue: true},
compOp: &filter.CompareOp{StartsWith: true},
wantErr: true,
},
{
name: "invalid comp value",
compareValue: &filter.CompValue{StringValue: gu.Ptr("foo")},
compOp: &filter.CompareOp{Equal: true},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := buildActiveUserStateQuery(context.Background(), tt.compareValue, tt.compOp)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equalf(t, tt.want, got, "buildActiveUserStateQuery(%#v, %#v)", tt.compareValue, tt.compOp)
})
}
}