feat: org command sides (#96)

* start org

* refactor(eventstore): filter in sql for querier

* feat(eventstore): Aggregate precondition

preconditions are checked right before insert. Insert is still transaction save

* feat(eventstore): check preconditions in repository

* test(eventstore): test precondition in models

* test(eventstore): precondition-tests

* start org

* refactor(eventstore): filter in sql for querier

* feat(eventstore): Aggregate precondition

preconditions are checked right before insert. Insert is still transaction save

* feat(admin): start implement org

* feat(eventstore): check preconditions in repository

* fix(eventstore): data as NULL if empty
refactor(eventstore): naming in sequence methods

* feat(admin): org command side

* feat(management): start org-repo

* feat(org): member

* fix: replace ObjectRoot.ID with ObjectRoot.AggregateID

* aggregateID

* add remove,change member

* refactor(org): namings

* refactor(eventstore): querier as type

* fix(precondition): rename validation from precondition to validation

* test(eventstore): isErr func instead of wantErr bool

* fix(tests): Data

* fix(eventstore): correct check for existing events in push,
simplify insert statement

* fix(eventstore): aggregate id public

* test(org): eventsourcing

* test(org): eventstore

* test(org): deactivate, reactivate, orgbyid

* test(org): getMemberByIDs

* tests

* running tests

* add user repo to admin

* thorw not found if no org found

* eventstore tests done

* lauft

* validate if user is already member of org

* modules

* delete unused file

* add member validation test

* return error if unable to validat member

* generate org id once,
set resourceowner of org

* Update internal/admin/repository/eventsourcing/eventstore/org.go

* Update internal/admin/repository/eventsourcing/eventstore/org.go

* Update internal/org/repository/eventsourcing/member_model.go

* Update internal/org/repository/eventsourcing/org.go

* Update internal/org/repository/eventsourcing/org.go

* Update internal/org/repository/eventsourcing/org_member.go

* Update internal/org/repository/eventsourcing/org_member.go

* Update internal/org/repository/eventsourcing/org_model.go

* Update internal/org/repository/eventsourcing/org.go

* Update internal/org/repository/eventsourcing/org_model.go

* Update internal/org/repository/eventsourcing/org_model.go

* typo

* correct user events

* usercreate for setuporg instead of userregister

* set data

* mod

* mod

* tests

* cleanup code

* code styling

* return member on add and change

* change username in startup

* girignore

* orgID as parameter in re-/deactive org

* startup config

* migration for admin_api-user

* probes fro admin

* move unique org

Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
This commit is contained in:
Silvan
2020-05-13 14:22:29 +02:00
committed by GitHub
parent 7facd78026
commit 9e32740eb8
67 changed files with 16694 additions and 13035 deletions

View File

@@ -0,0 +1,21 @@
package model
import es_models "github.com/caos/zitadel/internal/eventstore/models"
type OrgMember struct {
es_models.ObjectRoot
UserID string
Roles []string
}
func NewOrgMember(orgID, userID string) *OrgMember {
return &OrgMember{ObjectRoot: es_models.ObjectRoot{AggregateID: orgID}, UserID: userID}
}
func NewOrgMemberWithRoles(orgID, userID string, roles ...string) *OrgMember {
return &OrgMember{ObjectRoot: es_models.ObjectRoot{AggregateID: orgID}, UserID: userID, Roles: roles}
}
func (member *OrgMember) IsValid() bool {
return member.AggregateID != "" && member.UserID != ""
}

43
internal/org/model/org.go Normal file
View File

@@ -0,0 +1,43 @@
package model
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
)
type Org struct {
es_models.ObjectRoot
State OrgState
Name string
Domain string
Members []*OrgMember
}
type OrgState int32
const (
ORGSTATE_ACTIVE OrgState = iota
ORGSTATE_INACTIVE
)
func NewOrg(id string) *Org {
return &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: id}, State: ORGSTATE_ACTIVE}
}
func (o *Org) IsActive() bool {
return o.State == ORGSTATE_ACTIVE
}
func (o *Org) IsValid() bool {
return o.Name != "" && o.Domain != ""
}
func (o *Org) ContainsMember(userID string) bool {
for _, member := range o.Members {
if member.UserID == userID {
return true
}
}
return false
}

View File

@@ -0,0 +1,24 @@
package model
import "github.com/caos/zitadel/internal/eventstore/models"
const (
OrgAggregate models.AggregateType = "org"
OrgDomainAggregate models.AggregateType = "org.domain"
OrgNameAggregate models.AggregateType = "org.name"
OrgAdded models.EventType = "org.added"
OrgChanged models.EventType = "org.changed"
OrgDeactivated models.EventType = "org.deactivated"
OrgReactivated models.EventType = "org.reactivated"
OrgNameReserved models.EventType = "org.name.reserved"
OrgNameReleased models.EventType = "org.name.released"
OrgDomainReserved models.EventType = "org.domain.reserved"
OrgDomainReleased models.EventType = "org.domain.released"
OrgMemberAdded models.EventType = "org.member.added"
OrgMemberChanged models.EventType = "org.member.changed"
OrgMemberRemoved models.EventType = "org.member.removed"
)

View File

@@ -0,0 +1,215 @@
package eventsourcing
import (
"context"
"strconv"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
es_models "github.com/caos/zitadel/internal/eventstore/models"
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
org_model "github.com/caos/zitadel/internal/org/model"
)
type OrgEventstore struct {
eventstore.Eventstore
}
type OrgConfig struct {
eventstore.Eventstore
}
func StartOrg(conf OrgConfig) *OrgEventstore {
return &OrgEventstore{Eventstore: conf.Eventstore}
}
func (es *OrgEventstore) PrepareCreateOrg(ctx context.Context, orgModel *org_model.Org) (*Org, []*es_models.Aggregate, error) {
if orgModel == nil || !orgModel.IsValid() {
return nil, nil, errors.ThrowInvalidArgument(nil, "EVENT-OeLSk", "org not valid")
}
id, err := idGenerator.NextID()
if err != nil {
return nil, nil, errors.ThrowInternal(err, "EVENT-OwciI", "id gen failed")
}
orgModel.AggregateID = strconv.FormatUint(id, 10)
org := OrgFromModel(orgModel)
aggregates, err := orgCreatedAggregates(ctx, es.AggregateCreator(), org)
return org, aggregates, err
}
func (es *OrgEventstore) CreateOrg(ctx context.Context, orgModel *org_model.Org) (*org_model.Org, error) {
org, aggregates, err := es.PrepareCreateOrg(ctx, orgModel)
err = es_sdk.PushAggregates(ctx, es.PushAggregates, org.AppendEvents, aggregates...)
if err != nil {
return nil, err
}
return OrgToModel(org), nil
}
func (es *OrgEventstore) OrgByID(ctx context.Context, org *org_model.Org) (*org_model.Org, error) {
if org == nil {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-gQTYP", "org not set")
}
query, err := OrgByIDQuery(org.AggregateID, org.Sequence)
if err != nil {
return nil, err
}
esOrg := OrgFromModel(org)
err = es_sdk.Filter(ctx, es.FilterEvents, esOrg.AppendEvents, query)
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
if esOrg.Sequence == 0 {
return nil, errors.ThrowNotFound(nil, "EVENT-kVLb2", "org not found")
}
return OrgToModel(esOrg), nil
}
func (es *OrgEventstore) IsOrgUnique(ctx context.Context, name, domain string) (isUnique bool, err error) {
var found bool
err = es_sdk.Filter(ctx, es.FilterEvents, isUniqueValidation(&found), OrgNameUniqueQuery(name))
if (err != nil && !errors.IsNotFound(err)) || found {
return false, err
}
err = es_sdk.Filter(ctx, es.FilterEvents, isUniqueValidation(&found), OrgDomainUniqueQuery(domain))
if err != nil && !errors.IsNotFound(err) {
return false, err
}
return !found, nil
}
func isUniqueValidation(unique *bool) func(events ...*es_models.Event) error {
return func(events ...*es_models.Event) error {
if len(events) == 0 {
return nil
}
*unique = *unique || events[0].Type == org_model.OrgDomainReserved || events[0].Type == org_model.OrgNameReserved
return nil
}
}
func (es *OrgEventstore) DeactivateOrg(ctx context.Context, orgID string) (*org_model.Org, error) {
existingOrg, err := es.OrgByID(ctx, org_model.NewOrg(orgID))
if err != nil {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-oL9nT", "org not found")
}
org := OrgFromModel(existingOrg)
aggregate := orgDeactivateAggregate(es.AggregateCreator(), org)
err = es_sdk.Push(ctx, es.PushAggregates, org.AppendEvents, aggregate)
if err != nil {
return nil, err
}
return OrgToModel(org), nil
}
func (es *OrgEventstore) ReactivateOrg(ctx context.Context, orgID string) (*org_model.Org, error) {
existingOrg, err := es.OrgByID(ctx, org_model.NewOrg(orgID))
if err != nil {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-oL9nT", "org not set")
}
org := OrgFromModel(existingOrg)
aggregate := orgReactivateAggregate(es.AggregateCreator(), org)
err = es_sdk.Push(ctx, es.PushAggregates, org.AppendEvents, aggregate)
if err != nil {
return nil, err
}
return OrgToModel(org), nil
}
func (es *OrgEventstore) OrgMemberByIDs(ctx context.Context, member *org_model.OrgMember) (*org_model.OrgMember, error) {
if member == nil || member.UserID == "" || member.AggregateID == "" {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ld93d", "member not set")
}
org, err := es.OrgByID(ctx, &org_model.Org{ObjectRoot: member.ObjectRoot, Members: []*org_model.OrgMember{member}})
if err != nil {
return nil, err
}
for _, currentMember := range org.Members {
if currentMember.UserID == member.UserID {
return currentMember, nil
}
}
return nil, errors.ThrowNotFound(nil, "EVENT-SXji6", "member not found")
}
func (es *OrgEventstore) PrepareAddOrgMember(ctx context.Context, member *org_model.OrgMember) (*OrgMember, *es_models.Aggregate, error) {
if member == nil || !member.IsValid() {
return nil, nil, errors.ThrowPreconditionFailed(nil, "EVENT-9dk45", "UserID and Roles are required")
}
repoMember := OrgMemberFromModel(member)
addAggregate, err := orgMemberAddedAggregate(ctx, es.Eventstore.AggregateCreator(), repoMember)
return repoMember, addAggregate, err
}
func (es *OrgEventstore) AddOrgMember(ctx context.Context, member *org_model.OrgMember) (*org_model.OrgMember, error) {
repoMember, addAggregate, err := es.PrepareAddOrgMember(ctx, member)
if err != nil {
return nil, err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoMember.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
return OrgMemberToModel(repoMember), nil
}
func (es *OrgEventstore) ChangeOrgMember(ctx context.Context, member *org_model.OrgMember) (*org_model.OrgMember, error) {
if member == nil || !member.IsValid() {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-9dk45", "UserID and Roles are required")
}
existingMember, err := es.OrgMemberByIDs(ctx, member)
if err != nil {
return nil, err
}
member.ObjectRoot = existingMember.ObjectRoot
repoMember := OrgMemberFromModel(member)
repoExistingMember := OrgMemberFromModel(existingMember)
orgAggregate := orgMemberChangedAggregate(es.Eventstore.AggregateCreator(), repoExistingMember, repoMember)
err = es_sdk.Push(ctx, es.PushAggregates, repoMember.AppendEvents, orgAggregate)
if err != nil {
return nil, err
}
return OrgMemberToModel(repoMember), nil
}
func (es *OrgEventstore) RemoveOrgMember(ctx context.Context, member *org_model.OrgMember) error {
if member == nil || member.UserID == "" {
return errors.ThrowInvalidArgument(nil, "EVENT-d43fs", "UserID is required")
}
existingMember, err := es.OrgMemberByIDs(ctx, member)
if errors.IsNotFound(err) {
return nil
}
if err != nil {
return err
}
member.ObjectRoot = existingMember.ObjectRoot
repoMember := OrgMemberFromModel(member)
orgAggregate := orgMemberRemovedAggregate(es.Eventstore.AggregateCreator(), repoMember)
return es_sdk.Push(ctx, es.PushAggregates, repoMember.AppendEvents, orgAggregate)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,96 @@
package eventsourcing
import (
"encoding/json"
"reflect"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/org/model"
)
type OrgMember struct {
es_models.ObjectRoot `json:"-"`
UserID string `json:"userId,omitempty"`
Roles []string `json:"roles,omitempty"`
}
func (m *OrgMember) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
err := m.AppendEvent(event)
if err != nil {
return err
}
}
return nil
}
func (m *OrgMember) AppendEvent(event *es_models.Event) error {
m.ObjectRoot.AppendEvent(event)
return m.setData(event)
}
func (m *OrgMember) setData(event *es_models.Event) error {
err := json.Unmarshal(event.Data, m)
if err != nil {
return errors.ThrowInternal(err, "EVENT-Hz7Mb", "unable to unmarshal data")
}
return nil
}
func (m *OrgMember) Changes(updatedMember *OrgMember) map[string]interface{} {
changes := make(map[string]interface{}, 2)
if !reflect.DeepEqual(m.Roles, updatedMember.Roles) {
changes["roles"] = updatedMember.Roles
changes["userId"] = m.UserID
}
return changes
}
func OrgMemberFromEvent(member *OrgMember, event *es_models.Event) (*OrgMember, error) {
if member == nil {
member = new(OrgMember)
}
member.ObjectRoot.AppendEvent(event)
err := json.Unmarshal(event.Data, member)
if err != nil {
return nil, errors.ThrowInternal(err, "EVENT-D4qxo", "invalid event data")
}
return member, nil
}
func OrgMembersFromModel(members []*model.OrgMember) []*OrgMember {
convertedMembers := make([]*OrgMember, len(members))
for i, m := range members {
convertedMembers[i] = OrgMemberFromModel(m)
}
return convertedMembers
}
func OrgMemberFromModel(member *model.OrgMember) *OrgMember {
return &OrgMember{
ObjectRoot: member.ObjectRoot,
UserID: member.UserID,
Roles: member.Roles,
}
}
func OrgMembersToModel(members []*OrgMember) []*model.OrgMember {
convertedMembers := make([]*model.OrgMember, len(members))
for i, m := range members {
convertedMembers[i] = OrgMemberToModel(m)
}
return convertedMembers
}
func OrgMemberToModel(member *OrgMember) *model.OrgMember {
return &model.OrgMember{
ObjectRoot: member.ObjectRoot,
UserID: member.UserID,
Roles: member.Roles,
}
}

View File

@@ -0,0 +1,195 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
"github.com/sony/sonyflake"
)
var idGenerator = sonyflake.NewSonyflake(sonyflake.Settings{})
func OrgByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) {
if id == "" {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dke74", "id should be filled")
}
return OrgQuery(latestSequence).
AggregateIDFilter(id), nil
}
func OrgDomainUniqueQuery(domain string) *es_models.SearchQuery {
return es_models.NewSearchQuery().
AggregateTypeFilter(org_model.OrgDomainAggregate).
AggregateIDFilter(domain).
OrderDesc().
SetLimit(1)
}
func OrgNameUniqueQuery(name string) *es_models.SearchQuery {
return es_models.NewSearchQuery().
AggregateTypeFilter(org_model.OrgNameAggregate).
AggregateIDFilter(name).
OrderDesc().
SetLimit(1)
}
func OrgQuery(latestSequence uint64) *es_models.SearchQuery {
return es_models.NewSearchQuery().
AggregateTypeFilter(org_model.OrgAggregate).
LatestSequenceFilter(latestSequence)
}
func OrgAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, id string, sequence uint64) (*es_models.Aggregate, error) {
return aggCreator.NewAggregate(ctx, id, org_model.OrgAggregate, orgVersion, sequence)
}
func orgCreatedAggregates(ctx context.Context, aggCreator *es_models.AggregateCreator, org *Org) (_ []*es_models.Aggregate, err error) {
if org == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-kdie7", "org should not be nil")
}
domainAgrregate, err := uniqueDomainAggregate(ctx, aggCreator, org.Domain)
if err != nil {
return nil, err
}
nameAggregate, err := uniqueNameAggregate(ctx, aggCreator, org.Name)
if err != nil {
return nil, err
}
agg, err := aggCreator.NewAggregate(ctx, org.AggregateID, org_model.OrgAggregate, orgVersion, org.Sequence, es_models.OverwriteResourceOwner(org.AggregateID))
if err != nil {
return nil, err
}
agg, err = agg.AppendEvent(org_model.OrgAdded, org)
if err != nil {
return nil, err
}
return []*es_models.Aggregate{
agg,
domainAgrregate,
nameAggregate,
}, nil
}
func OrgUpdateAggregates(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Org, updated *Org) ([]*es_models.Aggregate, error) {
if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dk83d", "existing org must not be nil")
}
if updated == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dhr74", "updated org must not be nil")
}
changes := existing.Changes(updated)
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-E0hc5", "no changes")
}
aggregates := make([]*es_models.Aggregate, 0, 3)
if name, ok := changes["name"]; ok {
nameAggregate, err := uniqueNameAggregate(ctx, aggCreator, name.(string))
if err != nil {
return nil, err
}
aggregates = append(aggregates, nameAggregate)
}
if name, ok := changes["domain"]; ok {
domainAggregate, err := uniqueDomainAggregate(ctx, aggCreator, name.(string))
if err != nil {
return nil, err
}
aggregates = append(aggregates, domainAggregate)
}
orgAggregate, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
orgAggregate, err = orgAggregate.AppendEvent(org_model.OrgChanged, changes)
if err != nil {
return nil, err
}
aggregates = append(aggregates, orgAggregate)
return aggregates, nil
}
func orgDeactivateAggregate(aggCreator *es_models.AggregateCreator, org *Org) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if org == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-R03z8", "existing org must not be nil")
}
if org.State == int32(org_model.ORGSTATE_INACTIVE) {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-mcPH0", "org already inactive")
}
agg, err := OrgAggregate(ctx, aggCreator, org.AggregateID, org.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(org_model.OrgDeactivated, nil)
}
}
func orgReactivateAggregate(aggCreator *es_models.AggregateCreator, org *Org) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if org == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-cTHLd", "existing org must not be nil")
}
if org.State == int32(org_model.ORGSTATE_ACTIVE) {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-pUSMs", "org already active")
}
agg, err := OrgAggregate(ctx, aggCreator, org.AggregateID, org.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(org_model.OrgReactivated, nil)
}
}
func uniqueDomainAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, domain string) (*es_models.Aggregate, error) {
aggregate, err := aggCreator.NewAggregate(ctx, domain, org_model.OrgDomainAggregate, orgVersion, 0)
if err != nil {
return nil, err
}
aggregate, err = aggregate.AppendEvent(org_model.OrgDomainReserved, nil)
if err != nil {
return nil, err
}
return aggregate.SetPrecondition(OrgDomainUniqueQuery(domain), isReservedValidation(aggregate, org_model.OrgDomainReserved)), nil
}
func uniqueNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, name string) (*es_models.Aggregate, error) {
aggregate, err := aggCreator.NewAggregate(ctx, name, org_model.OrgNameAggregate, orgVersion, 0)
if err != nil {
return nil, err
}
aggregate, err = aggregate.AppendEvent(org_model.OrgNameReserved, nil)
if err != nil {
return nil, err
}
return aggregate.SetPrecondition(OrgNameUniqueQuery(name), isReservedValidation(aggregate, org_model.OrgNameReserved)), nil
}
func isReservedValidation(aggregate *es_models.Aggregate, resevedEventType es_models.EventType) func(...*es_models.Event) error {
return func(events ...*es_models.Event) error {
if len(events) == 0 {
aggregate.PreviousSequence = 0
return nil
}
if events[0].Type == resevedEventType {
return errors.ThrowPreconditionFailed(nil, "EVENT-eJQqe", "org already reseved")
}
aggregate.PreviousSequence = events[0].Sequence
return nil
}
}

View File

@@ -0,0 +1,93 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
)
func orgMemberAddedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, member *OrgMember) (*es_models.Aggregate, error) {
if member == nil {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-c63Ap", "member must not be nil")
}
aggregate, err := aggCreator.NewAggregate(ctx, member.AggregateID, org_model.OrgAggregate, orgVersion, member.Sequence)
if err != nil {
return nil, err
}
validationQuery := es_models.NewSearchQuery().
AggregateTypeFilter("org", "user").
AggregateIDsFilter(member.AggregateID, member.UserID)
validation := addMemberValidation(aggregate, member)
return aggregate.SetPrecondition(validationQuery, validation).AppendEvent(org_model.OrgMemberAdded, member)
}
func orgMemberChangedAggregate(aggCreator *es_models.AggregateCreator, existingMember *OrgMember, member *OrgMember) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if member == nil || existingMember == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-d34fs", "member must not be nil")
}
changes := existingMember.Changes(member)
if len(changes) == 0 {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-VLMGn", "nothing changed")
}
agg, err := OrgAggregate(ctx, aggCreator, existingMember.AggregateID, existingMember.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(org_model.OrgMemberChanged, changes)
}
}
func orgMemberRemovedAggregate(aggCreator *es_models.AggregateCreator, member *OrgMember) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if member == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dieu7", "member must not be nil")
}
agg, err := OrgAggregate(ctx, aggCreator, member.AggregateID, member.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(org_model.OrgMemberRemoved, member)
}
}
func addMemberValidation(aggregate *es_models.Aggregate, member *OrgMember) func(...*es_models.Event) error {
return func(events ...*es_models.Event) error {
existsOrg := false
existsUser := false
isMember := false
for _, event := range events {
switch event.AggregateType {
case usr_model.UserAggregate:
existsUser = true
case org_model.OrgAggregate:
aggregate.PreviousSequence = event.Sequence
existsOrg = true
switch event.Type {
case org_model.OrgMemberAdded, org_model.OrgMemberRemoved:
manipulatedMember, err := OrgMemberFromEvent(new(OrgMember), event)
if err != nil {
return errors.ThrowInternal(err, "EVENT-Eg8St", "unable to validate object")
}
if manipulatedMember.UserID == member.UserID {
isMember = event.Type == org_model.OrgMemberAdded
}
}
}
}
if existsOrg && existsUser && !isMember {
return nil
}
return errors.ThrowPreconditionFailed(nil, "EVENT-3OfIm", "conditions not met")
}
}

View File

@@ -0,0 +1,397 @@
package eventsourcing
import (
"context"
"testing"
"github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
usr_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
)
func TestOrgMemberAddedAggregate(t *testing.T) {
type res struct {
isErr func(error) bool
eventCount int
}
type args struct {
aggCreator *es_models.AggregateCreator
member *OrgMember
ctx context.Context
}
tests := []struct {
name string
args args
res res
}{
{
name: "no member",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
member: nil,
},
res: res{
isErr: errors.IsErrorInvalidArgument,
},
},
{
name: "member added sucessfully",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
member: &OrgMember{
ObjectRoot: es_models.ObjectRoot{AggregateID: "asdf", Sequence: 234},
},
},
res: res{
isErr: nil,
eventCount: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aggregate, err := orgMemberAddedAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.member)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && aggregate == nil {
t.Error("aggregate must not be nil")
}
if tt.res.isErr == nil && len(aggregate.Events) != tt.res.eventCount {
t.Error("wrong amount of events")
}
})
}
}
func TestOrgMemberChangedAggregate(t *testing.T) {
type res struct {
isErr func(error) bool
eventCount int
}
type args struct {
aggCreator *es_models.AggregateCreator
existingMember *OrgMember
member *OrgMember
ctx context.Context
}
tests := []struct {
name string
args args
res res
}{
{
name: "no member",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
member: nil,
existingMember: &OrgMember{},
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "no existing member",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
existingMember: nil,
member: &OrgMember{},
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "no changes",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
member: &OrgMember{
ObjectRoot: es_models.ObjectRoot{AggregateID: "asdf", Sequence: 234},
},
existingMember: &OrgMember{
ObjectRoot: es_models.ObjectRoot{AggregateID: "asdf", Sequence: 234},
},
},
res: res{
isErr: errors.IsErrorInvalidArgument,
},
},
{
name: "with changes success",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
member: &OrgMember{
ObjectRoot: es_models.ObjectRoot{AggregateID: "asdf", Sequence: 234},
Roles: []string{"asdf"},
},
existingMember: &OrgMember{
ObjectRoot: es_models.ObjectRoot{AggregateID: "asdf", Sequence: 234},
Roles: []string{"asdf", "woeri"},
},
},
res: res{
isErr: nil,
eventCount: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aggregateCreator := orgMemberChangedAggregate(tt.args.aggCreator, tt.args.existingMember, tt.args.member)
aggregate, err := aggregateCreator(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && aggregate == nil {
t.Error("aggregate must not be nil")
}
if tt.res.isErr == nil && len(aggregate.Events) != tt.res.eventCount {
t.Error("wrong amount of events")
}
})
}
}
func TestOrgMemberRemovedAggregate(t *testing.T) {
type res struct {
isErr func(error) bool
eventCount int
}
type args struct {
aggCreator *es_models.AggregateCreator
member *OrgMember
ctx context.Context
}
tests := []struct {
name string
args args
res res
}{
{
name: "no member",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
member: nil,
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "member added sucessfully",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
member: &OrgMember{
ObjectRoot: es_models.ObjectRoot{AggregateID: "asdf", Sequence: 234},
},
},
res: res{
isErr: nil,
eventCount: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aggregateCreator := orgMemberRemovedAggregate(tt.args.aggCreator, tt.args.member)
aggregate, err := aggregateCreator(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && aggregate == nil {
t.Error("aggregate must not be nil")
}
if tt.res.isErr == nil && len(aggregate.Events) != tt.res.eventCount {
t.Error("wrong amount of events")
}
})
}
}
func Test_addMemberValidation(t *testing.T) {
type res struct {
isErr func(error) bool
preivousSequence uint64
}
type args struct {
aggregate *es_models.Aggregate
events []*es_models.Event
member *OrgMember
}
tests := []struct {
name string
args args
res res
}{
{
name: "no events",
args: args{
aggregate: &es_models.Aggregate{},
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "only org events",
args: args{
aggregate: &es_models.Aggregate{},
events: []*es_models.Event{
{
AggregateType: org_model.OrgAggregate,
Sequence: 13,
},
{
AggregateType: org_model.OrgAggregate,
Sequence: 142,
},
{
AggregateType: org_model.OrgAggregate,
Sequence: 1234,
Type: org_model.OrgMemberAdded,
Data: []byte(`{"userId":"hodor"}`),
},
},
member: &OrgMember{UserID: "hodor"},
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "only user events",
args: args{
aggregate: &es_models.Aggregate{},
events: []*es_models.Event{
{
AggregateType: usr_model.UserAggregate,
Sequence: 13,
},
{
AggregateType: usr_model.UserAggregate,
Sequence: 142,
},
},
member: &OrgMember{UserID: "hodor"},
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "user, org events success",
args: args{
aggregate: &es_models.Aggregate{},
events: []*es_models.Event{
{
AggregateType: usr_model.UserAggregate,
Sequence: 13,
},
{
AggregateType: org_model.OrgAggregate,
Sequence: 142,
},
},
member: &OrgMember{UserID: "hodor"},
},
res: res{
isErr: nil,
preivousSequence: 142,
},
},
{
name: "user, org and member events success",
args: args{
aggregate: &es_models.Aggregate{},
events: []*es_models.Event{
{
AggregateType: usr_model.UserAggregate,
Sequence: 13,
},
{
AggregateType: org_model.OrgAggregate,
Sequence: 142,
},
{
AggregateType: org_model.OrgAggregate,
Sequence: 1234,
Type: org_model.OrgMemberAdded,
Data: []byte(`{"userId":"hodor"}`),
},
{
AggregateType: org_model.OrgAggregate,
Sequence: 1236,
Type: org_model.OrgMemberRemoved,
Data: []byte(`{"userId":"hodor"}`),
},
},
member: &OrgMember{UserID: "hodor"},
},
res: res{
isErr: nil,
preivousSequence: 1236,
},
},
{
name: "user, org and member added events fail",
args: args{
aggregate: &es_models.Aggregate{},
events: []*es_models.Event{
{
AggregateType: usr_model.UserAggregate,
Sequence: 13,
},
{
AggregateType: org_model.OrgAggregate,
Sequence: 142,
},
{
AggregateType: org_model.OrgAggregate,
Sequence: 1234,
Type: org_model.OrgMemberAdded,
Data: []byte(`{"userId":"hodor"}`),
},
},
member: &OrgMember{UserID: "hodor"},
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
validaiton := addMemberValidation(tt.args.aggregate, tt.args.member)
err := validaiton(tt.args.events...)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && tt.args.aggregate.PreviousSequence != tt.res.preivousSequence {
t.Errorf("wrong previous sequence got: %d want %d", tt.args.aggregate.PreviousSequence, tt.res.preivousSequence)
}
})
}
}

View File

@@ -0,0 +1,160 @@
package eventsourcing
import (
"encoding/json"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
)
const (
orgVersion = "v1"
)
type Org struct {
es_models.ObjectRoot `json:"-"`
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
State int32 `json:"-"`
Members []*OrgMember `json:"-"`
}
func OrgFromModel(org *org_model.Org) *Org {
members := OrgMembersFromModel(org.Members)
return &Org{
ObjectRoot: org.ObjectRoot,
Domain: org.Domain,
Name: org.Name,
State: int32(org.State),
Members: members,
}
}
func OrgToModel(org *Org) *org_model.Org {
return &org_model.Org{
ObjectRoot: org.ObjectRoot,
Domain: org.Domain,
Name: org.Name,
State: org_model.OrgState(org.State),
Members: OrgMembersToModel(org.Members),
}
}
func OrgFromEvents(org *Org, events ...*es_models.Event) (*Org, error) {
if org == nil {
org = new(Org)
}
return org, org.AppendEvents(events...)
}
func (o *Org) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
err := o.AppendEvent(event)
if err != nil {
return err
}
}
return nil
}
func (o *Org) AppendEvent(event *es_models.Event) error {
switch event.Type {
case org_model.OrgAdded:
*o = Org{}
err := o.setData(event)
if err != nil {
return err
}
case org_model.OrgChanged:
err := o.setData(event)
if err != nil {
return err
}
case org_model.OrgDeactivated:
o.State = int32(org_model.ORGSTATE_INACTIVE)
case org_model.OrgReactivated:
o.State = int32(org_model.ORGSTATE_ACTIVE)
case org_model.OrgMemberAdded:
member, err := OrgMemberFromEvent(nil, event)
if err != nil {
return err
}
member.CreationDate = event.CreationDate
o.setMember(member)
case org_model.OrgMemberChanged:
member, err := OrgMemberFromEvent(nil, event)
if err != nil {
return err
}
existingMember := o.getMember(member.UserID)
member.CreationDate = existingMember.CreationDate
o.setMember(member)
case org_model.OrgMemberRemoved:
member, err := OrgMemberFromEvent(nil, event)
if err != nil {
return err
}
o.removeMember(member.UserID)
}
o.ObjectRoot.AppendEvent(event)
return nil
}
func (o *Org) setData(event *es_models.Event) error {
err := json.Unmarshal(event.Data, o)
if err != nil {
return errors.ThrowInternal(err, "EVENT-BpbQZ", "unable to unmarshal event")
}
return nil
}
func (o *Org) getMember(userID string) *OrgMember {
for _, member := range o.Members {
if member.UserID == userID {
return member
}
}
return nil
}
func (o *Org) setMember(member *OrgMember) {
for i, existingMember := range o.Members {
if existingMember.UserID == member.UserID {
o.Members[i] = member
return
}
}
o.Members = append(o.Members, member)
}
func (o *Org) removeMember(userID string) {
for i := len(o.Members) - 1; i >= 0; i-- {
if o.Members[i].UserID == userID {
copy(o.Members[i:], o.Members[i+1:])
o.Members[len(o.Members)-1] = nil
o.Members = o.Members[:len(o.Members)-1]
}
}
}
func (o *Org) Changes(changed *Org) map[string]interface{} {
changes := make(map[string]interface{}, 2)
if changed.Name != "" && changed.Name != o.Name {
changes["name"] = changed.Name
}
if changed.Domain != "" && changed.Domain != o.Domain {
changes["domain"] = changed.Domain
}
return changes
}

View File

@@ -0,0 +1,170 @@
package eventsourcing
import (
"encoding/json"
"testing"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/org/model"
)
func TestOrgFromEvents(t *testing.T) {
type args struct {
event []*es_models.Event
org *Org
}
tests := []struct {
name string
args args
result *Org
}{
{
name: "org from events, ok",
args: args{
event: []*es_models.Event{
{AggregateID: "ID", Sequence: 1, Type: model.OrgAdded},
},
org: &Org{Name: "OrgName"},
},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_ACTIVE), Name: "OrgName"},
},
{
name: "org from events, nil org",
args: args{
event: []*es_models.Event{
{AggregateID: "ID", Sequence: 1, Type: model.OrgAdded},
},
org: nil,
},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_ACTIVE)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.org != nil {
data, _ := json.Marshal(tt.args.org)
tt.args.event[0].Data = data
}
result, _ := OrgFromEvents(tt.args.org, tt.args.event...)
if result.Name != tt.result.Name {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.result.Name, result.Name)
}
})
}
}
func TestAppendEvent(t *testing.T) {
type args struct {
event *es_models.Event
org *Org
}
tests := []struct {
name string
args args
result *Org
}{
{
name: "append added event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.OrgAdded},
org: &Org{Name: "OrgName"},
},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_ACTIVE), Name: "OrgName"},
},
{
name: "append change event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.OrgChanged, Data: []byte(`{"domain": "OrgDomain"}`)},
org: &Org{Name: "OrgName", Domain: "asdf"},
},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_ACTIVE), Name: "OrgName", Domain: "OrgDomain"},
},
{
name: "append deactivate event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.OrgDeactivated},
},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_INACTIVE)},
},
{
name: "append reactivate event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.OrgReactivated},
},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_ACTIVE)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.org != nil {
data, _ := json.Marshal(tt.args.org)
tt.args.event.Data = data
}
result := &Org{}
result.AppendEvent(tt.args.event)
if result.State != tt.result.State {
t.Errorf("got wrong result state: expected: %v, actual: %v ", tt.result.State, result.State)
}
if result.Name != tt.result.Name {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.result.Name, result.Name)
}
if result.ObjectRoot.AggregateID != tt.result.ObjectRoot.AggregateID {
t.Errorf("got wrong result id: expected: %v, actual: %v ", tt.result.ObjectRoot.AggregateID, result.ObjectRoot.AggregateID)
}
})
}
}
func TestChanges(t *testing.T) {
type args struct {
existing *Org
new *Org
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "org name changes",
args: args{
existing: &Org{Name: "Name"},
new: &Org{Name: "NameChanged"},
},
res: res{
changesLen: 1,
},
},
{
name: "org domain changes",
args: args{
existing: &Org{Name: "Name", Domain: "old domain"},
new: &Org{Name: "Name", Domain: "new domain"},
},
res: res{
changesLen: 1,
},
},
{
name: "no changes",
args: args{
existing: &Org{Name: "Name"},
new: &Org{Name: "Name"},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existing.Changes(tt.args.new)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}

View File

@@ -0,0 +1,585 @@
package eventsourcing
import (
"context"
"testing"
"github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
)
func Test_isReservedValidation(t *testing.T) {
type res struct {
isErr func(error) bool
agggregateSequence uint64
}
type args struct {
aggregate *es_models.Aggregate
eventType es_models.EventType
Events []*es_models.Event
}
tests := []struct {
name string
args args
res res
}{
{
name: "no events success",
args: args{
aggregate: &es_models.Aggregate{},
eventType: "object.reserved",
Events: []*es_models.Event{},
},
res: res{
isErr: nil,
agggregateSequence: 0,
},
},
{
name: "not reseved success",
args: args{
aggregate: &es_models.Aggregate{},
eventType: "object.reserved",
Events: []*es_models.Event{
{
AggregateID: "asdf",
AggregateType: "org",
Sequence: 45,
Type: "object.released",
},
},
},
res: res{
isErr: nil,
agggregateSequence: 45,
},
},
{
name: "reseved error",
args: args{
aggregate: &es_models.Aggregate{},
eventType: "object.reserved",
Events: []*es_models.Event{
{
AggregateID: "asdf",
AggregateType: "org",
Sequence: 45,
Type: "object.reserved",
},
},
},
res: res{
isErr: errors.IsPreconditionFailed,
agggregateSequence: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
validate := isReservedValidation(tt.args.aggregate, tt.args.eventType)
err := validate(tt.args.Events...)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got: %v", err)
}
if err == nil && tt.args.aggregate.PreviousSequence != tt.res.agggregateSequence {
t.Errorf("expected sequence %d got %d", tt.res.agggregateSequence, tt.args.aggregate.PreviousSequence)
}
})
}
}
func aggregateWithPrecondition() *es_models.Aggregate {
return nil
}
func Test_uniqueNameAggregate(t *testing.T) {
type res struct {
expected *es_models.Aggregate
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
orgName string
}
tests := []struct {
name string
args args
res res
}{
{
name: "no org name error",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
aggCreator: es_models.NewAggregateCreator("test"),
orgName: "",
},
res: res{
expected: nil,
isErr: errors.IsPreconditionFailed,
},
},
{
name: "aggregate created",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
aggCreator: es_models.NewAggregateCreator("test"),
orgName: "asdf",
},
res: res{
expected: aggregateWithPrecondition(),
isErr: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := uniqueNameAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.orgName)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && (got.Precondition == nil || got.Precondition.Query == nil || got.Precondition.Validation == nil) {
t.Errorf("precondition is not set correctly")
}
})
}
}
func Test_uniqueDomainAggregate(t *testing.T) {
type res struct {
expected *es_models.Aggregate
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
orgDomain string
}
tests := []struct {
name string
args args
res res
}{
{
name: "no org domain error",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
aggCreator: es_models.NewAggregateCreator("test"),
orgDomain: "",
},
res: res{
expected: nil,
isErr: errors.IsPreconditionFailed,
},
},
{
name: "aggregate created",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
aggCreator: es_models.NewAggregateCreator("test"),
orgDomain: "asdf",
},
res: res{
expected: aggregateWithPrecondition(),
isErr: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := uniqueDomainAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.orgDomain)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && (got.Precondition == nil || got.Precondition.Query == nil || got.Precondition.Validation == nil) {
t.Errorf("precondition is not set correctly")
}
})
}
}
func TestOrgReactivateAggregate(t *testing.T) {
type res struct {
isErr func(error) bool
}
type args struct {
aggCreator *es_models.AggregateCreator
org *Org
ctx context.Context
}
tests := []struct {
name string
args args
res res
}{
{
name: "correct",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
org: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "orgID",
Sequence: 2,
},
State: int32(org_model.ORGSTATE_INACTIVE),
},
},
},
{
name: "already active error",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
org: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "orgID",
Sequence: 2,
},
State: int32(org_model.ORGSTATE_ACTIVE),
},
},
res: res{
isErr: errors.IsErrorInvalidArgument,
},
},
{
name: "org nil error",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
org: nil,
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aggregateCreator := orgReactivateAggregate(tt.args.aggCreator, tt.args.org)
aggregate, err := aggregateCreator(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && aggregate == nil {
t.Error("aggregate must not be nil")
}
})
}
}
func TestOrgDeactivateAggregate(t *testing.T) {
type res struct {
isErr func(error) bool
}
type args struct {
aggCreator *es_models.AggregateCreator
org *Org
ctx context.Context
}
tests := []struct {
name string
args args
res res
}{
{
name: "correct",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
org: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "orgID",
Sequence: 2,
},
State: int32(org_model.ORGSTATE_ACTIVE),
},
},
},
{
name: "already inactive error",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
org: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "orgID",
Sequence: 2,
},
State: int32(org_model.ORGSTATE_INACTIVE),
},
},
res: res{
isErr: errors.IsErrorInvalidArgument,
},
},
{
name: "org nil error",
args: args{
aggCreator: es_models.NewAggregateCreator("test"),
ctx: auth.NewMockContext("org", "user"),
org: nil,
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
aggregateCreator := orgDeactivateAggregate(tt.args.aggCreator, tt.args.org)
aggregate, err := aggregateCreator(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && aggregate == nil {
t.Error("aggregate must not be nil")
}
})
}
}
func TestOrgUpdateAggregates(t *testing.T) {
type res struct {
aggregateCount int
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
existing *Org
updated *Org
}
tests := []struct {
name string
args args
res res
}{
{
name: "no existing org error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
existing: nil,
updated: &Org{},
},
res: res{
aggregateCount: 0,
isErr: errors.IsPreconditionFailed,
},
},
{
name: "no updated org error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
existing: &Org{},
updated: nil,
},
res: res{
aggregateCount: 0,
isErr: errors.IsPreconditionFailed,
},
},
{
name: "no changes",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
existing: &Org{},
updated: &Org{},
},
res: res{
aggregateCount: 0,
isErr: errors.IsPreconditionFailed,
},
},
{
name: "name changed",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
existing: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
Name: "coas",
},
updated: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
Name: "caos",
},
},
res: res{
aggregateCount: 2,
isErr: nil,
},
},
{
name: "domain changed",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
existing: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.swiss",
Name: "caos",
},
updated: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
Name: "caos",
},
},
res: res{
aggregateCount: 2,
isErr: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := OrgUpdateAggregates(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.updated)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && len(got) != tt.res.aggregateCount {
t.Errorf("OrgUpdateAggregates() aggregate count = %d, wanted count %d", len(got), tt.res.aggregateCount)
}
})
}
}
func TestOrgCreatedAggregates(t *testing.T) {
type res struct {
aggregateCount int
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
org *Org
}
tests := []struct {
name string
args args
res res
}{
{
name: "no org error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: nil,
},
res: res{
aggregateCount: 0,
isErr: errors.IsPreconditionFailed,
},
},
{
name: "org successful",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
Name: "caos",
},
},
res: res{
aggregateCount: 3,
isErr: nil,
},
},
{
name: "no domain error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Name: "caos",
},
},
res: res{
aggregateCount: 2,
isErr: errors.IsPreconditionFailed,
},
},
{
name: "no name error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
},
},
res: res{
aggregateCount: 2,
isErr: errors.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := orgCreatedAggregates(tt.args.ctx, tt.args.aggCreator, tt.args.org)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got %T: %v", err, err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && len(got) != tt.res.aggregateCount {
t.Errorf("OrgUpdateAggregates() aggregate count = %d, wanted count %d", len(got), tt.res.aggregateCount)
}
})
}
}

View File

@@ -0,0 +1,10 @@
package repository
import es_models "github.com/caos/zitadel/internal/eventstore/models"
type Org struct {
es_models.ObjectRoot
Name string
Domain string
}