zitadel/internal/api/grpc/instance/v2beta/converter_test.go
Marco A. 490e4bd623
feat: instance requests implementation for resource API (#9830)
<!--
Please inform yourself about the contribution guidelines on submitting a
PR here:
https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr.
Take note of how PR/commit titles should be written and replace the
template texts in the sections below. Don't remove any of the sections.
It is important that the commit history clearly shows what is changed
and why.
Important: By submitting a contribution you agree to the terms from our
Licensing Policy as described here:
https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions.
-->

# Which Problems Are Solved

These changes introduce resource-based API endpoints for managing
instances and custom domains.

There are 4 types of changes:

- Endpoint implementation: consisting of the protobuf interface and the
implementation of the endpoint. E.g:
606439a17227b629c1d018842dc3f1c569e4627a
- (Integration) Tests: testing the implemented endpoint. E.g:
cdfe1f0372b30cb74e34f0f23c6ada776e4477e9
- Fixes: Bugs found during development that are being fixed. E.g:
acbbeedd3259b785948c1d702eb98f5810b3e60a
- Miscellaneous: code needed to put everything together or that doesn't
fit any of the above categories. E.g:
529df92abce1ffd69c0b3214bd835be404fd0de0 or
6802cb5468fbe24664ae6639fd3a40679222a2fd

# How the Problems Are Solved

_Ticked checkboxes indicate that the functionality is complete_

- [x] Instance
  - [x] Create endpoint
  - [x] Create endpoint tests
  - [x] Update endpoint
  - [x] Update endpoint tests
  - [x] Get endpoint
  - [x] Get endpoint tests
  - [x] Delete endpoint
  - [x] Delete endpoint tests
- [x] Custom Domains
  - [x] Add custom domain
  - [x] Add custom domain tests
  - [x] Remove custom domain
  - [x] Remove custom domain tests
  - [x] List custom domains
  - [x] List custom domains tests
- [x] Trusted Domains
  - [x] Add trusted domain
  - [x] Add trusted domain tests
  - [x] Remove trusted domain
  - [x] Remove trusted domain tests
  - [x] List trusted domains
  - [x] List trusted domains tests

# Additional Changes

When looking for instances (through the `ListInstances` endpoint)
matching a given query, if you ask for the results to be order by a
specific column, the query will fail due to a syntax error. This is
fixed in acbbeedd3259b785948c1d702eb98f5810b3e60a . Further explanation
can be found in the commit message

# Additional Context

- Relates to #9452 
- CreateInstance has been excluded:
https://github.com/zitadel/zitadel/issues/9930
- Permission checks / instance retrieval (middleware) needs to be
changed to allow context based permission checks
(https://github.com/zitadel/zitadel/issues/9929), required for
ListInstances

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
2025-05-21 10:50:44 +02:00

391 lines
11 KiB
Go

package instance
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/cmd/build"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
)
func Test_InstancesToPb(t *testing.T) {
instances := []*query.Instance{
{
ID: "instance1",
Name: "Instance One",
Domains: []*query.InstanceDomain{
{
Domain: "example.com",
IsPrimary: true,
IsGenerated: false,
Sequence: 1,
CreationDate: time.Unix(123, 0),
ChangeDate: time.Unix(124, 0),
InstanceID: "instance1",
},
},
Sequence: 1,
CreationDate: time.Unix(123, 0),
ChangeDate: time.Unix(124, 0),
},
}
want := []*instance.Instance{
{
Id: "instance1",
Name: "Instance One",
Domains: []*instance.Domain{
{
Domain: "example.com",
Primary: true,
Generated: false,
InstanceId: "instance1",
CreationDate: &timestamppb.Timestamp{Seconds: 123},
},
},
Version: build.Version(),
ChangeDate: &timestamppb.Timestamp{Seconds: 124},
CreationDate: &timestamppb.Timestamp{Seconds: 123},
},
}
got := InstancesToPb(instances)
assert.Equal(t, want, got)
}
func Test_ListInstancesRequestToModel(t *testing.T) {
t.Parallel()
searchInstanceByID, err := query.NewInstanceIDsListSearchQuery("instance1", "instance2")
require.Nil(t, err)
tt := []struct {
testName string
inputRequest *instance.ListInstancesRequest
maxQueryLimit uint64
expectedResult *query.InstanceSearchQueries
expectedError error
}{
{
testName: "when query limit exceeds max query limit should return invalid argument error",
maxQueryLimit: 1,
inputRequest: &instance.ListInstancesRequest{
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
Queries: []*instance.Query{{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{Ids: []string{"instance1", "instance2"}}}}},
},
expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
},
{
testName: "when valid request should return instance search query model",
inputRequest: &instance.ListInstancesRequest{
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
SortingColumn: instance.FieldName_FIELD_NAME_ID.Enum(),
Queries: []*instance.Query{{Query: &instance.Query_IdQuery{IdQuery: &instance.IdsQuery{Ids: []string{"instance1", "instance2"}}}}},
},
expectedResult: &query.InstanceSearchQueries{
SearchRequest: query.SearchRequest{
Offset: 0,
Limit: 10,
Asc: true,
SortingColumn: query.InstanceColumnID,
},
Queries: []query.SearchQuery{searchInstanceByID},
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tc.maxQueryLimit}
got, err := ListInstancesRequestToModel(tc.inputRequest, sysDefaults)
assert.Equal(t, tc.expectedError, err)
assert.Equal(t, tc.expectedResult, got)
})
}
}
func Test_fieldNameToInstanceColumn(t *testing.T) {
t.Parallel()
tests := []struct {
name string
fieldName instance.FieldName
want query.Column
}{
{
name: "ID field",
fieldName: instance.FieldName_FIELD_NAME_ID,
want: query.InstanceColumnID,
},
{
name: "Name field",
fieldName: instance.FieldName_FIELD_NAME_NAME,
want: query.InstanceColumnName,
},
{
name: "Creation Date field",
fieldName: instance.FieldName_FIELD_NAME_CREATION_DATE,
want: query.InstanceColumnCreationDate,
},
{
name: "Unknown field",
fieldName: instance.FieldName(99),
want: query.Column{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := fieldNameToInstanceColumn(tt.fieldName)
assert.Equal(t, tt.want, got)
})
}
}
func Test_instanceQueryToModel(t *testing.T) {
t.Parallel()
searchInstanceByID, err := query.NewInstanceIDsListSearchQuery("instance1")
require.Nil(t, err)
searchInstanceByDomain, err := query.NewInstanceDomainsListSearchQuery("example.com")
require.Nil(t, err)
tests := []struct {
name string
searchQuery *instance.Query
want query.SearchQuery
wantErr bool
}{
{
name: "ID Query",
searchQuery: &instance.Query{
Query: &instance.Query_IdQuery{
IdQuery: &instance.IdsQuery{
Ids: []string{"instance1"},
},
},
},
want: searchInstanceByID,
wantErr: false,
},
{
name: "Domain Query",
searchQuery: &instance.Query{
Query: &instance.Query_DomainQuery{
DomainQuery: &instance.DomainsQuery{
Domains: []string{"example.com"},
},
},
},
want: searchInstanceByDomain,
wantErr: false,
},
{
name: "Invalid Query",
searchQuery: &instance.Query{
Query: nil,
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := instanceQueryToModel(tt.searchQuery)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
}
})
}
}
func Test_ListCustomDomainsRequestToModel(t *testing.T) {
t.Parallel()
querySearchRes, err := query.NewInstanceDomainDomainSearchQuery(query.TextEquals, "example.com")
require.Nil(t, err)
queryGeneratedRes, err := query.NewInstanceDomainGeneratedSearchQuery(false)
require.Nil(t, err)
tests := []struct {
name string
inputRequest *instance.ListCustomDomainsRequest
maxQueryLimit uint64
expectedResult *query.InstanceDomainSearchQueries
expectedError error
}{
{
name: "when query limit exceeds max query limit should return invalid argument error",
inputRequest: &instance.ListCustomDomainsRequest{
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_DOMAIN,
Queries: []*instance.DomainSearchQuery{
{
Query: &instance.DomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
Domain: "example.com",
},
},
},
},
},
maxQueryLimit: 1,
expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
},
{
name: "when valid request should return domain search query model",
inputRequest: &instance.ListCustomDomainsRequest{
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_PRIMARY,
Queries: []*instance.DomainSearchQuery{
{
Query: &instance.DomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, Domain: "example.com"}},
},
{
Query: &instance.DomainSearchQuery_GeneratedQuery{
GeneratedQuery: &instance.DomainGeneratedQuery{Generated: false}},
},
},
},
maxQueryLimit: 100,
expectedResult: &query.InstanceDomainSearchQueries{
SearchRequest: query.SearchRequest{Offset: 0, Limit: 10, Asc: true, SortingColumn: query.InstanceDomainIsPrimaryCol},
Queries: []query.SearchQuery{
querySearchRes,
queryGeneratedRes,
},
},
expectedError: nil,
},
{
name: "when invalid query should return error",
inputRequest: &instance.ListCustomDomainsRequest{
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
SortingColumn: instance.DomainFieldName_DOMAIN_FIELD_NAME_GENERATED,
Queries: []*instance.DomainSearchQuery{
{
Query: nil,
},
},
},
maxQueryLimit: 100,
expectedResult: nil,
expectedError: zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tt.maxQueryLimit}
got, err := ListCustomDomainsRequestToModel(tt.inputRequest, sysDefaults)
assert.Equal(t, tt.expectedError, err)
assert.Equal(t, tt.expectedResult, got)
})
}
}
func Test_ListTrustedDomainsRequestToModel(t *testing.T) {
t.Parallel()
querySearchRes, err := query.NewInstanceTrustedDomainDomainSearchQuery(query.TextEquals, "example.com")
require.Nil(t, err)
tests := []struct {
name string
inputRequest *instance.ListTrustedDomainsRequest
maxQueryLimit uint64
expectedResult *query.InstanceTrustedDomainSearchQueries
expectedError error
}{
{
name: "when query limit exceeds max query limit should return invalid argument error",
inputRequest: &instance.ListTrustedDomainsRequest{
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_DOMAIN,
Queries: []*instance.TrustedDomainSearchQuery{
{
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{
Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS,
Domain: "example.com",
},
},
},
},
},
maxQueryLimit: 1,
expectedError: zerrors.ThrowInvalidArgumentf(errors.New("given: 10, allowed: 1"), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
},
{
name: "when valid request should return domain search query model",
inputRequest: &instance.ListTrustedDomainsRequest{
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
SortingColumn: instance.TrustedDomainFieldName_TRUSTED_DOMAIN_FIELD_NAME_CREATION_DATE,
Queries: []*instance.TrustedDomainSearchQuery{
{
Query: &instance.TrustedDomainSearchQuery_DomainQuery{
DomainQuery: &instance.DomainQuery{Method: object.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS, Domain: "example.com"}},
},
},
},
maxQueryLimit: 100,
expectedResult: &query.InstanceTrustedDomainSearchQueries{
SearchRequest: query.SearchRequest{Offset: 0, Limit: 10, Asc: true, SortingColumn: query.InstanceTrustedDomainCreationDateCol},
Queries: []query.SearchQuery{querySearchRes},
},
expectedError: nil,
},
{
name: "when invalid query should return error",
inputRequest: &instance.ListTrustedDomainsRequest{
Pagination: &filter.PaginationRequest{Limit: 10, Offset: 0, Asc: true},
Queries: []*instance.TrustedDomainSearchQuery{
{
Query: nil,
},
},
},
maxQueryLimit: 100,
expectedResult: nil,
expectedError: zerrors.ThrowInvalidArgument(nil, "INST-Ags42", "List.Query.Invalid"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
sysDefaults := systemdefaults.SystemDefaults{MaxQueryLimit: tt.maxQueryLimit}
got, err := ListTrustedDomainsRequestToModel(tt.inputRequest, sysDefaults)
assert.Equal(t, tt.expectedError, err)
assert.Equal(t, tt.expectedResult, got)
})
}
}