zitadel/internal/command/project_application_api_test.go
Tim Möhlmann 1ebbe275b9
chore(oidc): remove legacy storage methods (#10061)
# Which Problems Are Solved

Stabilize the optimized introspection code and cleanup unused code.

# How the Problems Are Solved

- `oidc_legacy_introspection` feature flag is removed and reserved.
- `OPStorage` which are no longer needed have their bodies removed.
- The method definitions need to remain in place so the interface
remains implemented.
  - A panic is thrown in case any such method is still called

# Additional Changes

- A number of `OPStorage` methods related to token creation were already
unused. These are also cleaned up.

# Additional Context

- Closes #10027 
- #7822

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
2025-06-26 08:08:37 +00:00

769 lines
18 KiB
Go

package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/id"
id_mock "github.com/zitadel/zitadel/internal/id/mock"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestAddAPIConfig(t *testing.T) {
type fields struct {
idGenerator id.Generator
}
type args struct {
a *project.Aggregate
appID string
name string
filter preparation.FilterToQueryReducer
}
ctx := context.Background()
agg := project.NewAggregate("test", "test")
tests := []struct {
name string
fields fields
args args
want Want
}{
{
name: "invalid appID",
fields: fields{},
args: args{
a: agg,
appID: "",
name: "name",
},
want: Want{
ValidationErr: zerrors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument"),
},
},
{
name: "invalid name",
fields: fields{},
args: args{
a: agg,
appID: "appID",
name: "",
},
want: Want{
ValidationErr: zerrors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument"),
},
},
{
name: "project not exists",
fields: fields{},
args: args{
a: agg,
appID: "id",
name: "name",
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return nil, nil
}).
Filter(),
},
want: Want{
CreateErr: zerrors.ThrowNotFound(nil, "PROJE-Sf2gb", "Errors.Project.NotFound"),
},
},
{
name: "correct without client secret",
fields: fields{
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "clientID"),
},
args: args{
a: agg,
appID: "appID",
name: "name",
filter: NewMultiFilter().
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
return []eventstore.Event{
project.NewProjectAddedEvent(
ctx,
&agg.Aggregate,
"project",
false,
false,
false,
domain.PrivateLabelingSettingUnspecified,
),
}, nil
}).
Filter(),
},
want: Want{
Commands: []eventstore.Command{
project.NewApplicationAddedEvent(
ctx,
&agg.Aggregate,
"appID",
"name",
),
project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate,
"appID",
"clientID",
"",
domain.APIAuthMethodTypePrivateKeyJWT,
),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
idGenerator: tt.fields.idGenerator,
}
AssertValidation(t,
context.Background(),
c.AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *tt.args.a,
ID: tt.args.appID,
Name: tt.args.name,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
), tt.args.filter, tt.want)
})
}
}
func TestCommandSide_AddAPIApplication(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
}
type args struct {
ctx context.Context
apiApp *domain.APIApp
resourceOwner string
}
type res struct {
want *domain.APIApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "no aggregate id, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "project not existing, not found error",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsPreconditionFailed,
},
},
{
name: "invalid app, invalid argument error",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project", true, true, true,
domain.PrivateLabelingSettingUnspecified),
),
),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "",
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "create api app basic, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project", true, true, true,
domain.PrivateLabelingSettingUnspecified),
),
),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1",
"secret",
domain.APIAuthMethodTypeBasic),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypeBasic,
},
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1",
ClientSecretString: "secret",
AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive,
},
},
},
{
name: "create api app basic old ID format, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project", true, true, true,
domain.PrivateLabelingSettingUnspecified),
),
),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project1",
"secret",
domain.APIAuthMethodTypeBasic),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1@project1"),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypeBasic,
},
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project1",
ClientSecretString: "secret",
AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive,
},
},
},
{
name: "create api app jwt, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project", true, true, true,
domain.PrivateLabelingSettingUnspecified),
),
),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1",
"",
domain.APIAuthMethodTypePrivateKeyJWT),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "app1", "client1"),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
State: domain.AppStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
}
got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeAPIApplication(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
apiApp *domain.APIApp
resourceOwner string
}
type res struct {
want *domain.APIApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "",
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "missing aggregateid, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "",
},
AppID: "appid",
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
"",
domain.APIAuthMethodTypePrivateKeyJWT),
),
),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsPreconditionFailed,
},
},
{
name: "change api app, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
"secret",
domain.APIAuthMethodTypeBasic),
),
),
expectPush(
newAPIAppChangedEvent(context.Background(),
"app1",
"project1",
"org1",
domain.APIAuthMethodTypePrivateKeyJWT),
),
),
},
args: args{
ctx: context.Background(),
apiApp: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
State: domain.AppStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
}
got, err := r.ChangeAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
appID string
projectID string
resourceOwner string
}
type res struct {
want *domain.APIApp
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "no projectid, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "no appid, invalid argument error",
fields: fields{
eventstore: expectEventstore(),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "",
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "app not existing, not found error",
fields: fields{
eventstore: expectEventstore(
expectFilter(),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
err: zerrors.IsNotFound,
},
},
{
name: "change secret, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
),
),
eventFromEventPusher(
project.NewAPIConfigAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"client1@project",
"secret",
domain.APIAuthMethodTypeBasic),
),
),
expectPush(
project.NewAPIConfigSecretChangedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"secret",
),
),
),
},
args: args{
ctx: context.Background(),
projectID: "project1",
appID: "app1",
resourceOwner: "org1",
},
res: res{
want: &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
ClientID: "client1@project",
ClientSecretString: "secret",
AuthMethodType: domain.APIAuthMethodTypeBasic,
State: domain.AppStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
}
got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newAPIAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner string, authMethodType domain.APIAuthMethodType) *project.APIConfigChangedEvent {
changes := []project.APIConfigChanges{
project.ChangeAPIAuthMethodType(authMethodType),
}
event, _ := project.NewAPIConfigChangedEvent(ctx,
&project.NewAggregate(projectID, resourceOwner).Aggregate,
appID,
changes,
)
return event
}