instance custom domain event tests done

This commit is contained in:
adlerhurst
2025-07-28 09:10:33 +02:00
parent c7718aca8f
commit ce60693c24
14 changed files with 565 additions and 278 deletions

View File

@@ -13,6 +13,13 @@ const (
DomainValidationTypeHTTP DomainValidationType = "http"
)
type DomainType string
const (
DomainTypeCustom DomainType = "custom"
DomainTypeTrusted DomainType = "trusted"
)
type domainColumns interface {
// InstanceIDColumn returns the column for the instance id field.
// `qualified` indicates if the column should be qualified with the table name.
@@ -20,15 +27,9 @@ type domainColumns interface {
// DomainColumn returns the column for the domain field.
// `qualified` indicates if the column should be qualified with the table name.
DomainColumn(qualified bool) database.Column
// IsVerifiedColumn returns the column for the is verified field.
// `qualified` indicates if the column should be qualified with the table name.
IsVerifiedColumn(qualified bool) database.Column
// IsPrimaryColumn returns the column for the is primary field.
// `qualified` indicates if the column should be qualified with the table name.
IsPrimaryColumn(qualified bool) database.Column
// ValidationTypeColumn returns the column for the verification type field.
// `qualified` indicates if the column should be qualified with the table name.
ValidationTypeColumn(qualified bool) database.Column
// CreatedAtColumn returns the column for the created at field.
// `qualified` indicates if the column should be qualified with the table name.
CreatedAtColumn(qualified bool) database.Column
@@ -44,13 +45,9 @@ type domainConditions interface {
DomainCondition(op database.TextOperation, domain string) database.Condition
// IsPrimaryCondition returns a filter on the is primary field.
IsPrimaryCondition(isPrimary bool) database.Condition
// IsVerifiedCondition returns a filter on the is verified field.
IsVerifiedCondition(isVerified bool) database.Condition
}
type domainChanges interface {
// SetVerified sets the is verified column to true.
SetVerified() database.Change
// SetPrimary sets a domain as primary based on the condition.
// All other domains will be set to non-primary.
//
@@ -62,9 +59,6 @@ type domainChanges interface {
// - The domain is already primary.
// - No domain matches the condition.
SetPrimary() database.Change
// SetValidationType sets the verification type column.
// If the domain is already verified, this is a no-op.
SetValidationType(verificationType DomainValidationType) database.Change
// SetUpdatedAt sets the updated at column.
// This is used for reducing events.
SetUpdatedAt(t time.Time) database.Change

View File

@@ -8,30 +8,28 @@ import (
)
type InstanceDomain struct {
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
Domain string `json:"domain,omitempty" db:"domain"`
IsVerified bool `json:"isVerified,omitempty" db:"is_verified"`
IsPrimary bool `json:"isPrimary,omitempty" db:"is_primary"`
ValidationType *DomainValidationType `json:"validationType,omitempty" db:"validation_type"`
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
Domain string `json:"domain,omitempty" db:"domain"`
IsPrimary bool `json:"isPrimary,omitempty" db:"is_primary"`
Type DomainType `json:"type,omitempty" db:"type"`
CreatedAt time.Time `json:"createdAt,omitzero" db:"created_at"`
UpdatedAt time.Time `json:"updatedAt,omitzero" db:"updated_at"`
}
type AddInstanceDomain struct {
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
Domain string `json:"domain,omitempty" db:"domain"`
IsVerified bool `json:"isVerified,omitempty" db:"is_verified"`
IsPrimary bool `json:"isPrimary,omitempty" db:"is_primary"`
IsGenerated bool `json:"isGenerated,omitempty" db:"is_generated"`
ValidationType *DomainValidationType `json:"validationType,omitempty" db:"validation_type"`
InstanceID string `json:"instanceId,omitempty" db:"instance_id"`
Domain string `json:"domain,omitempty" db:"domain"`
IsPrimary bool `json:"isPrimary,omitempty" db:"is_primary"`
IsGenerated bool `json:"isGenerated,omitempty" db:"is_generated"`
Type DomainType `json:"type,omitempty" db:"type"`
// CreatedAt is the time when the domain was added.
// It is set by the repository and should not be set by the caller.
CreatedAt time.Time `json:"createdAt,omitzero" db:"created_at"`
CreatedAt *time.Time `json:"createdAt,omitzero" db:"created_at"`
// UpdatedAt is the time when the domain was last updated.
// It is set by the repository and should not be set by the caller.
UpdatedAt time.Time `json:"updatedAt,omitzero" db:"updated_at"`
UpdatedAt *time.Time `json:"updatedAt,omitzero" db:"updated_at"`
}
type instanceDomainColumns interface {
@@ -39,14 +37,21 @@ type instanceDomainColumns interface {
// IsGeneratedColumn returns the column for the is generated field.
// `qualified` indicates if the column should be qualified with the table name.
IsGeneratedColumn(qualified bool) database.Column
// TypeColumn returns the column for the type field.
// `qualified` indicates if the column should be qualified with the table name.
TypeColumn(qualified bool) database.Column
}
type instanceDomainConditions interface {
domainConditions
// TypeCondition returns a filter for the type field.
TypeCondition(typ DomainType) database.Condition
}
type instanceDomainChanges interface {
domainChanges
// SetType sets the type column.
SetType(typ DomainType) database.Change
}
type InstanceDomainRepository interface {

View File

@@ -38,17 +38,31 @@ type AddOrganizationDomain struct {
type organizationDomainColumns interface {
domainColumns
// OrgIDColumn returns the column for the org id field.
// `qualified` indicates if the column should be qualified with the table name.
OrgIDColumn(qualified bool) database.Column
// IsVerifiedColumn returns the column for the is verified field.
// `qualified` indicates if the column should be qualified with the table name.
IsVerifiedColumn(qualified bool) database.Column
// ValidationTypeColumn returns the column for the verification type field.
// `qualified` indicates if the column should be qualified with the table name.
ValidationTypeColumn(qualified bool) database.Column
}
type organizationDomainConditions interface {
domainConditions
// OrgIDCondition returns a filter on the org id field.
OrgIDCondition(orgID string) database.Condition
// IsVerifiedCondition returns a filter on the is verified field.
IsVerifiedCondition(isVerified bool) database.Condition
}
type organizationDomainChanges interface {
domainChanges
// SetVerified sets the is verified column to true.
SetVerified() database.Change
// SetValidationType sets the verification type column.
// If the domain is already verified, this is a no-op.
SetValidationType(verificationType DomainValidationType) database.Change
}
type OrganizationDomainRepository interface {

View File

@@ -3,20 +3,28 @@ CREATE TYPE zitadel.domain_validation_type AS ENUM (
, 'dns'
);
CREATE TYPE zitadel.domain_type AS ENUM (
'custom'
, 'trusted'
);
CREATE TABLE zitadel.instance_domains(
instance_id TEXT NOT NULL
, domain TEXT NOT NULL CHECK (LENGTH(domain) BETWEEN 1 AND 255)
, is_verified BOOLEAN NOT NULL DEFAULT FALSE
, is_primary BOOLEAN NOT NULL DEFAULT FALSE
, is_generated BOOLEAN NOT NULL DEFAULT FALSE
, validation_type zitadel.domain_validation_type
, is_primary BOOLEAN
, is_generated BOOLEAN
, type zitadel.domain_type NOT NULL
, created_at TIMESTAMP DEFAULT NOW()
, updated_at TIMESTAMP DEFAULT NOW()
, created_at TIMESTAMPTZ DEFAULT NOW()
, updated_at TIMESTAMPTZ DEFAULT NOW()
, PRIMARY KEY (domain)
, FOREIGN KEY (instance_id) REFERENCES zitadel.instances(id) ON DELETE CASCADE
, CONSTRAINT primary_cannot_be_trusted CHECK (is_primary IS NULL OR type != 'trusted')
, CONSTRAINT generated_cannot_be_trusted CHECK (is_generated IS NULL OR type != 'trusted')
, CONSTRAINT custom_values_set CHECK (type = 'custom' AND is_primary IS NOT NULL AND is_generated IS NOT NULL)
);
CREATE INDEX idx_instance_domain_instance ON zitadel.instance_domains(instance_id);
@@ -29,8 +37,8 @@ CREATE TABLE zitadel.org_domains(
, is_primary BOOLEAN NOT NULL DEFAULT FALSE
, validation_type zitadel.domain_validation_type
, created_at TIMESTAMP DEFAULT NOW()
, updated_at TIMESTAMP DEFAULT NOW()
, created_at TIMESTAMPTZ DEFAULT NOW()
, updated_at TIMESTAMPTZ DEFAULT NOW()
, PRIMARY KEY (instance_id, org_id, domain)
@@ -91,7 +99,8 @@ BEGIN
SET is_primary = FALSE, updated_at = NOW()
WHERE instance_id = NEW.instance_id
AND domain != NEW.domain
AND is_primary = TRUE;
AND is_primary = TRUE
AND type = 'custom';
RETURN NEW;
END;

View File

@@ -4,6 +4,7 @@ package events_test
import (
"context"
"log"
"os"
"testing"
"time"
@@ -14,11 +15,12 @@ import (
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres"
"github.com/zitadel/zitadel/internal/integration"
v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
v2beta_org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/system"
)
const ConnString = "host=localhost port=5432 user=zitadel dbname=zitadel sslmode=disable"
const ConnString = "host=localhost port=5432 user=zitadel password=zitadel dbname=zitadel sslmode=disable"
var (
dbPool *pgxpool.Pool
@@ -35,12 +37,21 @@ func TestMain(m *testing.M) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
Instance = integration.NewInstance(ctx)
CTX = integration.WithSystemAuthorization(ctx)
Instance = integration.NewInstance(CTX)
CTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
SystemClient = integration.SystemClient()
OrgClient = Instance.Client.OrgV2beta
defer func() {
_, err := Instance.Client.InstanceV2Beta.DeleteInstance(CTX, &v2beta.DeleteInstanceRequest{
InstanceId: Instance.Instance.Id,
})
if err != nil {
log.Printf("Failed to delete instance on cleanup: %v\n", err)
}
}()
var err error
dbConfig, err := pgxpool.ParseConfig(ConnString)
if err != nil {

View File

@@ -0,0 +1,224 @@
//go:build integration
package events_test
import (
"log"
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/backend/v3/storage/database"
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
"github.com/zitadel/zitadel/internal/integration"
v2beta "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/system"
)
func TestServer_TestInstanceDomainReduces(t *testing.T) {
instance := integration.NewInstance(CTX)
instanceRepo := repository.InstanceRepository(pool)
instanceDomainRepo := instanceRepo.Domains(true)
t.Cleanup(func() {
_, err := instance.Client.InstanceV2Beta.DeleteInstance(CTX, &v2beta.DeleteInstanceRequest{
InstanceId: instance.Instance.Id,
})
if err != nil {
t.Logf("Failed to delete instance on cleanup: %v", err)
}
})
// Wait for instance to be created
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
_, err := instanceRepo.Get(CTX,
database.WithCondition(instanceRepo.IDCondition(instance.Instance.Id)),
)
assert.NoError(ttt, err)
}, retryDuration, tick)
t.Run("test instance domain add reduces", func(t *testing.T) {
// Add a domain to the instance
domainName := gofakeit.DomainName()
beforeAdd := time.Now()
_, err := instance.Client.InstanceV2Beta.AddCustomDomain(CTX, &v2beta.AddCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
afterAdd := time.Now()
t.Cleanup(func() {
_, err := instance.Client.InstanceV2Beta.RemoveCustomDomain(CTX, &v2beta.RemoveCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
if err != nil {
t.Logf("Failed to delete instance domain on cleanup: %v", err)
}
})
// Test that domain add reduces
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
),
),
)
require.NoError(ttt, err)
// event instance.domain.added
assert.Equal(ttt, domainName, domain.Domain)
assert.Equal(ttt, instance.Instance.Id, domain.InstanceID)
assert.False(ttt, domain.IsPrimary)
log.Printf("created at %v\n", domain.CreatedAt)
log.Printf("after %v\n", afterAdd)
log.Printf("before %v\n", beforeAdd)
assert.WithinRange(ttt, domain.CreatedAt, beforeAdd, afterAdd)
assert.WithinRange(ttt, domain.UpdatedAt, beforeAdd, afterAdd)
}, retryDuration, tick)
})
t.Run("test instance domain set primary reduces", func(t *testing.T) {
// Add a domain to the instance
domainName := gofakeit.DomainName()
_, err := instance.Client.InstanceV2Beta.AddCustomDomain(CTX, &v2beta.AddCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
t.Cleanup(func() {
// first we change the primary domain to something else
domain, err := instanceDomainRepo.Get(CTX,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.IsPrimaryCondition(false),
),
),
database.WithLimit(1),
)
require.NoError(t, err)
_, err = SystemClient.SetPrimaryDomain(CTX, &system.SetPrimaryDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domain.Domain,
})
require.NoError(t, err)
_, err = instance.Client.InstanceV2Beta.RemoveCustomDomain(CTX, &v2beta.RemoveCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
if err != nil {
t.Logf("Failed to delete instance domain on cleanup: %v", err)
}
})
// Wait for domain to be created
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName)),
),
)
require.NoError(ttt, err)
require.False(ttt, domain.IsPrimary)
assert.Equal(ttt, domainName, domain.Domain)
}, retryDuration, tick)
// Set domain as primary
beforeSetPrimary := time.Now()
_, err = SystemClient.SetPrimaryDomain(CTX, &system.SetPrimaryDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
afterSetPrimary := time.Now()
// Test that set primary reduces
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.IsPrimaryCondition(true),
),
),
)
require.NoError(ttt, err)
// event instance.domain.primary.set
assert.Equal(ttt, domainName, domain.Domain)
assert.True(ttt, domain.IsPrimary)
assert.WithinRange(ttt, domain.UpdatedAt, beforeSetPrimary, afterSetPrimary)
}, retryDuration, tick)
})
t.Run("test instance domain remove reduces", func(t *testing.T) {
// Add a domain to the instance
domainName := gofakeit.DomainName()
_, err := instance.Client.InstanceV2Beta.AddCustomDomain(CTX, &v2beta.AddCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
t.Cleanup(func() {
_, err := instance.Client.InstanceV2Beta.RemoveCustomDomain(CTX, &v2beta.RemoveCustomDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
if err != nil {
t.Logf("Failed to delete instance domain on cleanup: %v", err)
}
})
// Wait for domain to be created and verify it exists
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
_, err := instanceDomainRepo.Get(CTX,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
),
),
)
require.NoError(ttt, err)
}, retryDuration, tick)
// Remove the domain
_, err = SystemClient.RemoveDomain(CTX, &system.RemoveDomainRequest{
InstanceId: instance.Instance.Id,
Domain: domainName,
})
require.NoError(t, err)
// Test that domain remove reduces
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
domain, err := instanceDomainRepo.Get(CTX,
database.WithCondition(
database.And(
instanceDomainRepo.InstanceIDCondition(instance.Instance.Id),
instanceDomainRepo.DomainCondition(database.TextOperationEqual, domainName),
),
),
)
// event instance.domain.removed
assert.Nil(ttt, domain)
require.ErrorIs(ttt, err, new(database.NoRowFoundError))
}, retryDuration, tick)
})
}

View File

@@ -17,6 +17,8 @@ import (
)
func TestServer_TestInstanceReduces(t *testing.T) {
instanceRepo := repository.InstanceRepository(pool)
t.Run("test instance add reduces", func(t *testing.T) {
instanceName := gofakeit.Name()
beforeCreate := time.Now()
@@ -33,8 +35,15 @@ func TestServer_TestInstanceReduces(t *testing.T) {
afterCreate := time.Now()
require.NoError(t, err)
t.Cleanup(func() {
_, err = SystemClient.RemoveInstance(CTX, &system.RemoveInstanceRequest{
InstanceId: instance.GetInstanceId(),
})
if err != nil {
t.Logf("Failed to delete instance on cleanup: %v", err)
}
})
instanceRepo := repository.InstanceRepository(pool)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
instance, err := instanceRepo.Get(CTX,
@@ -71,6 +80,14 @@ func TestServer_TestInstanceReduces(t *testing.T) {
},
})
require.NoError(t, err)
t.Cleanup(func() {
_, err = SystemClient.RemoveInstance(CTX, &system.RemoveInstanceRequest{
InstanceId: res.GetInstanceId(),
})
if err != nil {
t.Logf("Failed to delete instance on cleanup: %v", err)
}
})
instanceName += "new"
beforeUpdate := time.Now()
@@ -78,10 +95,9 @@ func TestServer_TestInstanceReduces(t *testing.T) {
InstanceId: res.InstanceId,
InstanceName: instanceName,
})
require.NoError(t, err)
afterUpdate := time.Now()
require.NoError(t, err)
instanceRepo := repository.InstanceRepository(pool)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
instance, err := instanceRepo.Get(CTX,
@@ -108,8 +124,6 @@ func TestServer_TestInstanceReduces(t *testing.T) {
})
require.NoError(t, err)
instanceRepo := repository.InstanceRepository(pool)
// check instance exists
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {

View File

@@ -19,25 +19,33 @@ import (
func TestServer_TestOrganizationReduces(t *testing.T) {
instanceID := Instance.ID()
orgRepo := repository.OrganizationRepository(pool)
t.Run("test org add reduces", func(t *testing.T) {
beforeCreate := time.Now()
orgName := gofakeit.Name()
_, err := OrgClient.CreateOrganization(CTX, &v2beta_org.CreateOrganizationRequest{
org, err := OrgClient.CreateOrganization(CTX, &v2beta_org.CreateOrganizationRequest{
Name: orgName,
})
require.NoError(t, err)
afterCreate := time.Now()
orgRepo := repository.OrganizationRepository(pool)
t.Cleanup(func() {
_, err = OrgClient.DeleteOrganization(CTX, &v2beta_org.DeleteOrganizationRequest{
Id: org.GetId(),
})
if err != nil {
t.Logf("Failed to delete organization on cleanup: %v", err)
}
})
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(tt *assert.CollectT) {
organization, err := orgRepo.Get(CTX,
database.WithCondition(
database.And(
orgRepo.NameCondition(orgName),
orgRepo.IDCondition(org.GetId()),
orgRepo.InstanceIDCondition(instanceID),
),
),
@@ -63,6 +71,15 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
})
require.NoError(t, err)
t.Cleanup(func() {
_, err = OrgClient.DeleteOrganization(CTX, &v2beta_org.DeleteOrganizationRequest{
Id: organization.Id,
})
if err != nil {
t.Logf("Failed to delete organization on cleanup: %v", err)
}
})
// 2. update org name
beforeUpdate := time.Now()
orgName = orgName + "_new"
@@ -73,14 +90,12 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
require.NoError(t, err)
afterUpdate := time.Now()
orgRepo := repository.OrganizationRepository(pool)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
organization, err := orgRepo.Get(CTX,
database.WithCondition(
database.And(
orgRepo.NameCondition(orgName),
orgRepo.IDCondition(organization.Id),
orgRepo.InstanceIDCondition(instanceID),
),
),
@@ -101,6 +116,15 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
Name: orgName,
})
require.NoError(t, err)
t.Cleanup(func() {
// Cleanup: delete the organization
_, err = OrgClient.DeleteOrganization(CTX, &v2beta_org.DeleteOrganizationRequest{
Id: organization.Id,
})
if err != nil {
t.Logf("Failed to delete organization on cleanup: %v", err)
}
})
// 2. deactivate org name
beforeDeactivate := time.Now()
@@ -111,14 +135,12 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
require.NoError(t, err)
afterDeactivate := time.Now()
orgRepo := repository.OrganizationRepository(pool)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
organization, err := orgRepo.Get(CTX,
database.WithCondition(
database.And(
orgRepo.NameCondition(orgName),
orgRepo.IDCondition(organization.Id),
orgRepo.InstanceIDCondition(instanceID),
),
),
@@ -126,7 +148,6 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
require.NoError(t, err)
// event org.deactivate
assert.Equal(t, orgName, organization.Name)
assert.Equal(t, domain.OrgStateInactive, organization.State)
assert.WithinRange(t, organization.UpdatedAt, beforeDeactivate, afterDeactivate)
}, retryDuration, tick)
@@ -140,6 +161,15 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
Name: orgName,
})
require.NoError(t, err)
t.Cleanup(func() {
// Cleanup: delete the organization
_, err = OrgClient.DeleteOrganization(CTX, &v2beta_org.DeleteOrganizationRequest{
Id: organization.Id,
})
if err != nil {
t.Logf("Failed to delete organization on cleanup: %v", err)
}
})
// 2. deactivate org name
_, err = OrgClient.DeactivateOrganization(CTX, &v2beta_org.DeactivateOrganizationRequest{
@@ -154,14 +184,12 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
organization, err := orgRepo.Get(CTX,
database.WithCondition(
database.And(
orgRepo.NameCondition(orgName),
orgRepo.IDCondition(organization.Id),
orgRepo.InstanceIDCondition(instanceID),
),
),
)
require.NoError(t, err)
assert.Equal(t, orgName, organization.Name)
assert.Equal(t, domain.OrgStateInactive, organization.State)
}, retryDuration, tick)
@@ -178,7 +206,7 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
organization, err := orgRepo.Get(CTX,
database.WithCondition(
database.And(
orgRepo.NameCondition(orgName),
orgRepo.IDCondition(organization.Id),
orgRepo.InstanceIDCondition(instanceID),
),
),
@@ -201,24 +229,19 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
})
require.NoError(t, err)
// 2. check org retrivable
// 2. check org retrievable
orgRepo := repository.OrganizationRepository(pool)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
assert.EventuallyWithT(t, func(t *assert.CollectT) {
organization, err := orgRepo.Get(CTX,
_, err := orgRepo.Get(CTX,
database.WithCondition(
database.And(
orgRepo.NameCondition(orgName),
orgRepo.IDCondition(organization.Id),
orgRepo.InstanceIDCondition(instanceID),
),
),
)
require.NoError(t, err)
if organization == nil {
assert.Fail(t, "this error is here because of a race condition")
}
assert.Equal(t, orgName, organization.Name)
}, retryDuration, tick)
// 3. delete org
@@ -232,7 +255,7 @@ func TestServer_TestOrganizationReduces(t *testing.T) {
organization, err := orgRepo.Get(CTX,
database.WithCondition(
database.And(
orgRepo.NameCondition(orgName),
orgRepo.IDCondition(organization.Id),
orgRepo.InstanceIDCondition(instanceID),
),
),

View File

@@ -30,7 +30,7 @@ func InstanceRepository(client database.QueryExecutor) domain.InstanceRepository
const (
queryInstanceStmt = `SELECT instances.id, instances.name, instances.default_org_id, instances.iam_project_id, instances.console_client_id, instances.console_app_id, instances.default_language, instances.created_at, instances.updated_at` +
` , CASE WHEN count(instance_domains.domain) > 0 THEN jsonb_agg(json_build_object('domain', instance_domains.domain, 'isVerified', instance_domains.is_verified, 'isPrimary', instance_domains.is_primary, 'isGenerated', instance_domains.is_generated, 'validationType', instance_domains.validation_type, 'createdAt', instance_domains.created_at, 'updatedAt', instance_domains.updated_at)) ELSE NULL::JSONB END domains` +
` , CASE WHEN count(instance_domains.domain) > 0 THEN jsonb_agg(json_build_object('domain', instance_domains.domain, 'isPrimary', instance_domains.is_primary, 'isGenerated', instance_domains.is_generated, 'createdAt', instance_domains.created_at, 'updatedAt', instance_domains.updated_at)) ELSE NULL::JSONB END domains` +
` FROM zitadel.instances`
)

View File

@@ -19,7 +19,7 @@ type instanceDomain struct {
// repository
// -------------------------------------------------------------
const queryInstanceDomainStmt = `SELECT instance_domains.instance_id, instance_domains.domain, instance_domains.is_verified, instance_domains.is_primary, instance_domains.validation_type, instance_domains.created_at, instance_domains.updated_at ` +
const queryInstanceDomainStmt = `SELECT instance_domains.instance_id, instance_domains.domain, instance_domains.is_primary, instance_domains.created_at, instance_domains.updated_at ` +
`FROM zitadel.instance_domains`
// Get implements [domain.InstanceDomainRepository].
@@ -56,11 +56,11 @@ func (i *instanceDomain) List(ctx context.Context, opts ...database.QueryOption)
func (i *instanceDomain) Add(ctx context.Context, domain *domain.AddInstanceDomain) error {
var builder database.StatementBuilder
builder.WriteString(`INSERT INTO zitadel.instance_domains (instance_id, domain, is_verified, is_primary, validation_type) VALUES (`)
builder.WriteArgs(domain.InstanceID, domain.Domain, domain.IsVerified, domain.IsPrimary, domain.ValidationType)
builder.WriteString(`INSERT INTO zitadel.instance_domains (instance_id, domain, is_primary, is_generated, type, created_at, updated_at) VALUES (`)
builder.WriteArgs(domain.InstanceID, domain.Domain, domain.IsPrimary, domain.IsGenerated, domain.Type, domain.CreatedAt, domain.UpdatedAt)
builder.WriteString(`) RETURNING created_at, updated_at`)
return i.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&domain.CreatedAt, &domain.UpdatedAt)
return i.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(domain.CreatedAt, domain.UpdatedAt)
}
// Remove implements [domain.InstanceDomainRepository].
@@ -93,26 +93,21 @@ func (i *instanceDomain) Update(ctx context.Context, condition database.Conditio
// changes
// -------------------------------------------------------------
// SetValidationType implements [domain.InstanceDomainRepository].
func (i instanceDomain) SetValidationType(verificationType domain.DomainValidationType) database.Change {
return database.NewChange(i.ValidationTypeColumn(false), verificationType)
}
// SetPrimary implements [domain.InstanceDomainRepository].
func (i instanceDomain) SetPrimary() database.Change {
return database.NewChange(i.IsPrimaryColumn(false), true)
}
// SetVerified implements [domain.InstanceDomainRepository].
func (i instanceDomain) SetVerified() database.Change {
return database.NewChange(i.IsVerifiedColumn(false), true)
}
// SetUpdatedAt implements [domain.OrganizationDomainRepository].
func (i instanceDomain) SetUpdatedAt(updatedAt time.Time) database.Change {
return database.NewChange(i.UpdatedAtColumn(false), updatedAt)
}
// SetType implements [domain.InstanceDomainRepository].
func (i instanceDomain) SetType(typ domain.DomainType) database.Change {
return database.NewChange(i.TypeColumn(false), typ)
}
// -------------------------------------------------------------
// conditions
// -------------------------------------------------------------
@@ -132,9 +127,9 @@ func (i instanceDomain) IsPrimaryCondition(isPrimary bool) database.Condition {
return database.NewBooleanCondition(i.IsPrimaryColumn(true), isPrimary)
}
// IsVerifiedCondition implements [domain.InstanceDomainRepository].
func (i instanceDomain) IsVerifiedCondition(isVerified bool) database.Condition {
return database.NewBooleanCondition(i.IsVerifiedColumn(true), isVerified)
// TypeCondition implements [domain.InstanceDomainRepository].
func (i instanceDomain) TypeCondition(typ domain.DomainType) database.Condition {
return database.NewTextCondition(i.TypeColumn(true), database.TextOperationEqual, typ)
}
// -------------------------------------------------------------
@@ -174,14 +169,6 @@ func (instanceDomain) IsPrimaryColumn(qualified bool) database.Column {
return database.NewColumn("is_primary")
}
// IsVerifiedColumn implements [domain.InstanceDomainRepository].
func (instanceDomain) IsVerifiedColumn(qualified bool) database.Column {
if qualified {
return database.NewColumn("instance_domains.is_verified")
}
return database.NewColumn("is_verified")
}
// UpdatedAtColumn implements [domain.InstanceDomainRepository].
// Subtle: this method shadows the method ([domain.InstanceRepository]).UpdatedAtColumn of instanceDomain.instance.
func (instanceDomain) UpdatedAtColumn(qualified bool) database.Column {
@@ -191,14 +178,6 @@ func (instanceDomain) UpdatedAtColumn(qualified bool) database.Column {
return database.NewColumn("updated_at")
}
// ValidationTypeColumn implements [domain.InstanceDomainRepository].
func (instanceDomain) ValidationTypeColumn(qualified bool) database.Column {
if qualified {
return database.NewColumn("instance_domains.validation_type")
}
return database.NewColumn("validation_type")
}
// IsGeneratedColumn implements [domain.InstanceDomainRepository].
func (instanceDomain) IsGeneratedColumn(qualified bool) database.Column {
if qualified {
@@ -207,6 +186,14 @@ func (instanceDomain) IsGeneratedColumn(qualified bool) database.Column {
return database.NewColumn("is_generated")
}
// TypeColumn implements [domain.InstanceDomainRepository].
func (instanceDomain) TypeColumn(qualified bool) database.Column {
if qualified {
return database.NewColumn("instance_domains.type")
}
return database.NewColumn("type")
}
// -------------------------------------------------------------
// scanners
// -------------------------------------------------------------

View File

@@ -5,7 +5,6 @@ import (
"testing"
"github.com/brianvoe/gofakeit/v6"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -39,41 +38,25 @@ func TestAddInstanceDomain(t *testing.T) {
{
name: "happy path",
instanceDomain: domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
},
},
{
name: "add verified domain",
instanceDomain: domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsVerified: true,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsPrimary: false,
},
},
{
name: "add primary domain",
instanceDomain: domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsPrimary: true,
},
},
{
name: "add domain without domain name",
instanceDomain: domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: "",
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: "",
IsPrimary: false,
},
err: new(database.CheckError),
},
@@ -83,11 +66,9 @@ func TestAddInstanceDomain(t *testing.T) {
domainName := gofakeit.DomainName()
instanceDomain := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName,
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: domainName,
IsPrimary: false,
}
err := domainRepo.Add(ctx, instanceDomain)
@@ -95,11 +76,9 @@ func TestAddInstanceDomain(t *testing.T) {
// return same domain again
return &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName,
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
InstanceID: instanceID,
Domain: domainName,
IsPrimary: true,
}
},
err: new(database.UniqueError),
@@ -107,21 +86,17 @@ func TestAddInstanceDomain(t *testing.T) {
{
name: "add domain with non-existent instance",
instanceDomain: domain.AddInstanceDomain{
InstanceID: "non-existent-instance",
Domain: gofakeit.DomainName(),
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: "non-existent-instance",
Domain: gofakeit.DomainName(),
IsPrimary: false,
},
err: new(database.ForeignKeyError),
},
{
name: "add domain without instance id",
instanceDomain: domain.AddInstanceDomain{
Domain: gofakeit.DomainName(),
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
Domain: gofakeit.DomainName(),
IsPrimary: false,
},
err: new(database.ForeignKeyError),
},
@@ -186,18 +161,14 @@ func TestGetInstanceDomain(t *testing.T) {
domainName2 := gofakeit.DomainName()
domain1 := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName1,
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: domainName1,
IsPrimary: true,
}
domain2 := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName2,
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
InstanceID: instanceID,
Domain: domainName2,
IsPrimary: false,
}
err = domainRepo.Add(t.Context(), domain1)
@@ -217,11 +188,9 @@ func TestGetInstanceDomain(t *testing.T) {
database.WithCondition(domainRepo.IsPrimaryCondition(true)),
},
expected: &domain.InstanceDomain{
InstanceID: instanceID,
Domain: domainName1,
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: domainName1,
IsPrimary: true,
},
},
{
@@ -230,24 +199,9 @@ func TestGetInstanceDomain(t *testing.T) {
database.WithCondition(domainRepo.DomainCondition(database.TextOperationEqual, domainName2)),
},
expected: &domain.InstanceDomain{
InstanceID: instanceID,
Domain: domainName2,
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
},
},
{
name: "get verified domain",
opts: []database.QueryOption{
database.WithCondition(domainRepo.IsVerifiedCondition(true)),
},
expected: &domain.InstanceDomain{
InstanceID: instanceID,
Domain: domainName1,
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: domainName2,
IsPrimary: false,
},
},
{
@@ -272,9 +226,7 @@ func TestGetInstanceDomain(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, test.expected.InstanceID, result.InstanceID)
assert.Equal(t, test.expected.Domain, result.Domain)
assert.Equal(t, test.expected.IsVerified, result.IsVerified)
assert.Equal(t, test.expected.IsPrimary, result.IsPrimary)
assert.Equal(t, test.expected.ValidationType, result.ValidationType)
assert.NotEmpty(t, result.CreatedAt)
assert.NotEmpty(t, result.UpdatedAt)
})
@@ -307,25 +259,19 @@ func TestListInstanceDomains(t *testing.T) {
domainRepo := instanceRepo.Domains(false)
domains := []domain.AddInstanceDomain{
{
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsPrimary: true,
},
{
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsPrimary: false,
},
{
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsVerified: true,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: gofakeit.DomainName(),
IsPrimary: false,
},
}
@@ -344,13 +290,6 @@ func TestListInstanceDomains(t *testing.T) {
opts: []database.QueryOption{},
expectedCount: 3,
},
{
name: "list verified domains",
opts: []database.QueryOption{
database.WithCondition(domainRepo.IsVerifiedCondition(true)),
},
expectedCount: 2,
},
{
name: "list primary domains",
opts: []database.QueryOption{
@@ -419,11 +358,9 @@ func TestUpdateInstanceDomain(t *testing.T) {
domainRepo := instanceRepo.Domains(false)
domainName := gofakeit.DomainName()
instanceDomain := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName,
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: domainName,
IsPrimary: false,
}
err = domainRepo.Add(t.Context(), instanceDomain)
@@ -436,38 +373,16 @@ func TestUpdateInstanceDomain(t *testing.T) {
expected int64
err error
}{
{
name: "set verified",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{domainRepo.SetVerified()},
expected: 1,
},
{
name: "set primary",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{domainRepo.SetPrimary()},
expected: 1,
},
{
name: "set validation type",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{domainRepo.SetValidationType(domain.DomainValidationTypeHTTP)},
expected: 1,
},
{
name: "multiple changes",
condition: domainRepo.DomainCondition(database.TextOperationEqual, domainName),
changes: []database.Change{
domainRepo.SetVerified(),
domainRepo.SetPrimary(),
domainRepo.SetValidationType(domain.DomainValidationTypeDNS),
},
expected: 1,
},
{
name: "update non-existent domain",
condition: domainRepo.DomainCondition(database.TextOperationEqual, "non-existent.com"),
changes: []database.Change{domainRepo.SetVerified()},
changes: []database.Change{domainRepo.SetPrimary()},
expected: 0,
},
{
@@ -533,18 +448,14 @@ func TestRemoveInstanceDomain(t *testing.T) {
domainName2 := gofakeit.DomainName()
domain1 := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName1,
IsVerified: true,
IsPrimary: true,
ValidationType: gu.Ptr(domain.DomainValidationTypeDNS),
InstanceID: instanceID,
Domain: domainName1,
IsPrimary: true,
}
domain2 := &domain.AddInstanceDomain{
InstanceID: instanceID,
Domain: domainName2,
IsVerified: false,
IsPrimary: false,
ValidationType: gu.Ptr(domain.DomainValidationTypeHTTP),
InstanceID: instanceID,
Domain: domainName2,
IsPrimary: false,
}
err = domainRepo.Add(t.Context(), domain1)
@@ -628,16 +539,6 @@ func TestInstanceDomainConditions(t *testing.T) {
condition: domainRepo.IsPrimaryCondition(false),
expected: "instance_domains.is_primary = $1",
},
{
name: "is verified true",
condition: domainRepo.IsVerifiedCondition(true),
expected: "instance_domains.is_verified = $1",
},
{
name: "is verified false",
condition: domainRepo.IsVerifiedCondition(false),
expected: "instance_domains.is_verified = $1",
},
}
for _, test := range tests {
@@ -658,26 +559,11 @@ func TestInstanceDomainChanges(t *testing.T) {
change database.Change
expected string
}{
{
name: "set verified",
change: domainRepo.SetVerified(),
expected: "is_verified = $1",
},
{
name: "set primary",
change: domainRepo.SetPrimary(),
expected: "is_primary = $1",
},
{
name: "set validation type DNS",
change: domainRepo.SetValidationType(domain.DomainValidationTypeDNS),
expected: "validation_type = $1",
},
{
name: "set validation type HTTP",
change: domainRepo.SetValidationType(domain.DomainValidationTypeHTTP),
expected: "validation_type = $1",
},
}
for _, test := range tests {