Project commands (#26)

* feat: eventstore repository

* fix: remove gorm

* version

* feat: pkg

* feat: add some files for project

* feat: eventstore without eventstore-lib

* rename files

* gnueg

* fix: key json

* fix: add object

* fix: change imports

* fix: internal models

* fix: some imports

* fix: global model

* fix: add some functions on repo

* feat(eventstore): sdk

* fix(eventstore): search query

* fix(eventstore): rename app to eventstore

* delete empty test

* remove unused func

* merge master

* fix(eventstore): tests

* fix(models): delete unused struct

* fix: some funcitons

* feat(eventstore): implemented push events

* fix: move project eventstore to project package

* fix: change project eventstore funcs

* feat(eventstore): overwrite context data

* fix: change project eventstore

* fix: add project repo to mgmt server

* feat(types): SQL-config

* fix: commented code

* feat(eventstore): options to overwrite editor

* feat: auth interceptor and cockroach migrations

* fix: migrations

* fix: fix filter

* fix: not found on getbyid

* fix: add sequence

* fix: add some tests

* fix(eventstore): nullable sequence

* fix: add some tests

* merge

* fix: add some tests

* fix(migrations): correct statements for sequence

* fix: add some tests

* fix: add some tests

* fix: changes from mr

* Update internal/eventstore/models/field.go

Co-Authored-By: livio-a <livio.a@gmail.com>

* fix(eventstore): code quality

* fix: add types to aggregate/Event-types

* fix(eventstore): rename modifier* to editor*

* fix(eventstore): delete editor_org

* fix(migrations): remove editor_org field,
rename modifier_* to editor_*

* fix: generate files

* fix(eventstore): tests

* fix(eventstore): rename modifier to editor

* fix(migrations): add cluster migration,
fix(migrations): fix typo of host in clean clsuter

* fix(eventstore): move health

* fix(eventstore): AggregateTypeFilter aggregateType as param

* code quality

* feat: start implementing project members

* feat: remove member funcs

* feat: remove member model

* feat: remove member events

* feat: remove member repo model

* fix: better error func testing

* Update docs/local.md

Co-Authored-By: Silvan <silvan.reusser@gmail.com>

* Update docs/local.md

Co-Authored-By: Silvan <silvan.reusser@gmail.com>

* fix: mr requests

* fix: md file

Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
Co-authored-by: livio-a <livio.a@gmail.com>
This commit is contained in:
Fabi
2020-04-07 13:23:04 +02:00
committed by GitHub
parent 007fc9e9bd
commit c07ed83c41
61 changed files with 5259 additions and 3481 deletions

View File

@@ -0,0 +1,35 @@
package model
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
in_model "github.com/caos/zitadel/internal/model"
)
type Project struct {
es_models.ObjectRoot
State ProjectState
Name string
}
type ProjectState in_model.Enum
var states = []string{"Active", "Inactive"}
func NewProject(id string) *Project {
return &Project{ObjectRoot: es_models.ObjectRoot{ID: id}, State: Active}
}
func (p *Project) IsActive() bool {
if p.State == Active {
return true
}
return false
}
func (p *Project) IsValid() bool {
if p.Name == "" {
return false
}
return true
}

View File

@@ -0,0 +1,23 @@
package model
type state int32
func (s state) String() string {
return states[s]
}
const (
Active state = iota
Inactive
)
func ProjectStateToInt(s ProjectState) int32 {
if s == nil {
return 0
}
return int32(s.(state))
}
func ProjectStateFromInt(index int32) ProjectState {
return state(index)
}

View File

@@ -0,0 +1,37 @@
package model
import "github.com/caos/zitadel/internal/eventstore/models"
const (
ProjectAggregate models.AggregateType = "project"
ProjectAdded models.EventType = "project.added"
ProjectChanged models.EventType = "project.changed"
ProjectDeactivated models.EventType = "project.deactivated"
ProjectReactivated models.EventType = "project.reactivated"
ProjectMemberAdded models.EventType = "project.member.added"
ProjectMemberChanged models.EventType = "project.member.changed"
ProjectMemberRemoved models.EventType = "project.member.removed"
ProjectRoleAdded models.EventType = "project.role.added"
ProjectRoleRemoved models.EventType = "project.role.removed"
ProjectGrantAdded models.EventType = "project.grant.added"
ProjectGrantChanged models.EventType = "project.grant.changed"
ProjectGrantDeactivated models.EventType = "project.grant.deactivated"
ProjectGrantReactivated models.EventType = "project.grant.reactivated"
GrantMemberAdded models.EventType = "project.grant.member.added"
GrantMemberChanged models.EventType = "project.grant.member.changed"
GrantMemberRemoved models.EventType = "project.grant.member.removed"
ApplicationAdded models.EventType = "project.application.added"
ApplicationChanged models.EventType = "project.application.changed"
ApplicationDeactivated models.EventType = "project.application.deactivated"
ApplicationReactivated models.EventType = "project.application.reactivated"
OIDCConfigAdded models.EventType = "project.application.config.oidc.added"
OIDCConfigChanged models.EventType = "project.application.config.oidc.changed"
OIDCConfigSecretChanged models.EventType = "project.application.config.oidc.secret.changed"
)

View File

@@ -0,0 +1,55 @@
package eventsourcing
import (
"encoding/json"
"github.com/caos/logging"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
)
func ProjectFromEvents(project *Project, events ...*es_models.Event) (*Project, error) {
if project == nil {
project = &Project{}
}
return project, project.AppendEvents(events...)
}
func (p *Project) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
if err := p.AppendEvent(event); err != nil {
return err
}
}
return nil
}
func (p *Project) AppendEvent(event *es_models.Event) error {
p.ObjectRoot.AppendEvent(event)
switch event.Type {
case model.ProjectAdded, model.ProjectChanged:
if err := json.Unmarshal(event.Data, p); err != nil {
logging.Log("EVEN-idl93").WithError(err).Error("could not unmarshal event data")
return err
}
p.State = model.ProjectStateToInt(model.Active)
return nil
case model.ProjectDeactivated:
return p.appendDeactivatedEvent()
case model.ProjectReactivated:
return p.appendReactivatedEvent()
}
return nil
}
func (p *Project) appendDeactivatedEvent() error {
p.State = model.ProjectStateToInt(model.Inactive)
return nil
}
func (p *Project) appendReactivatedEvent() error {
p.State = model.ProjectStateToInt(model.Active)
return nil
}

View File

@@ -0,0 +1,169 @@
package eventsourcing
import (
"encoding/json"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"testing"
)
func TestProjectFromEvents(t *testing.T) {
type args struct {
event []*es_models.Event
project *Project
}
tests := []struct {
name string
args args
result *Project
}{
{
name: "project from events, ok",
args: args{
event: []*es_models.Event{
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded},
},
project: &Project{Name: "ProjectName"},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active), Name: "ProjectName"},
},
{
name: "project from events, nil project",
args: args{
event: []*es_models.Event{
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded},
},
project: nil,
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.project != nil {
data, _ := json.Marshal(tt.args.project)
tt.args.event[0].Data = data
}
result, _ := ProjectFromEvents(tt.args.project, 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
project *Project
}
tests := []struct {
name string
args args
result *Project
}{
{
name: "append added event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded},
project: &Project{Name: "ProjectName"},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active), Name: "ProjectName"},
},
{
name: "append change event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectChanged},
project: &Project{Name: "ProjectName"},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active), Name: "ProjectName"},
},
{
name: "append deactivate event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectDeactivated},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Inactive)},
},
{
name: "append reactivate event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectReactivated},
},
result: &Project{ObjectRoot: es_models.ObjectRoot{ID: "ID"}, State: int32(model.Active)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.project != nil {
data, _ := json.Marshal(tt.args.project)
tt.args.event.Data = data
}
result := &Project{}
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.ID != tt.result.ObjectRoot.ID {
t.Errorf("got wrong result id: expected: %v, actual: %v ", tt.result.ObjectRoot.ID, result.ObjectRoot.ID)
}
})
}
}
func TestAppendDeactivatedEvent(t *testing.T) {
type args struct {
project *Project
}
tests := []struct {
name string
args args
result *Project
}{
{
name: "append reactivate event",
args: args{
project: &Project{},
},
result: &Project{State: int32(model.Inactive)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.project.appendDeactivatedEvent()
if tt.args.project.State != tt.result.State {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.project)
}
})
}
}
func TestAppendReactivatedEvent(t *testing.T) {
type args struct {
project *Project
}
tests := []struct {
name string
args args
result *Project
}{
{
name: "append reactivate event",
args: args{
project: &Project{},
},
result: &Project{State: int32(model.Active)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.args.project.appendReactivatedEvent()
if tt.args.project.State != tt.result.State {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.project)
}
})
}
}

View File

@@ -0,0 +1,110 @@
package eventsourcing
import (
"context"
caos_errs "github.com/caos/zitadel/internal/errors"
es_int "github.com/caos/zitadel/internal/eventstore"
proj_model "github.com/caos/zitadel/internal/project/model"
)
type ProjectEventstore struct {
es_int.Eventstore
}
type ProjectConfig struct {
es_int.Eventstore
}
func StartProject(conf ProjectConfig) (*ProjectEventstore, error) {
return &ProjectEventstore{Eventstore: conf.Eventstore}, nil
}
func (es *ProjectEventstore) ProjectByID(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
filter, err := ProjectByIDQuery(project.ID, project.Sequence)
if err != nil {
return nil, err
}
events, err := es.Eventstore.FilterEvents(ctx, filter)
if err != nil {
return nil, err
}
if len(events) == 0 {
return nil, caos_errs.ThrowNotFound(nil, "EVENT-8due3", "Could not find project events")
}
foundProject, err := ProjectFromEvents(nil, events...)
if err != nil {
return nil, err
}
return ProjectToModel(foundProject), nil
}
func (es *ProjectEventstore) CreateProject(ctx context.Context, project *proj_model.Project) (*proj_model.Project, error) {
if !project.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Name is required")
}
project.State = proj_model.Active
repoProject := ProjectFromModel(project)
projectAggregate, err := ProjectCreateAggregate(ctx, es.Eventstore.AggregateCreator(), repoProject)
if err != nil {
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoProject.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoProject), nil
}
func (es *ProjectEventstore) UpdateProject(ctx context.Context, existing *proj_model.Project, new *proj_model.Project) (*proj_model.Project, error) {
if !new.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Name is required")
}
repoExisting := ProjectFromModel(existing)
repoNew := ProjectFromModel(new)
projectAggregate, err := ProjectUpdateAggregate(ctx, es.AggregateCreator(), repoExisting, repoNew)
if err != nil {
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoExisting.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoExisting), nil
}
func (es *ProjectEventstore) DeactivateProject(ctx context.Context, existing *proj_model.Project) (*proj_model.Project, error) {
if !existing.IsActive() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be active")
}
repoExisting := ProjectFromModel(existing)
projectAggregate, err := ProjectDeactivateAggregate(ctx, es.AggregateCreator(), repoExisting)
if err != nil {
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoExisting.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoExisting), nil
}
func (es *ProjectEventstore) ReactivateProject(ctx context.Context, existing *proj_model.Project) (*proj_model.Project, error) {
if existing.IsActive() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "project must be inactive")
}
repoExisting := ProjectFromModel(existing)
projectAggregate, err := ProjectReactivateAggregate(ctx, es.AggregateCreator(), repoExisting)
if err != nil {
return nil, err
}
err = es.PushAggregates(ctx, projectAggregate)
if err != nil {
return nil, err
}
repoExisting.AppendEvents(projectAggregate.Events...)
return ProjectToModel(repoExisting), nil
}

View File

@@ -0,0 +1,33 @@
package eventsourcing
import (
"encoding/json"
"github.com/caos/zitadel/internal/eventstore/mock"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"github.com/golang/mock/gomock"
)
func GetMockProjectByIDOK(ctrl *gomock.Controller) *ProjectEventstore {
data, _ := json.Marshal(Project{Name: "Name"})
events := []*es_models.Event{
&es_models.Event{AggregateID: "ID", Sequence: 1, Type: model.ProjectAdded, Data: data},
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
return &ProjectEventstore{Eventstore: mockEs}
}
func GetMockProjectByIDNoEvents(ctrl *gomock.Controller) *ProjectEventstore {
events := []*es_models.Event{}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
return &ProjectEventstore{Eventstore: mockEs}
}
func GetMockManipulateProject(ctrl *gomock.Controller) *ProjectEventstore {
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return &ProjectEventstore{Eventstore: mockEs}
}

View File

@@ -0,0 +1,311 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"github.com/golang/mock/gomock"
"testing"
)
func TestProjectByID(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
project *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project from events, ok",
args: args{
es: GetMockProjectByIDOK(ctrl),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}},
},
},
{
name: "project from events, no events",
args: args{
es: GetMockProjectByIDNoEvents(ctrl),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
{
name: "project from events, no id",
args: args{
es: GetMockProjectByIDNoEvents(ctrl),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "", Sequence: 1}},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ProjectByID(nil, tt.args.project)
if !tt.res.wantErr && result.ID != tt.res.project.ID {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.ID, result.ID)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestCreateProject(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
ctx context.Context
project *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project from events, ok",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name"},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name"},
},
},
{
name: "create project no name",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.CreateProject(tt.args.ctx, tt.args.project)
if !tt.res.wantErr && result.ID == "" {
t.Errorf("result has no id")
}
if !tt.res.wantErr && result.Name != tt.res.project.Name {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.Name, result.Name)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestUpdateProject(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
ctx context.Context
existing *model.Project
new *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project from events, ok",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name"},
new: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew"},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew"},
},
},
{
name: "create project no name",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name"},
new: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: ""},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.UpdateProject(tt.args.ctx, tt.args.existing, tt.args.new)
if !tt.res.wantErr && result.ID == "" {
t.Errorf("result has no id")
}
if !tt.res.wantErr && result.Name != tt.res.project.Name {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.Name, result.Name)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestDeactivateProject(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
ctx context.Context
existing *model.Project
new *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "deactivate project, ok",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name", State: model.Active},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew", State: model.Inactive},
},
},
{
name: "deactivate project with inactive state",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name", State: model.Inactive},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.DeactivateProject(tt.args.ctx, tt.args.existing)
if !tt.res.wantErr && result.ID == "" {
t.Errorf("result has no id")
}
if !tt.res.wantErr && result.State != tt.res.project.State {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.State, result.State)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestReactivateProject(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *ProjectEventstore
ctx context.Context
existing *model.Project
new *model.Project
}
type res struct {
project *model.Project
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "deactivate project, ok",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name", State: model.Inactive},
},
res: res{
project: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "NameNew", State: model.Active},
},
},
{
name: "deactivate project with inactive state",
args: args{
es: GetMockManipulateProject(ctrl),
ctx: auth.NewMockContext("orgID", "userID"),
existing: &model.Project{ObjectRoot: es_models.ObjectRoot{ID: "ID", Sequence: 1}, Name: "Name", State: model.Active},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ReactivateProject(tt.args.ctx, tt.args.existing)
if !tt.res.wantErr && result.ID == "" {
t.Errorf("result has no id")
}
if !tt.res.wantErr && result.State != tt.res.project.State {
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.project.State, result.State)
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -0,0 +1,131 @@
package eventsourcing
import (
"context"
"strconv"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"github.com/sony/sonyflake"
)
var idGenerator = sonyflake.NewSonyflake(sonyflake.Settings{})
const (
projectVersion = "v1"
)
type Project struct {
es_models.ObjectRoot
Name string `json:"name,omitempty"`
State int32 `json:"-"`
}
func (p *Project) Changes(changed *Project) map[string]interface{} {
changes := make(map[string]interface{}, 1)
if changed.Name != "" && p.Name != changed.Name {
changes["name"] = changed.Name
}
return changes
}
func ProjectFromModel(project *model.Project) *Project {
return &Project{
ObjectRoot: es_models.ObjectRoot{
ID: project.ObjectRoot.ID,
Sequence: project.Sequence,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
},
Name: project.Name,
State: model.ProjectStateToInt(project.State),
}
}
func ProjectToModel(project *Project) *model.Project {
return &model.Project{
ObjectRoot: es_models.ObjectRoot{
ID: project.ID,
ChangeDate: project.ChangeDate,
CreationDate: project.CreationDate,
Sequence: project.Sequence,
},
Name: project.Name,
State: model.ProjectStateFromInt(project.State),
}
}
func ProjectByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) {
if id == "" {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dke74", "id should be filled")
}
return ProjectQuery(latestSequence).
AggregateIDFilter(id), nil
}
func ProjectQuery(latestSequence uint64) *es_models.SearchQuery {
return es_models.NewSearchQuery().
AggregateTypeFilter(model.ProjectAggregate).
LatestSequenceFilter(latestSequence)
}
func ProjectAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, id string, sequence uint64) (*es_models.Aggregate, error) {
return aggCreator.NewAggregate(ctx, id, model.ProjectAggregate, projectVersion, sequence)
}
func ProjectCreateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, project *Project) (*es_models.Aggregate, error) {
if project == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-kdie6", "project should not be nil")
}
var err error
id, err := idGenerator.NextID()
if err != nil {
return nil, err
}
project.ID = strconv.FormatUint(id, 10)
agg, err := ProjectAggregate(ctx, aggCreator, project.ID, project.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.ProjectAdded, project)
}
func ProjectUpdateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project, new *Project) (*es_models.Aggregate, error) {
if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dk93d", "existing project should not be nil")
}
if new == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dhr74", "new project should not be nil")
}
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
if err != nil {
return nil, err
}
changes := existing.Changes(new)
return agg.AppendEvent(model.ProjectChanged, changes)
}
func ProjectDeactivateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project) (*es_models.Aggregate, error) {
if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ueh45", "existing project should not be nil")
}
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.ProjectDeactivated, nil)
}
func ProjectReactivateAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *Project) (*es_models.Aggregate, error) {
if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-37dur", "existing project should not be nil")
}
agg, err := ProjectAggregate(ctx, aggCreator, existing.ID, existing.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.ProjectReactivated, nil)
}

View File

@@ -0,0 +1,453 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
"testing"
)
func TestChanges(t *testing.T) {
type args struct {
existing *Project
new *Project
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "project name changes",
args: args{
existing: &Project{Name: "Name"},
new: &Project{Name: "NameChanged"},
},
res: res{
changesLen: 1,
},
},
{
name: "no changes",
args: args{
existing: &Project{Name: "Name"},
new: &Project{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))
}
})
}
}
func TestProjectByIDQuery(t *testing.T) {
type args struct {
id string
sequence uint64
}
type res struct {
filterLen int
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project by id query ok",
args: args{
id: "ID",
sequence: 1,
},
res: res{
filterLen: 3,
},
},
{
name: "project by id query, no id",
args: args{
sequence: 1,
},
res: res{
filterLen: 3,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
query, err := ProjectByIDQuery(tt.args.id, tt.args.sequence)
if !tt.res.wantErr && query == nil {
t.Errorf("query should not be nil")
}
if !tt.res.wantErr && len(query.Filters) != tt.res.filterLen {
t.Errorf("got wrong filter len: expected: %v, actual: %v ", tt.res.filterLen, len(query.Filters))
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestProjectQuery(t *testing.T) {
type args struct {
sequence uint64
}
type res struct {
filterLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "project query ok",
args: args{
sequence: 1,
},
res: res{
filterLen: 2,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
query := ProjectQuery(tt.args.sequence)
if query == nil {
t.Errorf("query should not be nil")
}
if len(query.Filters) != tt.res.filterLen {
t.Errorf("got wrong filter len: expected: %v, actual: %v ", tt.res.filterLen, len(query.Filters))
}
})
}
}
func TestProjectAggregate(t *testing.T) {
type args struct {
ctx context.Context
aggCreator *models.AggregateCreator
id string
sequence uint64
}
type res struct {
eventLen int
aggType models.AggregateType
}
tests := []struct {
name string
args args
res res
}{
{
name: "project update aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
aggCreator: models.NewAggregateCreator("Test"),
id: "ID",
sequence: 1,
},
res: res{
eventLen: 0,
aggType: model.ProjectAggregate,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, _ := ProjectAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.id, tt.args.sequence)
if agg == nil {
t.Errorf("agg should not be nil")
}
if len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
})
}
}
func TestProjectCreateAggregate(t *testing.T) {
type args struct {
ctx context.Context
new *Project
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project update aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
new: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Active)},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectAdded,
},
},
{
name: "new project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectAdded,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectCreateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.new)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if !tt.res.wantErr && agg.Events[0].Data == nil {
t.Errorf("should have data in event")
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestProjectUpdateAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *Project
new *Project
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project update aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Active)},
new: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName_Changed", State: int32(model.Active)},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectChanged,
},
},
{
name: "existing project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectChanged,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "new project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Active)},
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectChanged,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectUpdateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing, tt.args.new)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if !tt.res.wantErr && agg.Events[0].Data == nil {
t.Errorf("should have data in event")
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestProjectDeactivateAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *Project
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project deactivate aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Active)},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectDeactivated,
},
},
{
name: "existing project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectDeactivated,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectDeactivateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestProjectReactivateAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *Project
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventType models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "project reactivate aggregate ok",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: &Project{ObjectRoot: models.ObjectRoot{ID: "ID"}, Name: "ProjectName", State: int32(model.Inactive)},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectReactivated,
},
},
{
name: "existing project nil",
args: args{
ctx: auth.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventType: model.ProjectReactivated,
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := ProjectReactivateAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.existing)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
if !tt.res.wantErr && agg.Events[0].Type != tt.res.eventType {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}