Files
zitadel/internal/api/grpc/application/v2/integration_test/query_test.go
T
Livio SpringandGitHub 7d6a2950b4 feat(api): allow filtering apps by client id and entity id (#11385)
# Which Problems Are Solved

There's currently no way to retrieve a project based on the client_id of
an application. A developer would need to iterate through all projects
and their applications to make the correlation.

# How the Problems Are Solved

- Added a possibility to filter for `client_id` and `entity_id` on the
ListApplications endpoint.
- Added the `project_id` on the `Application` response (used on the
ListApplications endpoint).

# Additional Changes

None

# Additional Context

- closes #11340 
- requires backport to v4.x
2026-01-22 07:41:35 +00:00

1007 lines
34 KiB
Go

//go:build integration
package app_test
import (
"context"
"fmt"
"slices"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
)
func TestGetApplication(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
apiAppName := integration.ApplicationName()
createdApiApp, errAPIAppCreation := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: apiAppName,
ApplicationType: &application.CreateApplicationRequest_ApiConfiguration{
ApiConfiguration: &application.CreateAPIApplicationRequest{
AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
},
},
})
require.Nil(t, errAPIAppCreation)
samlAppName := integration.ApplicationName()
createdSAMLApp, errSAMLAppCreation := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: samlAppName,
ApplicationType: &application.CreateApplicationRequest_SamlConfiguration{
SamlConfiguration: &application.CreateSAMLApplicationRequest{
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV1{LoginV1: &application.LoginV1{}}},
Metadata: &application.CreateSAMLApplicationRequest_MetadataXml{MetadataXml: samlMetadataGen(integration.URL())},
},
},
})
require.Nil(t, errSAMLAppCreation)
oidcAppName := integration.ApplicationName()
createdOIDCApp, errOIDCAppCreation := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: oidcAppName,
ApplicationType: &application.CreateApplicationRequest_OidcConfiguration{
OidcConfiguration: &application.CreateOIDCApplicationRequest{
RedirectUris: []string{"http://example.com"},
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
ApplicationType: application.OIDCApplicationType_OIDC_APP_TYPE_WEB,
AuthMethodType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
PostLogoutRedirectUris: []string{"http://example.com/home"},
Version: application.OIDCVersion_OIDC_VERSION_1_0,
AccessTokenType: application.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
BackChannelLogoutUri: "http://example.com/logout",
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.LoginV2{BaseUri: &baseURI}}},
},
},
})
require.Nil(t, errOIDCAppCreation)
t.Parallel()
tt := []struct {
testName string
inputRequest *application.GetApplicationRequest
inputCtx context.Context
expectedErrorType codes.Code
expectedAppName string
expectedAppID string
expectedApplicationType string
}{
{
testName: "when unknown application ID should return not found error",
inputCtx: IAMOwnerCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: integration.ID(),
},
expectedErrorType: codes.NotFound,
},
{
testName: "when user has no permission should return membership not found error",
inputCtx: NoPermissionCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: createdApiApp.GetApplicationId(),
},
expectedErrorType: codes.NotFound,
},
{
testName: "when providing API application ID should return valid API application result",
inputCtx: projectOwnerCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: createdApiApp.GetApplicationId(),
},
expectedAppName: apiAppName,
expectedAppID: createdApiApp.GetApplicationId(),
expectedApplicationType: fmt.Sprintf("%T", &application.Application_ApiConfiguration{}),
},
{
testName: "when providing SAML application ID should return valid SAML application result",
inputCtx: IAMOwnerCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: createdSAMLApp.GetApplicationId(),
},
expectedAppName: samlAppName,
expectedAppID: createdSAMLApp.GetApplicationId(),
expectedApplicationType: fmt.Sprintf("%T", &application.Application_SamlConfiguration{}),
},
{
testName: "when providing OIDC application ID should return valid OIDC application result",
inputCtx: IAMOwnerCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: createdOIDCApp.GetApplicationId(),
},
expectedAppName: oidcAppName,
expectedAppID: createdOIDCApp.GetApplicationId(),
expectedApplicationType: fmt.Sprintf("%T", &application.Application_OidcConfiguration{}),
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instance.Client.ApplicationV2.GetApplication(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(t, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
assert.Equal(t, tc.expectedAppID, res.GetApplication().GetApplicationId())
assert.Equal(t, tc.expectedAppName, res.GetApplication().GetName())
assert.NotZero(t, res.GetApplication().GetCreationDate())
assert.NotZero(t, res.GetApplication().GetChangeDate())
appType := fmt.Sprintf("%T", res.GetApplication().GetConfiguration())
assert.Equal(t, tc.expectedApplicationType, appType)
}
}, retryDuration, tick)
})
}
}
func TestListApplications(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
t.Parallel()
createdApiApp, apiAppName := createAPIAppWithName(t, IAMOwnerCtx, instance, p.GetId())
createdDeactivatedApiApp, deactivatedApiAppName := createAPIAppWithName(t, IAMOwnerCtx, instance, p.GetId())
deactivateApp(t, createdDeactivatedApiApp, p.GetId())
_, createdSAMLApp, samlAppName, samlEntityID := createSAMLAppWithName(t, integration.URL(), p.GetId())
createdOIDCApp, oidcAppName := createOIDCAppWithName(t, integration.URL(), p.GetId())
type appWithName struct {
app *application.CreateApplicationResponse
name string
}
// Sorting
appsSortedByName := []appWithName{
{name: apiAppName, app: createdApiApp},
{name: deactivatedApiAppName, app: createdDeactivatedApiApp},
{name: samlAppName, app: createdSAMLApp},
{name: oidcAppName, app: createdOIDCApp},
}
slices.SortFunc(appsSortedByName, func(a, b appWithName) int {
if a.name < b.name {
return -1
}
if a.name > b.name {
return 1
}
return 0
})
appsSortedByID := []appWithName{
{name: apiAppName, app: createdApiApp},
{name: deactivatedApiAppName, app: createdDeactivatedApiApp},
{name: samlAppName, app: createdSAMLApp},
{name: oidcAppName, app: createdOIDCApp},
}
slices.SortFunc(appsSortedByID, func(a, b appWithName) int {
if a.app.GetApplicationId() < b.app.GetApplicationId() {
return -1
}
if a.app.GetApplicationId() > b.app.GetApplicationId() {
return 1
}
return 0
})
appsSortedByCreationDate := []appWithName{
{name: apiAppName, app: createdApiApp},
{name: deactivatedApiAppName, app: createdDeactivatedApiApp},
{name: samlAppName, app: createdSAMLApp},
{name: oidcAppName, app: createdOIDCApp},
}
slices.SortFunc(appsSortedByCreationDate, func(a, b appWithName) int {
aCreationDate := a.app.GetCreationDate().AsTime()
bCreationDate := b.app.GetCreationDate().AsTime()
if aCreationDate.Before(bCreationDate) {
return -1
}
if bCreationDate.Before(aCreationDate) {
return 1
}
return 0
})
tt := []struct {
testName string
inputRequest *application.ListApplicationsRequest
inputCtx context.Context
expectedOrderedList []appWithName
expectedOrderedKeys func(keys []appWithName) any
actualOrderedKeys func(keys []*application.Application) any
}{
{
testName: "when no apps found should return empty list",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: "another-id"},
},
},
},
},
expectedOrderedList: []appWithName{},
expectedOrderedKeys: func(keys []appWithName) any { return keys },
actualOrderedKeys: func(keys []*application.Application) any { return keys },
},
{
testName: "when user has no read permission should return empty set",
inputCtx: NoPermissionCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedOrderedList: []appWithName{},
expectedOrderedKeys: func(keys []appWithName) any { return keys },
actualOrderedKeys: func(keys []*application.Application) any { return keys },
},
{
testName: "when sorting by name should return apps sorted by name in descending order",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
SortingColumn: application.ApplicationSorting_APPLICATION_SORT_BY_NAME,
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedOrderedList: appsSortedByName,
expectedOrderedKeys: func(apps []appWithName) any {
names := make([]string, len(apps))
for i, a := range apps {
names[i] = a.name
}
return names
},
actualOrderedKeys: func(apps []*application.Application) any {
names := make([]string, len(apps))
for i, a := range apps {
names[i] = a.GetName()
}
return names
},
},
{
testName: "when user is project owner should return apps sorted by name in ascending order",
inputCtx: projectOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
SortingColumn: application.ApplicationSorting_APPLICATION_SORT_BY_NAME,
Pagination: &filter.PaginationRequest{Asc: true},
},
expectedOrderedList: appsSortedByName,
expectedOrderedKeys: func(apps []appWithName) any {
names := make([]string, len(apps))
for i, a := range apps {
names[i] = a.name
}
return names
},
actualOrderedKeys: func(apps []*application.Application) any {
names := make([]string, len(apps))
for i, a := range apps {
names[i] = a.GetName()
}
return names
},
},
{
testName: "when sorting by id should return apps sorted by id in descending order",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
SortingColumn: application.ApplicationSorting_APPLICATION_SORT_BY_ID,
Pagination: &filter.PaginationRequest{Asc: true},
},
expectedOrderedList: appsSortedByID,
expectedOrderedKeys: func(apps []appWithName) any {
ids := make([]string, len(apps))
for i, a := range apps {
ids[i] = a.app.GetApplicationId()
}
return ids
},
actualOrderedKeys: func(apps []*application.Application) any {
ids := make([]string, len(apps))
for i, a := range apps {
ids[i] = a.GetApplicationId()
}
return ids
},
},
{
testName: "when sorting by creation date should return apps sorted by creation date in descending order",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
SortingColumn: application.ApplicationSorting_APPLICATION_SORT_BY_CREATION_DATE,
Pagination: &filter.PaginationRequest{Asc: true},
},
expectedOrderedList: appsSortedByCreationDate,
expectedOrderedKeys: func(apps []appWithName) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.app.GetCreationDate().AsTime()
}
return creationDates
},
actualOrderedKeys: func(apps []*application.Application) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.GetCreationDate().AsTime()
}
return creationDates
},
},
{
testName: "when filtering by active apps should return active apps only",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
{
Filter: &application.ApplicationSearchFilter_StateFilter{
StateFilter: application.ApplicationState_APPLICATION_STATE_ACTIVE,
},
},
},
},
expectedOrderedList: slices.DeleteFunc(
slices.Clone(appsSortedByID),
func(a appWithName) bool { return a.name == deactivatedApiAppName },
),
expectedOrderedKeys: func(apps []appWithName) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.app.GetCreationDate().AsTime()
}
return creationDates
},
actualOrderedKeys: func(apps []*application.Application) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.GetCreationDate().AsTime()
}
return creationDates
},
},
{
testName: "when filtering by application type should return apps of matching type only",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
{Filter: &application.ApplicationSearchFilter_TypeFilter{
TypeFilter: application.ApplicationType_APPLICATION_TYPE_OIDC,
}},
},
},
expectedOrderedList: slices.DeleteFunc(
slices.Clone(appsSortedByID),
func(a appWithName) bool { return a.name != oidcAppName },
),
expectedOrderedKeys: func(apps []appWithName) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.app.GetCreationDate().AsTime()
}
return creationDates
},
actualOrderedKeys: func(apps []*application.Application) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.GetCreationDate().AsTime()
}
return creationDates
},
},
{
testName: "when filtering by clientID should return matching OIDC app only",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ClientIdFilter{
ClientIdFilter: &application.ClientIDFilter{ClientId: createdOIDCApp.GetOidcConfiguration().GetClientId()},
},
},
},
},
expectedOrderedList: []appWithName{{name: oidcAppName, app: createdOIDCApp}},
expectedOrderedKeys: func(apps []appWithName) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.app.GetCreationDate().AsTime()
}
return creationDates
},
actualOrderedKeys: func(apps []*application.Application) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.GetCreationDate().AsTime()
}
return creationDates
},
},
{
testName: "when filtering by clientID should return matching API app only",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ClientIdFilter{
ClientIdFilter: &application.ClientIDFilter{ClientId: createdApiApp.GetApiConfiguration().GetClientId()},
},
},
},
},
expectedOrderedList: []appWithName{{name: apiAppName, app: createdApiApp}},
expectedOrderedKeys: func(apps []appWithName) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.app.GetCreationDate().AsTime()
}
return creationDates
},
actualOrderedKeys: func(apps []*application.Application) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.GetCreationDate().AsTime()
}
return creationDates
},
},
{
testName: "when filtering by entityID should return matching SAML app only",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_EntityIdFilter{
EntityIdFilter: &application.EntityIDFilter{EntityId: samlEntityID},
},
},
},
},
expectedOrderedList: []appWithName{{name: samlAppName, app: createdSAMLApp}},
expectedOrderedKeys: func(apps []appWithName) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.app.GetCreationDate().AsTime()
}
return creationDates
},
actualOrderedKeys: func(apps []*application.Application) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.GetCreationDate().AsTime()
}
return creationDates
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instance.Client.ApplicationV2.ListApplications(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(ttt, codes.OK, status.Code(err))
if err == nil {
assert.Len(ttt, res.GetApplications(), len(tc.expectedOrderedList))
actualOrderedKeys := tc.actualOrderedKeys(res.GetApplications())
expectedOrderedKeys := tc.expectedOrderedKeys(tc.expectedOrderedList)
assert.ElementsMatch(ttt, expectedOrderedKeys, actualOrderedKeys)
}
}, retryDuration, tick)
})
}
}
func TestListApplications_WithPermissionV2(t *testing.T) {
ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
iamOwnerCtx := instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
p, projectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
_, otherProjectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
appName1, appName2, appName3 := integration.ApplicationName(), integration.ApplicationName(), integration.ApplicationName()
reqForAPIAppCreation := &application.CreateApplicationRequest_ApiConfiguration{
ApiConfiguration: &application.CreateAPIApplicationRequest{AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT},
}
app1, appAPIConfigChangeErr := instancePermissionV2.Client.ApplicationV2.CreateApplication(iamOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: appName1,
ApplicationType: reqForAPIAppCreation,
})
require.Nil(t, appAPIConfigChangeErr)
app2, appAPIConfigChangeErr := instancePermissionV2.Client.ApplicationV2.CreateApplication(iamOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: appName2,
ApplicationType: reqForAPIAppCreation,
})
require.Nil(t, appAPIConfigChangeErr)
app3, appAPIConfigChangeErr := instancePermissionV2.Client.ApplicationV2.CreateApplication(iamOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: appName3,
ApplicationType: reqForAPIAppCreation,
})
require.Nil(t, appAPIConfigChangeErr)
t.Parallel()
tt := []struct {
testName string
inputRequest *application.ListApplicationsRequest
inputCtx context.Context
expectedCode codes.Code
expectedAppIDs []string
}{
{
testName: "when user has no read permission should return empty set",
inputCtx: instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeNoPermission),
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedAppIDs: []string{},
},
{
testName: "when projectOwner should return full application list",
inputCtx: projectOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedCode: codes.OK,
expectedAppIDs: []string{app1.GetApplicationId(), app2.GetApplicationId(), app3.GetApplicationId()},
},
{
testName: "when orgOwner should return full application list",
inputCtx: instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedAppIDs: []string{app1.GetApplicationId(), app2.GetApplicationId(), app3.GetApplicationId()},
},
{
testName: "when iamOwner user should return full application list",
inputCtx: iamOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedAppIDs: []string{app1.GetApplicationId(), app2.GetApplicationId(), app3.GetApplicationId()},
},
{
testName: "when other projectOwner user should return empty list",
inputCtx: otherProjectOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedAppIDs: []string{},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instancePermissionV2.Client.ApplicationV2.ListApplications(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(ttt, tc.expectedCode, status.Code(err))
if err == nil {
require.Len(ttt, res.GetApplications(), len(tc.expectedAppIDs))
resAppIDs := []string{}
for _, a := range res.GetApplications() {
resAppIDs = append(resAppIDs, a.GetApplicationId())
}
assert.ElementsMatch(ttt, tc.expectedAppIDs, resAppIDs)
}
}, retryDuration, tick)
})
}
}
func TestGetApplicationKey(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
createdApiApp := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
createdAppKey := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
t.Parallel()
tt := []struct {
testName string
inputRequest *application.GetApplicationKeyRequest
inputCtx context.Context
expectedErrorType codes.Code
expectedAppKeyID string
}{
{
testName: "when unknown application ID should return not found error",
inputCtx: IAMOwnerCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: integration.ID(),
},
expectedErrorType: codes.NotFound,
},
{
testName: "when user has no permission should return membership not found error",
inputCtx: NoPermissionCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
},
expectedErrorType: codes.NotFound,
},
{
testName: "when providing API application ID should return valid API application result",
inputCtx: projectOwnerCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
},
expectedAppKeyID: createdAppKey.GetKeyId(),
},
{
testName: "when user is OrgOwner should return request key",
inputCtx: OrgOwnerCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
},
expectedAppKeyID: createdAppKey.GetKeyId(),
},
{
testName: "when user is IAMOwner should return request key",
inputCtx: OrgOwnerCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
},
expectedAppKeyID: createdAppKey.GetKeyId(),
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instance.Client.ApplicationV2.GetApplicationKey(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(t, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
assert.Equal(t, tc.expectedAppKeyID, res.GetKeyId())
assert.NotEmpty(t, res.GetCreationDate())
assert.NotEmpty(t, res.GetExpirationDate())
}
}, retryDuration, tick)
})
}
}
func TestListApplicationKeys(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
createdApiApp1 := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
createdApiApp2 := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
tomorrow := time.Now().AddDate(0, 0, 1)
in2Days := tomorrow.AddDate(0, 0, 1)
in3Days := in2Days.AddDate(0, 0, 1)
appKey1 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetApplicationId(), in2Days)
appKey2 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetApplicationId(), in3Days)
appKey3 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetApplicationId(), tomorrow)
appKey4 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp2.GetApplicationId(), tomorrow)
t.Parallel()
tt := []struct {
testName string
inputRequest *application.ListApplicationKeysRequest
deps func() (projectID, applicationID, organizationID string)
inputCtx context.Context
expectedErrorType codes.Code
expectedAppKeysIDs []string
}{
{
testName: "when sorting by expiration date should return keys sorted by expiration date ascending",
inputCtx: LoginUserCtx,
inputRequest: &application.ListApplicationKeysRequest{
Pagination: &filter.PaginationRequest{Asc: true},
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION,
Filters: []*application.ApplicationKeySearchFilter{
{Filter: &application.ApplicationKeySearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ApplicationKeyProjectIDFilter{ProjectId: p.GetId()},
}},
},
},
expectedAppKeysIDs: []string{appKey3.GetKeyId(), appKey4.GetKeyId(), appKey1.GetKeyId(), appKey2.GetKeyId()},
},
{
testName: "when sorting by creation date should return keys sorted by creation date descending",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationKeysRequest{
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE,
Filters: []*application.ApplicationKeySearchFilter{
{Filter: &application.ApplicationKeySearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ApplicationKeyProjectIDFilter{ProjectId: p.GetId()},
}},
},
},
expectedAppKeysIDs: []string{appKey4.GetKeyId(), appKey3.GetKeyId(), appKey2.GetKeyId(), appKey1.GetKeyId()},
},
{
testName: "when filtering by application ID should return keys matching application ID sorted by ID",
inputCtx: projectOwnerCtx,
inputRequest: &application.ListApplicationKeysRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationKeySearchFilter{
{Filter: &application.ApplicationKeySearchFilter_ApplicationIdFilter{
ApplicationIdFilter: &application.ApplicationKeyApplicationIDFilter{ApplicationId: createdApiApp1.GetApplicationId()},
}},
},
},
expectedAppKeysIDs: []string{appKey1.GetKeyId(), appKey2.GetKeyId(), appKey3.GetKeyId()},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instance.Client.ApplicationV2.ListApplicationKeys(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(ttt, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
require.Len(ttt, res.GetKeys(), len(tc.expectedAppKeysIDs))
for i, k := range res.GetKeys() {
assert.Equal(ttt, tc.expectedAppKeysIDs[i], k.GetKeyId())
}
}
}, retryDuration, tick)
})
}
}
func TestListApplicationKeys_WithPermissionV2(t *testing.T) {
ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
iamOwnerCtx := instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
loginUserCtx := instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeLogin)
p, projectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
createdApiApp1 := createAPIApp(t, iamOwnerCtx, instancePermissionV2, p.GetId())
createdApiApp2 := createAPIApp(t, iamOwnerCtx, instancePermissionV2, p.GetId())
tomorrow := time.Now().AddDate(0, 0, 1)
in2Days := tomorrow.AddDate(0, 0, 1)
in3Days := in2Days.AddDate(0, 0, 1)
appKey1 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetApplicationId(), in2Days)
appKey2 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetApplicationId(), in3Days)
appKey3 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetApplicationId(), tomorrow)
appKey4 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp2.GetApplicationId(), tomorrow)
t.Parallel()
tt := []struct {
testName string
inputRequest *application.ListApplicationKeysRequest
deps func() (projectID, applicationID, organizationID string)
inputCtx context.Context
expectedErrorType codes.Code
expectedAppKeysIDs []string
}{
{
testName: "when sorting by expiration date should return keys sorted by expiration date ascending",
inputCtx: loginUserCtx,
inputRequest: &application.ListApplicationKeysRequest{
Pagination: &filter.PaginationRequest{Asc: true},
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION,
},
expectedAppKeysIDs: []string{appKey3.GetKeyId(), appKey4.GetKeyId(), appKey1.GetKeyId(), appKey2.GetKeyId()},
},
{
testName: "when sorting by creation date should return keys sorted by creation date descending",
inputCtx: iamOwnerCtx,
inputRequest: &application.ListApplicationKeysRequest{
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE,
},
expectedAppKeysIDs: []string{appKey4.GetKeyId(), appKey3.GetKeyId(), appKey2.GetKeyId(), appKey1.GetKeyId()},
},
{
testName: "when filtering by application ID should return keys matching application ID sorted by ID",
inputCtx: projectOwnerCtx,
inputRequest: &application.ListApplicationKeysRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationKeySearchFilter{
{Filter: &application.ApplicationKeySearchFilter_ApplicationIdFilter{
ApplicationIdFilter: &application.ApplicationKeyApplicationIDFilter{ApplicationId: createdApiApp1.GetApplicationId()},
}},
},
},
expectedAppKeysIDs: []string{appKey1.GetKeyId(), appKey2.GetKeyId(), appKey3.GetKeyId()},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instancePermissionV2.Client.ApplicationV2.ListApplicationKeys(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(ttt, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
require.Len(ttt, res.GetKeys(), len(tc.expectedAppKeysIDs))
for i, k := range res.GetKeys() {
assert.Equal(ttt, tc.expectedAppKeysIDs[i], k.GetKeyId())
}
}
}, retryDuration, tick)
})
}
}