mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-19 06:02:52 +00:00
# Which Problems Are Solved Add projects to the relational tables # How the Problems Are Solved - Define table migrations - Define and implement Project and Project Role repositories. - Provide projection handlers to populate the relational tables. # Additional Changes - Statement Builder now has a constructor which allows setting of a base query with arguments. - Certain operations, like Get, Update and Delete require the Primary Key to be set as conditions. However, this requires knowledge of the implementation and table definition. This PR proposes an additional condition for repositories: `PrimaryKeyCondition`. This gives clarity on the required IDs for these operations. - Added couple of helpers to the repository package, to allow more DRY code. - getOne / getMany: generic functions for query execution and scanning. - checkRestrictingColumns, checkPkCondition: simplify condition checking, instead of using ladders of conditionals. - Added a couple of helpers to the repository test package: - Transaction, savepoint and rollback helpers. - Create instance and organization helpers for objects that depend on them (like projects). # Additional Context - after https://github.com/zitadel/zitadel/pull/10809 - closes #10765
522 lines
16 KiB
Go
522 lines
16 KiB
Go
package repository_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/brianvoe/gofakeit/v6"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
|
)
|
|
|
|
func TestGetProject(t *testing.T) {
|
|
tx, rollback := transactionForRollback(t)
|
|
defer rollback()
|
|
instanceID := createInstance(t, tx)
|
|
orgID := createOrganization(t, tx, instanceID)
|
|
projectRepo := repository.ProjectRepository()
|
|
|
|
firstProject := &domain.Project{
|
|
InstanceID: instanceID,
|
|
OrganizationID: orgID,
|
|
ID: gofakeit.UUID(),
|
|
Name: gofakeit.Name(),
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
}
|
|
err := projectRepo.Create(t.Context(), tx, firstProject)
|
|
require.NoError(t, err)
|
|
secondProject := &domain.Project{
|
|
InstanceID: instanceID,
|
|
OrganizationID: orgID,
|
|
ID: gofakeit.UUID(),
|
|
Name: gofakeit.Name(),
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
}
|
|
err = projectRepo.Create(t.Context(), tx, secondProject)
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
condition database.Condition
|
|
want *domain.Project
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "incomplete condition",
|
|
condition: projectRepo.IDCondition(firstProject.ID),
|
|
wantErr: database.NewMissingConditionError(projectRepo.IDColumn()),
|
|
},
|
|
{
|
|
name: "not found",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, "nix"),
|
|
wantErr: database.NewNoRowFoundError(nil),
|
|
},
|
|
{
|
|
name: "too many",
|
|
condition: projectRepo.InstanceIDCondition(instanceID),
|
|
wantErr: database.NewMultipleRowsFoundError(nil),
|
|
},
|
|
{
|
|
name: "ok",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, firstProject.ID),
|
|
want: firstProject,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := projectRepo.Get(t.Context(), tx, database.WithCondition(tt.condition))
|
|
require.ErrorIs(t, err, tt.wantErr)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListProjects(t *testing.T) {
|
|
tx, rollback := transactionForRollback(t)
|
|
defer rollback()
|
|
instanceID := createInstance(t, tx)
|
|
firstOrgID := createOrganization(t, tx, instanceID)
|
|
secondOrgID := createOrganization(t, tx, instanceID)
|
|
projectRepo := repository.ProjectRepository()
|
|
|
|
projects := [...]*domain.Project{
|
|
{
|
|
InstanceID: instanceID,
|
|
OrganizationID: firstOrgID,
|
|
ID: "1",
|
|
Name: "spanac",
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
},
|
|
{
|
|
InstanceID: instanceID,
|
|
OrganizationID: firstOrgID,
|
|
ID: "2",
|
|
Name: "foobar",
|
|
State: domain.ProjectStateInactive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
},
|
|
{
|
|
InstanceID: instanceID,
|
|
OrganizationID: secondOrgID,
|
|
ID: "3",
|
|
Name: "foobaz",
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
},
|
|
{
|
|
InstanceID: instanceID,
|
|
OrganizationID: secondOrgID,
|
|
ID: "4",
|
|
Name: "bazqux",
|
|
State: domain.ProjectStateInactive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
},
|
|
}
|
|
for _, project := range projects {
|
|
err := projectRepo.Create(t.Context(), tx, project)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
condition database.Condition
|
|
want []*domain.Project
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "incomplete condition",
|
|
condition: projectRepo.OrganizationIDCondition(firstOrgID),
|
|
wantErr: database.NewMissingConditionError(projectRepo.IDColumn()),
|
|
},
|
|
{
|
|
name: "no results, ok",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, "nix"),
|
|
},
|
|
{
|
|
name: "all from instance",
|
|
condition: projectRepo.InstanceIDCondition(instanceID),
|
|
want: projects[:],
|
|
},
|
|
{
|
|
name: "all from first org",
|
|
condition: database.And(
|
|
projectRepo.InstanceIDCondition(instanceID),
|
|
projectRepo.OrganizationIDCondition(firstOrgID),
|
|
),
|
|
want: projects[0:2],
|
|
},
|
|
{
|
|
name: "name starts with 'foo'",
|
|
condition: database.And(
|
|
projectRepo.InstanceIDCondition(instanceID),
|
|
projectRepo.NameCondition(database.TextOperationStartsWith, "foo"),
|
|
),
|
|
want: projects[1:3],
|
|
},
|
|
{
|
|
name: "state active",
|
|
condition: database.And(
|
|
projectRepo.InstanceIDCondition(instanceID),
|
|
projectRepo.StateCondition(domain.ProjectStateActive),
|
|
),
|
|
want: []*domain.Project{projects[0], projects[2]},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := projectRepo.List(t.Context(), tx,
|
|
database.WithCondition(tt.condition),
|
|
database.WithOrderByAscending(projectRepo.PrimaryKeyColumns()...),
|
|
)
|
|
require.ErrorIs(t, err, tt.wantErr)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateProject(t *testing.T) {
|
|
tx, rollback := transactionForRollback(t)
|
|
defer rollback()
|
|
instanceID := createInstance(t, tx)
|
|
orgID := createOrganization(t, tx, instanceID)
|
|
projectRepo := repository.ProjectRepository()
|
|
|
|
existingProject := &domain.Project{
|
|
InstanceID: instanceID,
|
|
OrganizationID: orgID,
|
|
ID: gofakeit.UUID(),
|
|
Name: gofakeit.Name(),
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
}
|
|
err := projectRepo.Create(t.Context(), tx, existingProject)
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
project *domain.Project
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "add project",
|
|
project: &domain.Project{
|
|
InstanceID: instanceID,
|
|
OrganizationID: orgID,
|
|
ID: gofakeit.UUID(),
|
|
Name: gofakeit.Name(),
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "non-existing instance",
|
|
project: &domain.Project{
|
|
InstanceID: "foo",
|
|
OrganizationID: orgID,
|
|
ID: gofakeit.UUID(),
|
|
Name: gofakeit.Name(),
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
},
|
|
wantErr: new(database.ForeignKeyError),
|
|
},
|
|
{
|
|
name: "non-existing org",
|
|
project: &domain.Project{
|
|
InstanceID: instanceID,
|
|
OrganizationID: "foo",
|
|
ID: gofakeit.UUID(),
|
|
Name: gofakeit.Name(),
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
},
|
|
wantErr: new(database.ForeignKeyError),
|
|
},
|
|
{
|
|
name: "empty id error",
|
|
project: &domain.Project{
|
|
InstanceID: instanceID,
|
|
OrganizationID: orgID,
|
|
ID: "",
|
|
Name: gofakeit.Name(),
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
},
|
|
wantErr: new(database.CheckError),
|
|
},
|
|
{
|
|
name: "empty name error",
|
|
project: &domain.Project{
|
|
InstanceID: instanceID,
|
|
OrganizationID: orgID,
|
|
ID: gofakeit.UUID(),
|
|
Name: "",
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
},
|
|
wantErr: new(database.CheckError),
|
|
},
|
|
{
|
|
name: "duplicate project",
|
|
project: existingProject,
|
|
wantErr: new(database.UniqueError),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
savepoint, rollback := savepointForRollback(t, tx)
|
|
defer rollback()
|
|
err := projectRepo.Create(t.Context(), savepoint, tt.project)
|
|
require.ErrorIs(t, err, tt.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUpdateProject(t *testing.T) {
|
|
tx, rollback := transactionForRollback(t)
|
|
defer rollback()
|
|
instanceID := createInstance(t, tx)
|
|
orgID := createOrganization(t, tx, instanceID)
|
|
projectRepo := repository.ProjectRepository()
|
|
|
|
existingProject := &domain.Project{
|
|
InstanceID: instanceID,
|
|
OrganizationID: orgID,
|
|
ID: gofakeit.UUID(),
|
|
Name: gofakeit.Name(),
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
}
|
|
err := projectRepo.Create(t.Context(), tx, existingProject)
|
|
require.NoError(t, err)
|
|
lastUpdatedAt := existingProject.UpdatedAt
|
|
|
|
tests := []struct {
|
|
name string
|
|
condition database.Condition
|
|
changes []database.Change
|
|
wantRowsAffected int64
|
|
wantErr error
|
|
assertChanges func(t *testing.T, project *domain.Project)
|
|
}{
|
|
{
|
|
name: "no changes",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
changes: []database.Change{},
|
|
wantRowsAffected: 0,
|
|
wantErr: database.ErrNoChanges,
|
|
},
|
|
{
|
|
name: "incomplete condition",
|
|
condition: projectRepo.InstanceIDCondition(instanceID),
|
|
changes: []database.Change{
|
|
projectRepo.SetName("new name"),
|
|
},
|
|
wantRowsAffected: 0,
|
|
wantErr: database.NewMissingConditionError(projectRepo.IDColumn()),
|
|
},
|
|
{
|
|
name: "set name",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
changes: []database.Change{
|
|
projectRepo.SetName("new name"),
|
|
},
|
|
wantRowsAffected: 1,
|
|
assertChanges: func(t *testing.T, updatedProject *domain.Project) {
|
|
assert.Equal(t, "new name", updatedProject.Name)
|
|
},
|
|
},
|
|
{
|
|
name: "set state",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
changes: []database.Change{
|
|
projectRepo.SetState(domain.ProjectStateInactive),
|
|
},
|
|
wantRowsAffected: 1,
|
|
assertChanges: func(t *testing.T, updatedProject *domain.Project) {
|
|
assert.Equal(t, domain.ProjectStateInactive, updatedProject.State)
|
|
},
|
|
},
|
|
{
|
|
name: "set should_assert_role",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
changes: []database.Change{
|
|
projectRepo.SetShouldAssertRole(false),
|
|
},
|
|
wantRowsAffected: 1,
|
|
assertChanges: func(t *testing.T, updatedProject *domain.Project) {
|
|
assert.False(t, updatedProject.ShouldAssertRole)
|
|
},
|
|
},
|
|
{
|
|
name: "set is_authorization_required",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
changes: []database.Change{
|
|
projectRepo.SetIsAuthorizationRequired(false),
|
|
},
|
|
wantRowsAffected: 1,
|
|
assertChanges: func(t *testing.T, updatedProject *domain.Project) {
|
|
assert.False(t, updatedProject.IsAuthorizationRequired)
|
|
},
|
|
},
|
|
{
|
|
name: "set is_project_access_required",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
changes: []database.Change{
|
|
projectRepo.SetIsProjectAccessRequired(false),
|
|
},
|
|
wantRowsAffected: 1,
|
|
assertChanges: func(t *testing.T, updatedProject *domain.Project) {
|
|
assert.False(t, updatedProject.IsProjectAccessRequired)
|
|
},
|
|
},
|
|
{
|
|
name: "set used_labeling_setting_owner",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
changes: []database.Change{
|
|
projectRepo.SetUsedLabelingSettingOwner(2),
|
|
},
|
|
wantRowsAffected: 1,
|
|
assertChanges: func(t *testing.T, updatedProject *domain.Project) {
|
|
assert.Equal(t, int16(2), updatedProject.UsedLabelingSettingOwner)
|
|
},
|
|
},
|
|
{
|
|
name: "set empty name, not allowed",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
changes: []database.Change{
|
|
projectRepo.SetName(""),
|
|
},
|
|
wantRowsAffected: 0,
|
|
wantErr: new(database.CheckError),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
savepoint, rollback := savepointForRollback(t, tx)
|
|
defer rollback()
|
|
rowsAffected, err := projectRepo.Update(t.Context(), savepoint, tt.condition, tt.changes...)
|
|
require.ErrorIs(t, err, tt.wantErr)
|
|
assert.Equal(t, tt.wantRowsAffected, rowsAffected)
|
|
|
|
if tt.assertChanges != nil {
|
|
updatedProject, err := projectRepo.Get(t.Context(), savepoint, database.WithCondition(
|
|
projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
))
|
|
require.NoError(t, err)
|
|
assert.WithinRange(t, updatedProject.CreatedAt, existingProject.CreatedAt, existingProject.CreatedAt)
|
|
assert.WithinRange(t, updatedProject.UpdatedAt, lastUpdatedAt, lastUpdatedAt.Add(time.Second))
|
|
lastUpdatedAt = updatedProject.UpdatedAt
|
|
tt.assertChanges(t, updatedProject)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeleteProject(t *testing.T) {
|
|
tx, rollback := transactionForRollback(t)
|
|
defer rollback()
|
|
instanceID := createInstance(t, tx)
|
|
orgID := createOrganization(t, tx, instanceID)
|
|
projectRepo := repository.ProjectRepository()
|
|
|
|
existingProject := &domain.Project{
|
|
InstanceID: instanceID,
|
|
OrganizationID: orgID,
|
|
ID: gofakeit.UUID(),
|
|
Name: gofakeit.Name(),
|
|
State: domain.ProjectStateActive,
|
|
ShouldAssertRole: true,
|
|
IsAuthorizationRequired: true,
|
|
IsProjectAccessRequired: true,
|
|
UsedLabelingSettingOwner: 1,
|
|
}
|
|
err := projectRepo.Create(t.Context(), tx, existingProject)
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
condition database.Condition
|
|
wantRowsAffected int64
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "incomplete condition",
|
|
condition: projectRepo.InstanceIDCondition(instanceID),
|
|
wantRowsAffected: 0,
|
|
wantErr: database.NewMissingConditionError(projectRepo.IDColumn()),
|
|
},
|
|
{
|
|
name: "not found",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, "foo"),
|
|
wantRowsAffected: 0,
|
|
},
|
|
{
|
|
name: "delete project",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
wantRowsAffected: 1,
|
|
},
|
|
{
|
|
name: "delete project twice",
|
|
condition: projectRepo.PrimaryKeyCondition(instanceID, existingProject.ID),
|
|
wantRowsAffected: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rowsAffected, err := projectRepo.Delete(t.Context(), tx, tt.condition)
|
|
require.ErrorIs(t, err, tt.wantErr)
|
|
assert.Equal(t, tt.wantRowsAffected, rowsAffected)
|
|
})
|
|
}
|
|
}
|