diff --git a/api.yaml b/api.yaml index b7a2473c..990c4e7b 100644 --- a/api.yaml +++ b/api.yaml @@ -46,8 +46,8 @@ components: type: object properties: id: - type: string - format: ksuid + type: integer + format: uint firstName: type: string lastName: @@ -60,7 +60,6 @@ components: - lastName x-identifier: - id - - email Blog: type: object properties: diff --git a/controllers/rest/fixtures/blog-x-schema.yaml b/controllers/rest/fixtures/blog-x-schema.yaml index 71c78e5a..c6692ec9 100644 --- a/controllers/rest/fixtures/blog-x-schema.yaml +++ b/controllers/rest/fixtures/blog-x-schema.yaml @@ -32,14 +32,15 @@ components: Category: type: object properties: - title: + id: type: string + format: uuid description: type: string required: - - title + - id x-identifier: - - title + - id Author: type: object properties: @@ -58,7 +59,6 @@ components: - lastName x-identifier: - id - - email Blog: type: object properties: @@ -115,6 +115,15 @@ components: created: type: string format: date-time + Archives: + type: object + properties: + id: + type: integer + title: + type: string + x-identifier: + - id paths: /health: summary: Health Check @@ -605,4 +614,4 @@ paths: summary: Delete author responses: 200: - description: Delete author + description: Delete author \ No newline at end of file diff --git a/controllers/rest/middleware_initialize.go b/controllers/rest/middleware_initialize.go index 0a1b5000..d88d4bd8 100644 --- a/controllers/rest/middleware_initialize.go +++ b/controllers/rest/middleware_initialize.go @@ -121,6 +121,7 @@ func newSchema(ref *openapi3.Schema, logger echo.Logger) (ds.Builder, map[string relations[name] = strings.TrimPrefix(p.Ref, "#/components/schemas/") } else { t := p.Value.Type + f := p.Value.Format if strings.EqualFold(t, "array") { t2 := p.Value.Items.Value.Type if t2 != "object" { @@ -133,7 +134,12 @@ func newSchema(ref *openapi3.Schema, logger echo.Logger) (ds.Builder, map[string } else if t2 == "number" { instance.AddField(name, []float64{}, tagString) } else if t == "integer" { - instance.AddField(name, []int{}, tagString) + if f == "uint" { + instance.AddField(name, []uint{}, tagString) + } else { + instance.AddField(name, []int{}, tagString) + } + } else if t == "boolean" { instance.AddField(name, []bool{}, tagString) } @@ -166,8 +172,13 @@ func newSchema(ref *openapi3.Schema, logger echo.Logger) (ds.Builder, map[string var numbers *float32 defaultValue = numbers case "integer": - var integers *int - defaultValue = integers + if f == "uint" { + defaultValue = uint(0) + } else { + var integers *int + defaultValue = integers + } + case "boolean": var boolean *bool defaultValue = boolean diff --git a/controllers/rest/weos_mocks_test.go b/controllers/rest/weos_mocks_test.go index 10753fa5..2875edfe 100644 --- a/controllers/rest/weos_mocks_test.go +++ b/controllers/rest/weos_mocks_test.go @@ -1,3 +1,4 @@ +//This is a copy of model mocks // Code generated by moq; DO NOT EDIT. // github.com/matryer/moq @@ -550,45 +551,43 @@ func (mock *EventRepositoryMock) ReplayEventsCalls() []struct { return calls } -// Ensure, that ProjectionMock does implement model.Projection. -// If this is not the case, regenerate this file with moq. // Ensure, that ProjectionMock does implement model.Projection. // If this is not the case, regenerate this file with moq. var _ model.Projection = &ProjectionMock{} // ProjectionMock is a mock implementation of model.Projection. // -// func TestSomethingThatUsesProjection(t *testing.T) { +// func TestSomethingThatUsesProjection(t *testing.T) { // -// // make and configure a mocked model.Projection -// mockedProjection := &ProjectionMock{ -// GetByEntityIDFunc: func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { -// panic("mock out the GetByEntityID method") -// }, -// GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { -// panic("mock out the GetByKey method") -// }, -// GetByPropertiesFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { -// panic("mock out the GetByProperties method") -// }, -// GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page int, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { -// panic("mock out the GetContentEntities method") -// }, -// GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { -// panic("mock out the GetContentEntity method") -// }, -// GetEventHandlerFunc: func() model.EventHandler { -// panic("mock out the GetEventHandler method") -// }, -// MigrateFunc: func(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error { -// panic("mock out the Migrate method") -// }, -// } +// // make and configure a mocked model.Projection +// mockedProjection := &ProjectionMock{ +// GetByEntityIDFunc: func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { +// panic("mock out the GetByEntityID method") +// }, +// GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { +// panic("mock out the GetByKey method") +// }, +// GetByPropertiesFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { +// panic("mock out the GetByProperties method") +// }, +// GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page int, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { +// panic("mock out the GetContentEntities method") +// }, +// GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { +// panic("mock out the GetContentEntity method") +// }, +// GetEventHandlerFunc: func() model.EventHandler { +// panic("mock out the GetEventHandler method") +// }, +// MigrateFunc: func(ctx context.Context, builders map[string]ds.Builder, deletedFields map[string][]string) error { +// panic("mock out the Migrate method") +// }, +// } // -// // use mockedProjection in code that requires model.Projection -// // and then make assertions. +// // use mockedProjection in code that requires model.Projection +// // and then make assertions. // -// } +// } type ProjectionMock struct { // GetByEntityIDFunc mocks the GetByEntityID method. GetByEntityIDFunc func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) @@ -964,8 +963,6 @@ func (mock *ProjectionMock) MigrateCalls() []struct { return calls } - - // Ensure, that LogMock does implement model.Log. // If this is not the case, regenerate this file with moq. var _ model.Log = &LogMock{} @@ -2201,6 +2198,9 @@ var _ model.EntityFactory = &EntityFactoryMock{} // BuilderFunc: func(ctx context.Context) ds.Builder { // panic("mock out the Builder method") // }, +// CreateEntityWithValuesFunc: func(ctx context.Context, payload []byte) (*model.ContentEntity, error) { +// panic("mock out the CreateEntityWithValues method") +// }, // DynamicStructFunc: func(ctx context.Context) ds.DynamicStruct { // panic("mock out the DynamicStruct method") // }, @@ -2229,6 +2229,9 @@ type EntityFactoryMock struct { // BuilderFunc mocks the Builder method. BuilderFunc func(ctx context.Context) ds.Builder + // CreateEntityWithValuesFunc mocks the CreateEntityWithValues method. + CreateEntityWithValuesFunc func(ctx context.Context, payload []byte) (*model.ContentEntity, error) + // DynamicStructFunc mocks the DynamicStruct method. DynamicStructFunc func(ctx context.Context) ds.DynamicStruct @@ -2254,6 +2257,13 @@ type EntityFactoryMock struct { // Ctx is the ctx argument value. Ctx context.Context } + // CreateEntityWithValues holds details about calls to the CreateEntityWithValues method. + CreateEntityWithValues []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Payload is the payload argument value. + Payload []byte + } // DynamicStruct holds details about calls to the DynamicStruct method. DynamicStruct []struct { // Ctx is the ctx argument value. @@ -2283,13 +2293,14 @@ type EntityFactoryMock struct { TableName []struct { } } - lockBuilder sync.RWMutex - lockDynamicStruct sync.RWMutex - lockFromSchemaAndBuilder sync.RWMutex - lockName sync.RWMutex - lockNewEntity sync.RWMutex - lockSchema sync.RWMutex - lockTableName sync.RWMutex + lockBuilder sync.RWMutex + lockCreateEntityWithValues sync.RWMutex + lockDynamicStruct sync.RWMutex + lockFromSchemaAndBuilder sync.RWMutex + lockName sync.RWMutex + lockNewEntity sync.RWMutex + lockSchema sync.RWMutex + lockTableName sync.RWMutex } // Builder calls BuilderFunc. @@ -2323,6 +2334,41 @@ func (mock *EntityFactoryMock) BuilderCalls() []struct { return calls } +// CreateEntityWithValues calls CreateEntityWithValuesFunc. +func (mock *EntityFactoryMock) CreateEntityWithValues(ctx context.Context, payload []byte) (*model.ContentEntity, error) { + if mock.CreateEntityWithValuesFunc == nil { + panic("EntityFactoryMock.CreateEntityWithValuesFunc: method is nil but EntityFactory.CreateEntityWithValues was just called") + } + callInfo := struct { + Ctx context.Context + Payload []byte + }{ + Ctx: ctx, + Payload: payload, + } + mock.lockCreateEntityWithValues.Lock() + mock.calls.CreateEntityWithValues = append(mock.calls.CreateEntityWithValues, callInfo) + mock.lockCreateEntityWithValues.Unlock() + return mock.CreateEntityWithValuesFunc(ctx, payload) +} + +// CreateEntityWithValuesCalls gets all the calls that were made to CreateEntityWithValues. +// Check the length with: +// len(mockedEntityFactory.CreateEntityWithValuesCalls()) +func (mock *EntityFactoryMock) CreateEntityWithValuesCalls() []struct { + Ctx context.Context + Payload []byte +} { + var calls []struct { + Ctx context.Context + Payload []byte + } + mock.lockCreateEntityWithValues.RLock() + calls = mock.calls.CreateEntityWithValues + mock.lockCreateEntityWithValues.RUnlock() + return calls +} + // DynamicStruct calls DynamicStructFunc. func (mock *EntityFactoryMock) DynamicStruct(ctx context.Context) ds.DynamicStruct { if mock.DynamicStructFunc == nil { @@ -2501,153 +2547,3 @@ func (mock *EntityFactoryMock) TableNameCalls() []struct { mock.lockTableName.RUnlock() return calls } - -// Ensure, that EventDispatcherMock does implement model.EventDispatcher. -// If this is not the case, regenerate this file with moq. -var _ model.EventDispatcher = &EventDispatcherMock{} - -// EventDispatcherMock is a mock implementation of model.EventDispatcher. -// -// func TestSomethingThatUsesEventDispatcher(t *testing.T) { -// -// // make and configure a mocked model.EventDispatcher -// mockedEventDispatcher := &EventDispatcherMock{ -// AddSubscriberFunc: func(handler model.EventHandler) { -// panic("mock out the AddSubscriber method") -// }, -// DispatchFunc: func(ctx context.Context, event model.Event) { -// panic("mock out the Dispatch method") -// }, -// GetSubscribersFunc: func() []model.EventHandler { -// panic("mock out the GetSubscribers method") -// }, -// } -// -// // use mockedEventDispatcher in code that requires model.EventDispatcher -// // and then make assertions. -// -// } -type EventDispatcherMock struct { - // AddSubscriberFunc mocks the AddSubscriber method. - AddSubscriberFunc func(handler model.EventHandler) - - // DispatchFunc mocks the Dispatch method. - DispatchFunc func(ctx context.Context, event model.Event) - - // GetSubscribersFunc mocks the GetSubscribers method. - GetSubscribersFunc func() []model.EventHandler - - // calls tracks calls to the methods. - calls struct { - // AddSubscriber holds details about calls to the AddSubscriber method. - AddSubscriber []struct { - // Handler is the handler argument value. - Handler model.EventHandler - } - // Dispatch holds details about calls to the Dispatch method. - Dispatch []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // Event is the event argument value. - Event model.Event - } - // GetSubscribers holds details about calls to the GetSubscribers method. - GetSubscribers []struct { - } - } - lockAddSubscriber sync.RWMutex - lockDispatch sync.RWMutex - lockGetSubscribers sync.RWMutex -} - -// AddSubscriber calls AddSubscriberFunc. -func (mock *EventDispatcherMock) AddSubscriber(handler model.EventHandler) { - if mock.AddSubscriberFunc == nil { - panic("EventDispatcherMock.AddSubscriberFunc: method is nil but EventDispatcher.AddSubscriber was just called") - } - callInfo := struct { - Handler model.EventHandler - }{ - Handler: handler, - } - mock.lockAddSubscriber.Lock() - mock.calls.AddSubscriber = append(mock.calls.AddSubscriber, callInfo) - mock.lockAddSubscriber.Unlock() - mock.AddSubscriberFunc(handler) -} - -// AddSubscriberCalls gets all the calls that were made to AddSubscriber. -// Check the length with: -// len(mockedEventDispatcher.AddSubscriberCalls()) -func (mock *EventDispatcherMock) AddSubscriberCalls() []struct { - Handler model.EventHandler -} { - var calls []struct { - Handler model.EventHandler - } - mock.lockAddSubscriber.RLock() - calls = mock.calls.AddSubscriber - mock.lockAddSubscriber.RUnlock() - return calls -} - -// Dispatch calls DispatchFunc. -func (mock *EventDispatcherMock) Dispatch(ctx context.Context, event model.Event) { - if mock.DispatchFunc == nil { - panic("EventDispatcherMock.DispatchFunc: method is nil but EventDispatcher.Dispatch was just called") - } - callInfo := struct { - Ctx context.Context - Event model.Event - }{ - Ctx: ctx, - Event: event, - } - mock.lockDispatch.Lock() - mock.calls.Dispatch = append(mock.calls.Dispatch, callInfo) - mock.lockDispatch.Unlock() - mock.DispatchFunc(ctx, event) -} - -// DispatchCalls gets all the calls that were made to Dispatch. -// Check the length with: -// len(mockedEventDispatcher.DispatchCalls()) -func (mock *EventDispatcherMock) DispatchCalls() []struct { - Ctx context.Context - Event model.Event -} { - var calls []struct { - Ctx context.Context - Event model.Event - } - mock.lockDispatch.RLock() - calls = mock.calls.Dispatch - mock.lockDispatch.RUnlock() - return calls -} - -// GetSubscribers calls GetSubscribersFunc. -func (mock *EventDispatcherMock) GetSubscribers() []model.EventHandler { - if mock.GetSubscribersFunc == nil { - panic("EventDispatcherMock.GetSubscribersFunc: method is nil but EventDispatcher.GetSubscribers was just called") - } - callInfo := struct { - }{} - mock.lockGetSubscribers.Lock() - mock.calls.GetSubscribers = append(mock.calls.GetSubscribers, callInfo) - mock.lockGetSubscribers.Unlock() - return mock.GetSubscribersFunc() -} - -// GetSubscribersCalls gets all the calls that were made to GetSubscribers. -// Check the length with: -// len(mockedEventDispatcher.GetSubscribersCalls()) -func (mock *EventDispatcherMock) GetSubscribersCalls() []struct { -} { - var calls []struct { - } - mock.lockGetSubscribers.RLock() - calls = mock.calls.GetSubscribers - mock.lockGetSubscribers.RUnlock() - return calls -} diff --git a/end2end_test.go b/end2end_test.go index a177856c..d14393cb 100644 --- a/end2end_test.go +++ b/end2end_test.go @@ -7,8 +7,10 @@ import ( "encoding/json" "errors" "fmt" - "io" + "github.com/google/uuid" + "github.com/segmentio/ksuid" weosContext "github.com/wepala/weos/context" + "io" "mime/multipart" "net/http" "net/http/httptest" @@ -77,6 +79,7 @@ var contextWithValues context.Context var mockProjections map[string]*ProjectionMock var mockEventStores map[string]*EventRepositoryMock var expectedContentType string +var contentEntity map[string]interface{} type FilterProperties struct { Operator string @@ -115,6 +118,7 @@ func InitializeSuite(ctx *godog.TestSuiteContext) { limit = 0 token = "" expectedContentType = "" + contentEntity = map[string]interface{}{} result = api.ListApiResponse{} blogfixtures = []interface{}{} total, success, failed = 0, 0, 0 @@ -174,6 +178,7 @@ func reset(ctx context.Context, sc *godog.Scenario) (context.Context, error) { token = "" result = api.ListApiResponse{} errs = nil + contentEntity = map[string]interface{}{} header = make(http.Header) rec = httptest.NewRecorder() resp = nil @@ -471,6 +476,8 @@ func theIsCreated(contentType string, details *godog.Table) error { if rec.Result().StatusCode != http.StatusCreated { return fmt.Errorf("expected the status code to be '%d', got '%d'", http.StatusCreated, rec.Result().StatusCode) } + etag := rec.Header().Get("Etag") + weosID, _ := api.SplitEtag(etag) head := details.Rows[0].Cells compare := map[string]interface{}{} @@ -481,26 +488,23 @@ func theIsCreated(contentType string, details *godog.Table) error { } } - contentEntity := map[string]interface{}{} - var result *gorm.DB - //ETag would help with this - for key, value := range compare { - result = gormDB.Table(strings.Title(contentType)).Find(&contentEntity, key+" = ?", value) - if contentEntity != nil { - break - } - } - + contentEntity = map[string]interface{}{} + var resultdb *gorm.DB + resultdb = gormDB.Table(strings.Title(contentType)).Find(&contentEntity, "weos_id = ?", weosID) if contentEntity == nil { return fmt.Errorf("unexpected error finding content type in db") } - if result.Error != nil { - return fmt.Errorf("unexpected error finding content type: %s", result.Error) + if resultdb.Error != nil { + return fmt.Errorf("unexpected error finding content type: %s", resultdb.Error) } for key, value := range compare { if contentEntity[key] != value { + v, ok := value.(string) + if ok && v == "" && contentEntity[key] != nil { + continue + } return fmt.Errorf("expected %s %s %s, got %s", contentType, key, value, contentEntity[key]) } } @@ -556,6 +560,7 @@ func theSpecificationIs(arg1 *godog.DocString) error { } func theSpecificationIsParsed(arg1 string) error { + dropDB() //dropping the db is necessary for weos-1382 since the scenario has its own spec file it needs to overwite the background spec file openAPI = fmt.Sprintf(openAPI, dbconfig.Database, dbconfig.Driver, dbconfig.Host, dbconfig.Password, dbconfig.User, dbconfig.Port) tapi, err := api.New(openAPI) if err != nil { @@ -1732,6 +1737,27 @@ func theProjectionIsNotCalled(arg1 string) error { return fmt.Errorf("projection '%s' not found", arg1) } +func theIdShouldBeA(arg1, format string) error { + switch format { + case "uuid": + _, err := uuid.Parse(contentEntity["id"].(string)) + if err != nil { + fmt.Errorf("unexpected error parsing id as uuid: %s", err) + } + case "integer": + _, ok := contentEntity["id"].(int) + if !ok { + fmt.Errorf("unexpected error parsing id as int") + } + case "ksuid": + _, err := ksuid.Parse(contentEntity["id"].(string)) + if err != nil { + fmt.Errorf("unexpected error parsing id as ksuid: %s", err) + } + } + return nil +} + func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Before(reset) ctx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { @@ -1832,6 +1858,8 @@ func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^"([^"]*)" defines an event store "([^"]*)"$`, definesAnEventStore) ctx.Step(`^"([^"]*)" set the default event store as "([^"]*)"$`, setTheDefaultEventStoreAs) ctx.Step(`^the projection "([^"]*)" is not called$`, theProjectionIsNotCalled) + ctx.Step(`^the "([^"]*)" id should be a "([^"]*)"$`, theIdShouldBeA) + } func TestBDD(t *testing.T) { diff --git a/features/create-content.feature b/features/create-content.feature index 5af660c4..0e992a0f 100644 --- a/features/create-content.feature +++ b/features/create-content.feature @@ -281,3 +281,291 @@ Feature: Create content When the "Blog" is submitted Then an error should be returned + + @WEOS-1382 + Scenario: Automatically generate ksuid on create + + The id for an schema is automatically generated when the identifier is a single field and there is no value for that + field in the schema. The generation of the id should be based on the type and format of the identifier. + + Given the specification is + """ + openapi: 3.0.3 + info: + title: Blog Aggregator Rest API + version: 0.1.0 + description: REST API for interacting with the Blog Aggregator + servers: + - url: https://prod1.weos.sh/blog/dev + description: WeOS Dev + - url: https://prod1.weos.sh/blog/v1 + x-weos-config: + logger: + level: warn + report-caller: true + formatter: json + database: + database: "%s" + driver: "%s" + host: "%s" + password: "%s" + username: "%s" + port: %d + rest: + middleware: + - RequestID + - Recover + - ZapLogger + components: + schemas: + Blog: + type: object + properties: + id: + type: string + format: ksuid + title: + type: string + description: blog title + description: + type: string + required: + - title + x-identifier: + - id + paths: + /: + get: + operationId: Homepage + responses: + 200: + description: Application Homepage + /blog: + post: + operationId: Add Blog + requestBody: + description: Blog info that is submitted + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Blog" + application/xml: + schema: + $ref: "#/components/schemas/Blog" + responses: + 201: + description: Add Blog to Aggregator + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + 400: + description: Invalid blog submitted + """ + And the "OpenAPI 3.0" specification is parsed + And "Sojourner" is on the "Blog" create screen + And "Sojourner" enters "Some Blog" in the "title" field + And "Sojourner" enters "Some Description" in the "description" field + When the "Blog" is submitted + Then the "Blog" is created + | id | title | description | + | | Some Blog | Some Description | + And the "Blog" should have an id + And the "Blog" id should be a "ksuid" + + + @WEOS-1382 + Scenario: Automatically generate uuid on create + + If the id of a schema is a string and the format uuid is specified then generate a uuid + + + Given the specification is + """ + openapi: 3.0.3 + info: + title: Blog Aggregator Rest API + version: 0.1.0 + description: REST API for interacting with the Blog Aggregator + servers: + - url: https://prod1.weos.sh/blog/dev + description: WeOS Dev + - url: https://prod1.weos.sh/blog/v1 + x-weos-config: + logger: + level: warn + report-caller: true + formatter: json + database: + database: "%s" + driver: "%s" + host: "%s" + password: "%s" + username: "%s" + port: %d + rest: + middleware: + - RequestID + - Recover + - ZapLogger + components: + schemas: + Blog: + type: object + properties: + id: + type: string + format: uuid + title: + type: string + description: blog title + description: + type: string + required: + - title + x-identifier: + - id + paths: + /: + get: + operationId: Homepage + responses: + 200: + description: Application Homepage + /blog: + post: + operationId: Add Blog + requestBody: + description: Blog info that is submitted + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Blog" + application/xml: + schema: + $ref: "#/components/schemas/Blog" + responses: + 201: + description: Add Blog to Aggregator + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + 400: + description: Invalid blog submitted + """ + And the "OpenAPI 3.0" specification is parsed + And "Sojourner" is on the "Blog" create screen + And "Sojourner" enters "Some Blog" in the "title" field + And "Sojourner" enters "Some Description" in the "description" field + When the "Blog" is submitted + Then the "Blog" is created + | id | title | description | + | | Some Blog | Some Description | + And the "Blog" should have an id + And the "Blog" id should be a "uuid" + + @WEOS-1382 + Scenario: Automatically generate id on create + + If the id of a schema is an integer then use the auto increment functionality of the supporting database to increment + the integer. + + + Given the specification is + """ + openapi: 3.0.3 + info: + title: Blog Aggregator Rest API + version: 0.1.0 + description: REST API for interacting with the Blog Aggregator + servers: + - url: https://prod1.weos.sh/blog/dev + description: WeOS Dev + - url: https://prod1.weos.sh/blog/v1 + x-weos-config: + logger: + level: warn + report-caller: true + formatter: json + database: + database: "%s" + driver: "%s" + host: "%s" + password: "%s" + username: "%s" + port: %d + rest: + middleware: + - RequestID + - Recover + - ZapLogger + components: + schemas: + Blog: + type: object + properties: + custom_id: + type: integer + format: uint + title: + type: string + description: blog title + description: + type: string + required: + - title + x-identifier: + - custom_id + paths: + /: + get: + operationId: Homepage + responses: + 200: + description: Application Homepage + /blog: + post: + operationId: Add Blog + requestBody: + description: Blog info that is submitted + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Blog" + application/xml: + schema: + $ref: "#/components/schemas/Blog" + responses: + 201: + description: Add Blog to Aggregator + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + 400: + description: Invalid blog submitted + """ + And the "OpenAPI 3.0" specification is parsed + And "Sojourner" is on the "Blog" create screen + And "Sojourner" enters "Some Blog" in the "title" field + And "Sojourner" enters "Some Description" in the "description" field + When the "Blog" is submitted + Then the "Blog" is created + | custom_id | title | description | + | | Some Blog | Some Description | + And the "Blog" should have an id + And the "Blog" id should be a "integer" + diff --git a/go.mod b/go.mod index 378d0bfb..307834cf 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/coreos/go-oidc/v3 v3.1.0 github.com/cucumber/godog v0.12.2 github.com/getkin/kin-openapi v0.15.0 + github.com/google/uuid v1.3.0 // indirect github.com/jinzhu/inflection v1.0.0 github.com/kr/text v0.2.0 // indirect github.com/labstack/echo/v4 v4.5.0 diff --git a/mocks_test.go b/mocks_test.go index 5e57de4d..3f1ac65d 100644 --- a/mocks_test.go +++ b/mocks_test.go @@ -1,4 +1,4 @@ -//NOTE Copy of mocks in the model package +//This is a copy of model mocks // Code generated by moq; DO NOT EDIT. // github.com/matryer/moq @@ -8,7 +8,7 @@ import ( "context" "database/sql" "github.com/getkin/kin-openapi/openapi3" - "github.com/ompluscator/dynamic-struct" + ds "github.com/ompluscator/dynamic-struct" "github.com/wepala/weos/model" "gorm.io/gorm" "net/http" @@ -22,49 +22,49 @@ var _ model.EventRepository = &EventRepositoryMock{} // EventRepositoryMock is a mock implementation of model.EventRepository. // -// func TestSomethingThatUsesEventRepository(t *testing.T) { +// func TestSomethingThatUsesEventRepository(t *testing.T) { // -// // make and configure a mocked model.EventRepository -// mockedEventRepository := &EventRepositoryMock{ -// AddSubscriberFunc: func(handler model.EventHandler) { -// panic("mock out the AddSubscriber method") -// }, -// FlushFunc: func() error { -// panic("mock out the Flush method") -// }, -// GetAggregateSequenceNumberFunc: func(ID string) (int64, error) { -// panic("mock out the GetAggregateSequenceNumber method") -// }, -// GetByAggregateFunc: func(ID string) ([]*model.Event, error) { -// panic("mock out the GetByAggregate method") -// }, -// GetByAggregateAndSequenceRangeFunc: func(ID string, start int64, end int64) ([]*model.Event, error) { -// panic("mock out the GetByAggregateAndSequenceRange method") -// }, -// GetByAggregateAndTypeFunc: func(ID string, entityType string) ([]*model.Event, error) { -// panic("mock out the GetByAggregateAndType method") -// }, -// GetByEntityAndAggregateFunc: func(entityID string, entityType string, rootID string) ([]*model.Event, error) { -// panic("mock out the GetByEntityAndAggregate method") -// }, -// GetSubscribersFunc: func() ([]model.EventHandler, error) { -// panic("mock out the GetSubscribers method") -// }, -// MigrateFunc: func(ctx context.Context) error { -// panic("mock out the Migrate method") -// }, -// PersistFunc: func(ctxt context.Context, entity model.AggregateInterface) error { -// panic("mock out the Persist method") -// }, -// ReplayEventsFunc: func(ctxt context.Context, date time.Time, entityFactories map[string]model.EntityFactory, projection model.Projection) (int, int, int, []error) { -// panic("mock out the ReplayEvents method") -// }, -// } +// // make and configure a mocked model.EventRepository +// mockedEventRepository := &EventRepositoryMock{ +// AddSubscriberFunc: func(handler model.EventHandler) { +// panic("mock out the AddSubscriber method") +// }, +// FlushFunc: func() error { +// panic("mock out the Flush method") +// }, +// GetAggregateSequenceNumberFunc: func(ID string) (int64, error) { +// panic("mock out the GetAggregateSequenceNumber method") +// }, +// GetByAggregateFunc: func(ID string) ([]*model.Event, error) { +// panic("mock out the GetByAggregate method") +// }, +// GetByAggregateAndSequenceRangeFunc: func(ID string, start int64, end int64) ([]*model.Event, error) { +// panic("mock out the GetByAggregateAndSequenceRange method") +// }, +// GetByAggregateAndTypeFunc: func(ID string, entityType string) ([]*model.Event, error) { +// panic("mock out the GetByAggregateAndType method") +// }, +// GetByEntityAndAggregateFunc: func(entityID string, entityType string, rootID string) ([]*model.Event, error) { +// panic("mock out the GetByEntityAndAggregate method") +// }, +// GetSubscribersFunc: func() ([]model.EventHandler, error) { +// panic("mock out the GetSubscribers method") +// }, +// MigrateFunc: func(ctx context.Context) error { +// panic("mock out the Migrate method") +// }, +// PersistFunc: func(ctxt context.Context, entity model.AggregateInterface) error { +// panic("mock out the Persist method") +// }, +// ReplayEventsFunc: func(ctxt context.Context, date time.Time, entityFactories map[string]model.EntityFactory, projection model.Projection) (int, int, int, []error) { +// panic("mock out the ReplayEvents method") +// }, +// } // -// // use mockedEventRepository in code that requires model.EventRepository -// // and then make assertions. +// // use mockedEventRepository in code that requires model.EventRepository +// // and then make assertions. // -// } +// } type EventRepositoryMock struct { // AddSubscriberFunc mocks the AddSubscriber method. AddSubscriberFunc func(handler model.EventHandler) @@ -557,76 +557,37 @@ var _ model.Projection = &ProjectionMock{} // ProjectionMock is a mock implementation of model.Projection. // -// func TestSomethingThatUsesProjection(t *testing.T) { -// -// // make and configure a mocked model.Projection -// mockedProjection := &ProjectionMock{ -// GetByEntityIDFunc: func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { -// panic("mock out the GetByEntityID method") -// }, -// GetByIdentifiersFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { -// panic("mock out the GetByIdentifiers method") -// }, -// GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { -// panic("mock out the GetByKey method") -// }, -// GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page int, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { -// panic("mock out the GetContentEntities method") -// }, -// GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { -// panic("mock out the GetContentEntity method") -// }, -// GetEventHandlerFunc: func() model.EventHandler { -// panic("mock out the GetEventHandler method") -// }, -// MigrateFunc: func(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error { -// panic("mock out the Migrate method") -// }, -// } -// -// // use mockedProjection in code that requires model.Projection -// // and then make assertions. -// -// } -// Ensure, that ProjectionMock does implement model.Projection. -// If this is not the case, regenerate this file with moq. -// Ensure, that ProjectionMock does implement model.Projection. -// If this is not the case, regenerate this file with moq. -var _ model.Projection = &ProjectionMock{} - -// ProjectionMock is a mock implementation of model.Projection. -// -// func TestSomethingThatUsesProjection(t *testing.T) { +// func TestSomethingThatUsesProjection(t *testing.T) { // -// // make and configure a mocked model.Projection -// mockedProjection := &ProjectionMock{ -// GetByEntityIDFunc: func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { -// panic("mock out the GetByEntityID method") -// }, -// GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { -// panic("mock out the GetByKey method") -// }, -// GetByPropertiesFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { -// panic("mock out the GetByProperties method") -// }, -// GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page int, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { -// panic("mock out the GetContentEntities method") -// }, -// GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { -// panic("mock out the GetContentEntity method") -// }, -// GetEventHandlerFunc: func() model.EventHandler { -// panic("mock out the GetEventHandler method") -// }, -// MigrateFunc: func(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error { -// panic("mock out the Migrate method") -// }, -// } +// // make and configure a mocked model.Projection +// mockedProjection := &ProjectionMock{ +// GetByEntityIDFunc: func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { +// panic("mock out the GetByEntityID method") +// }, +// GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { +// panic("mock out the GetByKey method") +// }, +// GetByPropertiesFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { +// panic("mock out the GetByProperties method") +// }, +// GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page int, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { +// panic("mock out the GetContentEntities method") +// }, +// GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { +// panic("mock out the GetContentEntity method") +// }, +// GetEventHandlerFunc: func() model.EventHandler { +// panic("mock out the GetEventHandler method") +// }, +// MigrateFunc: func(ctx context.Context, builders map[string]ds.Builder, deletedFields map[string][]string) error { +// panic("mock out the Migrate method") +// }, +// } // -// // use mockedProjection in code that requires model.Projection -// // and then make assertions. +// // use mockedProjection in code that requires model.Projection +// // and then make assertions. // -// } +// } type ProjectionMock struct { // GetByEntityIDFunc mocks the GetByEntityID method. GetByEntityIDFunc func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) @@ -647,7 +608,7 @@ type ProjectionMock struct { GetEventHandlerFunc func() model.EventHandler // MigrateFunc mocks the Migrate method. - MigrateFunc func(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error + MigrateFunc func(ctx context.Context, builders map[string]ds.Builder, deletedFields map[string][]string) error // calls tracks calls to the methods. calls struct { @@ -712,7 +673,7 @@ type ProjectionMock struct { // Ctx is the ctx argument value. Ctx context.Context // Builders is the builders argument value. - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder // DeletedFields is the deletedFields argument value. DeletedFields map[string][]string } @@ -964,13 +925,13 @@ func (mock *ProjectionMock) GetEventHandlerCalls() []struct { } // Migrate calls MigrateFunc. -func (mock *ProjectionMock) Migrate(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error { +func (mock *ProjectionMock) Migrate(ctx context.Context, builders map[string]ds.Builder, deletedFields map[string][]string) error { if mock.MigrateFunc == nil { panic("ProjectionMock.MigrateFunc: method is nil but Projection.Migrate was just called") } callInfo := struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder DeletedFields map[string][]string }{ Ctx: ctx, @@ -988,12 +949,12 @@ func (mock *ProjectionMock) Migrate(ctx context.Context, builders map[string]dyn // len(mockedProjection.MigrateCalls()) func (mock *ProjectionMock) MigrateCalls() []struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder DeletedFields map[string][]string } { var calls []struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder DeletedFields map[string][]string } mock.lockMigrate.RLock() @@ -1008,52 +969,52 @@ var _ model.Log = &LogMock{} // LogMock is a mock implementation of model.Log. // -// func TestSomethingThatUsesLog(t *testing.T) { +// func TestSomethingThatUsesLog(t *testing.T) { // -// // make and configure a mocked model.Log -// mockedLog := &LogMock{ -// DebugFunc: func(args ...interface{}) { -// panic("mock out the Debug method") -// }, -// DebugfFunc: func(format string, args ...interface{}) { -// panic("mock out the Debugf method") -// }, -// ErrorFunc: func(args ...interface{}) { -// panic("mock out the Error method") -// }, -// ErrorfFunc: func(format string, args ...interface{}) { -// panic("mock out the Errorf method") -// }, -// FatalFunc: func(args ...interface{}) { -// panic("mock out the Fatal method") -// }, -// FatalfFunc: func(format string, args ...interface{}) { -// panic("mock out the Fatalf method") -// }, -// InfoFunc: func(args ...interface{}) { -// panic("mock out the Info method") -// }, -// InfofFunc: func(format string, args ...interface{}) { -// panic("mock out the Infof method") -// }, -// PanicFunc: func(args ...interface{}) { -// panic("mock out the Panic method") -// }, -// PanicfFunc: func(format string, args ...interface{}) { -// panic("mock out the Panicf method") -// }, -// PrintFunc: func(args ...interface{}) { -// panic("mock out the Print method") -// }, -// PrintfFunc: func(format string, args ...interface{}) { -// panic("mock out the Printf method") -// }, -// } +// // make and configure a mocked model.Log +// mockedLog := &LogMock{ +// DebugFunc: func(args ...interface{}) { +// panic("mock out the Debug method") +// }, +// DebugfFunc: func(format string, args ...interface{}) { +// panic("mock out the Debugf method") +// }, +// ErrorFunc: func(args ...interface{}) { +// panic("mock out the Error method") +// }, +// ErrorfFunc: func(format string, args ...interface{}) { +// panic("mock out the Errorf method") +// }, +// FatalFunc: func(args ...interface{}) { +// panic("mock out the Fatal method") +// }, +// FatalfFunc: func(format string, args ...interface{}) { +// panic("mock out the Fatalf method") +// }, +// InfoFunc: func(args ...interface{}) { +// panic("mock out the Info method") +// }, +// InfofFunc: func(format string, args ...interface{}) { +// panic("mock out the Infof method") +// }, +// PanicFunc: func(args ...interface{}) { +// panic("mock out the Panic method") +// }, +// PanicfFunc: func(format string, args ...interface{}) { +// panic("mock out the Panicf method") +// }, +// PrintFunc: func(args ...interface{}) { +// panic("mock out the Print method") +// }, +// PrintfFunc: func(format string, args ...interface{}) { +// panic("mock out the Printf method") +// }, +// } // -// // use mockedLog in code that requires model.Log -// // and then make assertions. +// // use mockedLog in code that requires model.Log +// // and then make assertions. // -// } +// } type LogMock struct { // DebugFunc mocks the Debug method. DebugFunc func(args ...interface{}) @@ -1582,25 +1543,25 @@ var _ model.CommandDispatcher = &CommandDispatcherMock{} // CommandDispatcherMock is a mock implementation of model.CommandDispatcher. // -// func TestSomethingThatUsesCommandDispatcher(t *testing.T) { +// func TestSomethingThatUsesCommandDispatcher(t *testing.T) { // -// // make and configure a mocked model.CommandDispatcher -// mockedCommandDispatcher := &CommandDispatcherMock{ -// AddSubscriberFunc: func(command *model.Command, handler model.CommandHandler) map[string][]model.CommandHandler { -// panic("mock out the AddSubscriber method") -// }, -// DispatchFunc: func(ctx context.Context, command *model.Command, eventStore model.EventRepository, projection model.Projection, logger model.Log) error { -// panic("mock out the Dispatch method") -// }, -// GetSubscribersFunc: func() map[string][]model.CommandHandler { -// panic("mock out the GetSubscribers method") -// }, -// } +// // make and configure a mocked model.CommandDispatcher +// mockedCommandDispatcher := &CommandDispatcherMock{ +// AddSubscriberFunc: func(command *model.Command, handler model.CommandHandler) map[string][]model.CommandHandler { +// panic("mock out the AddSubscriber method") +// }, +// DispatchFunc: func(ctx context.Context, command *model.Command, eventStore model.EventRepository, projection model.Projection, logger model.Log) error { +// panic("mock out the Dispatch method") +// }, +// GetSubscribersFunc: func() map[string][]model.CommandHandler { +// panic("mock out the GetSubscribers method") +// }, +// } // -// // use mockedCommandDispatcher in code that requires model.CommandDispatcher -// // and then make assertions. +// // use mockedCommandDispatcher in code that requires model.CommandDispatcher +// // and then make assertions. // -// } +// } type CommandDispatcherMock struct { // AddSubscriberFunc mocks the AddSubscriber method. AddSubscriberFunc func(command *model.Command, handler model.CommandHandler) map[string][]model.CommandHandler @@ -1756,52 +1717,52 @@ var _ model.Service = &ServiceMock{} // ServiceMock is a mock implementation of model.Service. // -// func TestSomethingThatUsesService(t *testing.T) { +// func TestSomethingThatUsesService(t *testing.T) { // -// // make and configure a mocked model.Service -// mockedService := &ServiceMock{ -// AddProjectionFunc: func(projection model.Projection) error { -// panic("mock out the AddProjection method") -// }, -// ConfigFunc: func() *model.ServiceConfig { -// panic("mock out the Config method") -// }, -// DBFunc: func() *gorm.DB { -// panic("mock out the DB method") -// }, -// DBConnectionFunc: func() *sql.DB { -// panic("mock out the DBConnection method") -// }, -// DispatcherFunc: func() model.CommandDispatcher { -// panic("mock out the Dispatcher method") -// }, -// EventRepositoryFunc: func() model.EventRepository { -// panic("mock out the EventRepository method") -// }, -// HTTPClientFunc: func() *http.Client { -// panic("mock out the HTTPClient method") -// }, -// IDFunc: func() string { -// panic("mock out the ID method") -// }, -// LoggerFunc: func() model.Log { -// panic("mock out the Logger method") -// }, -// MigrateFunc: func(ctx context.Context, builders map[string]dynamicstruct.Builder) error { -// panic("mock out the Migrate method") -// }, -// ProjectionsFunc: func() []model.Projection { -// panic("mock out the Projections method") -// }, -// TitleFunc: func() string { -// panic("mock out the Title method") -// }, -// } +// // make and configure a mocked model.Service +// mockedService := &ServiceMock{ +// AddProjectionFunc: func(projection model.Projection) error { +// panic("mock out the AddProjection method") +// }, +// ConfigFunc: func() *model.ServiceConfig { +// panic("mock out the Config method") +// }, +// DBFunc: func() *gorm.DB { +// panic("mock out the DB method") +// }, +// DBConnectionFunc: func() *sql.DB { +// panic("mock out the DBConnection method") +// }, +// DispatcherFunc: func() model.CommandDispatcher { +// panic("mock out the Dispatcher method") +// }, +// EventRepositoryFunc: func() model.EventRepository { +// panic("mock out the EventRepository method") +// }, +// HTTPClientFunc: func() *http.Client { +// panic("mock out the HTTPClient method") +// }, +// IDFunc: func() string { +// panic("mock out the ID method") +// }, +// LoggerFunc: func() model.Log { +// panic("mock out the Logger method") +// }, +// MigrateFunc: func(ctx context.Context, builders map[string]ds.Builder) error { +// panic("mock out the Migrate method") +// }, +// ProjectionsFunc: func() []model.Projection { +// panic("mock out the Projections method") +// }, +// TitleFunc: func() string { +// panic("mock out the Title method") +// }, +// } // -// // use mockedService in code that requires model.Service -// // and then make assertions. +// // use mockedService in code that requires model.Service +// // and then make assertions. // -// } +// } type ServiceMock struct { // AddProjectionFunc mocks the AddProjection method. AddProjectionFunc func(projection model.Projection) error @@ -1831,7 +1792,7 @@ type ServiceMock struct { LoggerFunc func() model.Log // MigrateFunc mocks the Migrate method. - MigrateFunc func(ctx context.Context, builders map[string]dynamicstruct.Builder) error + MigrateFunc func(ctx context.Context, builders map[string]ds.Builder) error // ProjectionsFunc mocks the Projections method. ProjectionsFunc func() []model.Projection @@ -1875,7 +1836,7 @@ type ServiceMock struct { // Ctx is the ctx argument value. Ctx context.Context // Builders is the builders argument value. - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder } // Projections holds details about calls to the Projections method. Projections []struct { @@ -2138,13 +2099,13 @@ func (mock *ServiceMock) LoggerCalls() []struct { } // Migrate calls MigrateFunc. -func (mock *ServiceMock) Migrate(ctx context.Context, builders map[string]dynamicstruct.Builder) error { +func (mock *ServiceMock) Migrate(ctx context.Context, builders map[string]ds.Builder) error { if mock.MigrateFunc == nil { panic("ServiceMock.MigrateFunc: method is nil but Service.Migrate was just called") } callInfo := struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder }{ Ctx: ctx, Builders: builders, @@ -2160,11 +2121,11 @@ func (mock *ServiceMock) Migrate(ctx context.Context, builders map[string]dynami // len(mockedService.MigrateCalls()) func (mock *ServiceMock) MigrateCalls() []struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder } { var calls []struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder } mock.lockMigrate.RLock() calls = mock.calls.Migrate @@ -2230,46 +2191,52 @@ var _ model.EntityFactory = &EntityFactoryMock{} // EntityFactoryMock is a mock implementation of model.EntityFactory. // -// func TestSomethingThatUsesEntityFactory(t *testing.T) { +// func TestSomethingThatUsesEntityFactory(t *testing.T) { // -// // make and configure a mocked model.EntityFactory -// mockedEntityFactory := &EntityFactoryMock{ -// BuilderFunc: func(ctx context.Context) dynamicstruct.Builder { -// panic("mock out the Builder method") -// }, -// DynamicStructFunc: func(ctx context.Context) dynamicstruct.DynamicStruct { -// panic("mock out the DynamicStruct method") -// }, -// FromSchemaAndBuilderFunc: func(in1 string, in2 *openapi3.Schema, in3 dynamicstruct.Builder) model.EntityFactory { -// panic("mock out the FromSchemaAndBuilder method") -// }, -// NameFunc: func() string { -// panic("mock out the Name method") -// }, -// NewEntityFunc: func(ctx context.Context) (*model.ContentEntity, error) { -// panic("mock out the NewEntity method") -// }, -// SchemaFunc: func() *openapi3.Schema { -// panic("mock out the Schema method") -// }, -// TableNameFunc: func() string { -// panic("mock out the TableName method") -// }, -// } +// // make and configure a mocked model.EntityFactory +// mockedEntityFactory := &EntityFactoryMock{ +// BuilderFunc: func(ctx context.Context) ds.Builder { +// panic("mock out the Builder method") +// }, +// CreateEntityWithValuesFunc: func(ctx context.Context, payload []byte) (*model.ContentEntity, error) { +// panic("mock out the CreateEntityWithValues method") +// }, +// DynamicStructFunc: func(ctx context.Context) ds.DynamicStruct { +// panic("mock out the DynamicStruct method") +// }, +// FromSchemaAndBuilderFunc: func(s string, schema *openapi3.Schema, builder ds.Builder) model.EntityFactory { +// panic("mock out the FromSchemaAndBuilder method") +// }, +// NameFunc: func() string { +// panic("mock out the Name method") +// }, +// NewEntityFunc: func(ctx context.Context) (*model.ContentEntity, error) { +// panic("mock out the NewEntity method") +// }, +// SchemaFunc: func() *openapi3.Schema { +// panic("mock out the Schema method") +// }, +// TableNameFunc: func() string { +// panic("mock out the TableName method") +// }, +// } // -// // use mockedEntityFactory in code that requires model.EntityFactory -// // and then make assertions. +// // use mockedEntityFactory in code that requires model.EntityFactory +// // and then make assertions. // -// } +// } type EntityFactoryMock struct { // BuilderFunc mocks the Builder method. - BuilderFunc func(ctx context.Context) dynamicstruct.Builder + BuilderFunc func(ctx context.Context) ds.Builder + + // CreateEntityWithValuesFunc mocks the CreateEntityWithValues method. + CreateEntityWithValuesFunc func(ctx context.Context, payload []byte) (*model.ContentEntity, error) // DynamicStructFunc mocks the DynamicStruct method. - DynamicStructFunc func(ctx context.Context) dynamicstruct.DynamicStruct + DynamicStructFunc func(ctx context.Context) ds.DynamicStruct // FromSchemaAndBuilderFunc mocks the FromSchemaAndBuilder method. - FromSchemaAndBuilderFunc func(in1 string, in2 *openapi3.Schema, in3 dynamicstruct.Builder) model.EntityFactory + FromSchemaAndBuilderFunc func(s string, schema *openapi3.Schema, builder ds.Builder) model.EntityFactory // NameFunc mocks the Name method. NameFunc func() string @@ -2290,6 +2257,13 @@ type EntityFactoryMock struct { // Ctx is the ctx argument value. Ctx context.Context } + // CreateEntityWithValues holds details about calls to the CreateEntityWithValues method. + CreateEntityWithValues []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Payload is the payload argument value. + Payload []byte + } // DynamicStruct holds details about calls to the DynamicStruct method. DynamicStruct []struct { // Ctx is the ctx argument value. @@ -2297,12 +2271,12 @@ type EntityFactoryMock struct { } // FromSchemaAndBuilder holds details about calls to the FromSchemaAndBuilder method. FromSchemaAndBuilder []struct { - // In1 is the in1 argument value. - In1 string - // In2 is the in2 argument value. - In2 *openapi3.Schema - // In3 is the in3 argument value. - In3 dynamicstruct.Builder + // S is the s argument value. + S string + // Schema is the schema argument value. + Schema *openapi3.Schema + // Builder is the builder argument value. + Builder ds.Builder } // Name holds details about calls to the Name method. Name []struct { @@ -2319,17 +2293,18 @@ type EntityFactoryMock struct { TableName []struct { } } - lockBuilder sync.RWMutex - lockDynamicStruct sync.RWMutex - lockFromSchemaAndBuilder sync.RWMutex - lockName sync.RWMutex - lockNewEntity sync.RWMutex - lockSchema sync.RWMutex - lockTableName sync.RWMutex + lockBuilder sync.RWMutex + lockCreateEntityWithValues sync.RWMutex + lockDynamicStruct sync.RWMutex + lockFromSchemaAndBuilder sync.RWMutex + lockName sync.RWMutex + lockNewEntity sync.RWMutex + lockSchema sync.RWMutex + lockTableName sync.RWMutex } // Builder calls BuilderFunc. -func (mock *EntityFactoryMock) Builder(ctx context.Context) dynamicstruct.Builder { +func (mock *EntityFactoryMock) Builder(ctx context.Context) ds.Builder { if mock.BuilderFunc == nil { panic("EntityFactoryMock.BuilderFunc: method is nil but EntityFactory.Builder was just called") } @@ -2359,8 +2334,43 @@ func (mock *EntityFactoryMock) BuilderCalls() []struct { return calls } +// CreateEntityWithValues calls CreateEntityWithValuesFunc. +func (mock *EntityFactoryMock) CreateEntityWithValues(ctx context.Context, payload []byte) (*model.ContentEntity, error) { + if mock.CreateEntityWithValuesFunc == nil { + panic("EntityFactoryMock.CreateEntityWithValuesFunc: method is nil but EntityFactory.CreateEntityWithValues was just called") + } + callInfo := struct { + Ctx context.Context + Payload []byte + }{ + Ctx: ctx, + Payload: payload, + } + mock.lockCreateEntityWithValues.Lock() + mock.calls.CreateEntityWithValues = append(mock.calls.CreateEntityWithValues, callInfo) + mock.lockCreateEntityWithValues.Unlock() + return mock.CreateEntityWithValuesFunc(ctx, payload) +} + +// CreateEntityWithValuesCalls gets all the calls that were made to CreateEntityWithValues. +// Check the length with: +// len(mockedEntityFactory.CreateEntityWithValuesCalls()) +func (mock *EntityFactoryMock) CreateEntityWithValuesCalls() []struct { + Ctx context.Context + Payload []byte +} { + var calls []struct { + Ctx context.Context + Payload []byte + } + mock.lockCreateEntityWithValues.RLock() + calls = mock.calls.CreateEntityWithValues + mock.lockCreateEntityWithValues.RUnlock() + return calls +} + // DynamicStruct calls DynamicStructFunc. -func (mock *EntityFactoryMock) DynamicStruct(ctx context.Context) dynamicstruct.DynamicStruct { +func (mock *EntityFactoryMock) DynamicStruct(ctx context.Context) ds.DynamicStruct { if mock.DynamicStructFunc == nil { panic("EntityFactoryMock.DynamicStructFunc: method is nil but EntityFactory.DynamicStruct was just called") } @@ -2391,37 +2401,37 @@ func (mock *EntityFactoryMock) DynamicStructCalls() []struct { } // FromSchemaAndBuilder calls FromSchemaAndBuilderFunc. -func (mock *EntityFactoryMock) FromSchemaAndBuilder(in1 string, in2 *openapi3.Schema, in3 dynamicstruct.Builder) model.EntityFactory { +func (mock *EntityFactoryMock) FromSchemaAndBuilder(s string, schema *openapi3.Schema, builder ds.Builder) model.EntityFactory { if mock.FromSchemaAndBuilderFunc == nil { panic("EntityFactoryMock.FromSchemaAndBuilderFunc: method is nil but EntityFactory.FromSchemaAndBuilder was just called") } callInfo := struct { - In1 string - In2 *openapi3.Schema - In3 dynamicstruct.Builder + S string + Schema *openapi3.Schema + Builder ds.Builder }{ - In1: in1, - In2: in2, - In3: in3, + S: s, + Schema: schema, + Builder: builder, } mock.lockFromSchemaAndBuilder.Lock() mock.calls.FromSchemaAndBuilder = append(mock.calls.FromSchemaAndBuilder, callInfo) mock.lockFromSchemaAndBuilder.Unlock() - return mock.FromSchemaAndBuilderFunc(in1, in2, in3) + return mock.FromSchemaAndBuilderFunc(s, schema, builder) } // FromSchemaAndBuilderCalls gets all the calls that were made to FromSchemaAndBuilder. // Check the length with: // len(mockedEntityFactory.FromSchemaAndBuilderCalls()) func (mock *EntityFactoryMock) FromSchemaAndBuilderCalls() []struct { - In1 string - In2 *openapi3.Schema - In3 dynamicstruct.Builder + S string + Schema *openapi3.Schema + Builder ds.Builder } { var calls []struct { - In1 string - In2 *openapi3.Schema - In3 dynamicstruct.Builder + S string + Schema *openapi3.Schema + Builder ds.Builder } mock.lockFromSchemaAndBuilder.RLock() calls = mock.calls.FromSchemaAndBuilder @@ -2537,153 +2547,3 @@ func (mock *EntityFactoryMock) TableNameCalls() []struct { mock.lockTableName.RUnlock() return calls } - -// Ensure, that EventDispatcherMock does implement model.EventDispatcher. -// If this is not the case, regenerate this file with moq. -var _ model.EventDispatcher = &EventDispatcherMock{} - -// EventDispatcherMock is a mock implementation of model.EventDispatcher. -// -// func TestSomethingThatUsesEventDispatcher(t *testing.T) { -// -// // make and configure a mocked model.EventDispatcher -// mockedEventDispatcher := &EventDispatcherMock{ -// AddSubscriberFunc: func(handler model.EventHandler) { -// panic("mock out the AddSubscriber method") -// }, -// DispatchFunc: func(ctx context.Context, event model.Event) { -// panic("mock out the Dispatch method") -// }, -// GetSubscribersFunc: func() []model.EventHandler { -// panic("mock out the GetSubscribers method") -// }, -// } -// -// // use mockedEventDispatcher in code that requires model.EventDispatcher -// // and then make assertions. -// -// } -type EventDispatcherMock struct { - // AddSubscriberFunc mocks the AddSubscriber method. - AddSubscriberFunc func(handler model.EventHandler) - - // DispatchFunc mocks the Dispatch method. - DispatchFunc func(ctx context.Context, event model.Event) - - // GetSubscribersFunc mocks the GetSubscribers method. - GetSubscribersFunc func() []model.EventHandler - - // calls tracks calls to the methods. - calls struct { - // AddSubscriber holds details about calls to the AddSubscriber method. - AddSubscriber []struct { - // Handler is the handler argument value. - Handler model.EventHandler - } - // Dispatch holds details about calls to the Dispatch method. - Dispatch []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // Event is the event argument value. - Event model.Event - } - // GetSubscribers holds details about calls to the GetSubscribers method. - GetSubscribers []struct { - } - } - lockAddSubscriber sync.RWMutex - lockDispatch sync.RWMutex - lockGetSubscribers sync.RWMutex -} - -// AddSubscriber calls AddSubscriberFunc. -func (mock *EventDispatcherMock) AddSubscriber(handler model.EventHandler) { - if mock.AddSubscriberFunc == nil { - panic("EventDispatcherMock.AddSubscriberFunc: method is nil but EventDispatcher.AddSubscriber was just called") - } - callInfo := struct { - Handler model.EventHandler - }{ - Handler: handler, - } - mock.lockAddSubscriber.Lock() - mock.calls.AddSubscriber = append(mock.calls.AddSubscriber, callInfo) - mock.lockAddSubscriber.Unlock() - mock.AddSubscriberFunc(handler) -} - -// AddSubscriberCalls gets all the calls that were made to AddSubscriber. -// Check the length with: -// len(mockedEventDispatcher.AddSubscriberCalls()) -func (mock *EventDispatcherMock) AddSubscriberCalls() []struct { - Handler model.EventHandler -} { - var calls []struct { - Handler model.EventHandler - } - mock.lockAddSubscriber.RLock() - calls = mock.calls.AddSubscriber - mock.lockAddSubscriber.RUnlock() - return calls -} - -// Dispatch calls DispatchFunc. -func (mock *EventDispatcherMock) Dispatch(ctx context.Context, event model.Event) { - if mock.DispatchFunc == nil { - panic("EventDispatcherMock.DispatchFunc: method is nil but EventDispatcher.Dispatch was just called") - } - callInfo := struct { - Ctx context.Context - Event model.Event - }{ - Ctx: ctx, - Event: event, - } - mock.lockDispatch.Lock() - mock.calls.Dispatch = append(mock.calls.Dispatch, callInfo) - mock.lockDispatch.Unlock() - mock.DispatchFunc(ctx, event) -} - -// DispatchCalls gets all the calls that were made to Dispatch. -// Check the length with: -// len(mockedEventDispatcher.DispatchCalls()) -func (mock *EventDispatcherMock) DispatchCalls() []struct { - Ctx context.Context - Event model.Event -} { - var calls []struct { - Ctx context.Context - Event model.Event - } - mock.lockDispatch.RLock() - calls = mock.calls.Dispatch - mock.lockDispatch.RUnlock() - return calls -} - -// GetSubscribers calls GetSubscribersFunc. -func (mock *EventDispatcherMock) GetSubscribers() []model.EventHandler { - if mock.GetSubscribersFunc == nil { - panic("EventDispatcherMock.GetSubscribersFunc: method is nil but EventDispatcher.GetSubscribers was just called") - } - callInfo := struct { - }{} - mock.lockGetSubscribers.Lock() - mock.calls.GetSubscribers = append(mock.calls.GetSubscribers, callInfo) - mock.lockGetSubscribers.Unlock() - return mock.GetSubscribersFunc() -} - -// GetSubscribersCalls gets all the calls that were made to GetSubscribers. -// Check the length with: -// len(mockedEventDispatcher.GetSubscribersCalls()) -func (mock *EventDispatcherMock) GetSubscribersCalls() []struct { -} { - var calls []struct { - } - mock.lockGetSubscribers.RLock() - calls = mock.calls.GetSubscribers - mock.lockGetSubscribers.RUnlock() - return calls -} diff --git a/model/content_entity.go b/model/content_entity.go index 44b52d50..f298b35f 100644 --- a/model/content_entity.go +++ b/model/content_entity.go @@ -4,7 +4,9 @@ import ( "encoding/json" "fmt" "github.com/getkin/kin-openapi/openapi3" + "github.com/google/uuid" ds "github.com/ompluscator/dynamic-struct" + "github.com/segmentio/ksuid" weosContext "github.com/wepala/weos/context" utils "github.com/wepala/weos/utils" "golang.org/x/net/context" @@ -439,6 +441,28 @@ func (w *ContentEntity) FromSchemaAndBuilder(ctx context.Context, ref *openapi3. return w, nil } +func (w *ContentEntity) Init(ctx context.Context, payload json.RawMessage) (*ContentEntity, error) { + err := w.SetValueFromPayload(ctx, payload) + if err != nil { + return nil, err + } + err = w.GenerateID(payload) + if err != nil { + return nil, err + } + eventPayload, err := json.Marshal(w.Property) + if err != nil { + return nil, NewDomainError("error marshalling event payload", w.Schema.Title, w.ID, err) + } + event := NewEntityEvent(CREATE_EVENT, w, w.ID, eventPayload) + if err != nil { + return nil, err + } + w.NewChange(event) + err = w.ApplyEvents([]*Event{event}) + return w, err +} + //Deprecated: this duplicates the work of making the dynamic struct builder. Use FromSchemaAndBuilder instead (this is used by the EntityFactory) //FromSchema builds properties from the schema func (w *ContentEntity) FromSchema(ctx context.Context, ref *openapi3.Schema) (*ContentEntity, error) { @@ -749,3 +773,53 @@ func (w *ContentEntity) UnmarshalJSON(data []byte) error { err = json.Unmarshal(data, &w.Property) return err } + +//GenerateID adds a generated id to the payload based on the schema +func (w *ContentEntity) GenerateID(payload []byte) error { + tentity := make(map[string]interface{}) + properties := w.Schema.ExtensionProps.Extensions["x-identifier"] + err := json.Unmarshal(payload, w) + if err != nil { + return err + } + if properties != nil { + propArray := []string{} + err = json.Unmarshal(properties.(json.RawMessage), &propArray) + if err != nil { + return fmt.Errorf("unexpected error unmarshalling identifiers: %s", err) + } + if len(propArray) == 1 { // if there is only one x-identifier specified then it should auto generate the identifier + property := propArray[0] + if w.Schema.Properties[property].Value.Type == "string" && w.GetString(property) == "" { + if w.Schema.Properties[property].Value.Format != "" { //if the format is specified + switch w.Schema.Properties[property].Value.Format { + case "ksuid": + tentity[property] = ksuid.New().String() + case "uuid": + tentity[property] = uuid.NewString() + default: + errr := "unexpected error: fail to generate identifier " + property + " since the format " + w.Schema.Properties[property].Value.Format + " is not supported" + return NewDomainError(errr, w.Schema.Title, "", nil) + } + } else { //if the format is not specified + errr := "unexpected error: fail to generate identifier " + property + " since the format was not specified" + return NewDomainError(errr, w.Schema.Title, "", nil) + } + } else if w.Schema.Properties[property].Value.Type == "integer" { + reader := ds.NewReader(w.Property) + if w.Schema.Properties[property].Value.Format == "" && reader.GetField(strings.Title(property)).PointerInt() == nil { + errr := "unexpected error: fail to generate identifier " + property + " since the format was not specified" + return NewDomainError(errr, w.Schema.Title, "", nil) + + } + } + + } + } + generatedIdentifier, err := json.Marshal(tentity) + if err != nil { + return err + } + + return json.Unmarshal(generatedIdentifier, w) +} diff --git a/model/content_entity_test.go b/model/content_entity_test.go index 00b038ac..5332adf2 100644 --- a/model/content_entity_test.go +++ b/model/content_entity_test.go @@ -2,8 +2,11 @@ package model_test import ( "encoding/json" + "fmt" "github.com/getkin/kin-openapi/openapi3" + "github.com/google/uuid" "github.com/labstack/echo/v4" + "github.com/segmentio/ksuid" weosContext "github.com/wepala/weos/context" "github.com/wepala/weos/controllers/rest" "github.com/wepala/weos/model" @@ -42,6 +45,47 @@ func TestContentEntity_FromSchema(t *testing.T) { } } +func TestContentEntity_Init(t *testing.T) { + //load open api spec + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("../controllers/rest/fixtures/blog.yaml") + if err != nil { + t.Fatalf("unexpected error occured '%s'", err) + } + var contentType string + var contentTypeSchema *openapi3.SchemaRef + contentType = "Blog" + contentTypeSchema = swagger.Components.Schemas[contentType] + ctx := context.Background() + ctx = context.WithValue(ctx, weosContext.CONTENT_TYPE, &weosContext.ContentType{ + Name: contentType, + Schema: contentTypeSchema.Value, + }) + ctx = context.WithValue(ctx, weosContext.USER_ID, "123") + builder := rest.CreateSchema(ctx, echo.New(), swagger) + + blog := make(map[string]interface{}) + blog["title"] = "Test" + payload, err := json.Marshal(blog) + if err != nil { + t.Fatalf("unexpected error marshalling payload '%s'", err) + } + + entity, err := new(model.ContentEntity).FromSchemaAndBuilder(ctx, swagger.Components.Schemas["Blog"].Value, builder[contentType]) + if err != nil { + t.Fatalf("unexpected error instantiating content entity '%s'", err) + } + + entity.Init(ctx, payload) + + if entity.Property == nil { + t.Fatal("expected item to be returned") + } + + if entity.GetString("Title") == "" { + t.Errorf("expected there to be a field '%s' with value '%s' got '%s'", blog["title"], " ", entity.GetString("Title")) + } +} + func TestContentEntity_IsValid(t *testing.T) { swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("../controllers/rest/fixtures/blog.yaml") if err != nil { @@ -848,3 +892,69 @@ func TestContentEntity_EnumerationFloat(t *testing.T) { } }) } + +func TestContentEntity_AutoGeneratedID(t *testing.T) { + //load open api spec + api, err := rest.New("../controllers/rest/fixtures/blog-x-schema.yaml") + if err != nil { + t.Fatalf("unexpected error setting up api: %s", err) + } + schemas := rest.CreateSchema(context.TODO(), api.EchoInstance(), api.Swagger) + t.Run("Generating id where the is format specified is ksuid", func(t *testing.T) { + contentType1 := "Author" + p1 := map[string]interface{}{"firstName": "my oh my", "lastName": "name"} + payload1, err := json.Marshal(p1) + if err != nil { + t.Errorf("unexpected error marshalling entity; %s", err) + } + entityFactory1 := new(model.DefaultEntityFactory).FromSchemaAndBuilder(contentType1, api.Swagger.Components.Schemas[contentType1].Value, schemas[contentType1]) + author, err := entityFactory1.CreateEntityWithValues(context.TODO(), payload1) + if err != nil { + t.Errorf("unexpected error generating id; %s", err) + } + if !author.IsValid() { + t.Error("expected ksuid to be generated") + for _, errString := range author.GetErrors() { + t.Errorf("domain error '%s'", errString) + } + } + _, err = ksuid.Parse(author.GetString("id")) + if err != nil { + fmt.Errorf("unexpected error parsing id as ksuid: %s", err) + } + }) + t.Run("Generating id where the is format specified is uuid", func(t *testing.T) { + contentType2 := "Category" + entityFactory2 := new(model.DefaultEntityFactory).FromSchemaAndBuilder(contentType2, api.Swagger.Components.Schemas[contentType2].Value, schemas[contentType2]) + p2 := map[string]interface{}{"description": "my favorite"} + payload2, err := json.Marshal(p2) + if err != nil { + t.Errorf("unexpected error marshalling entity; %s", err) + } + + category, err := entityFactory2.CreateEntityWithValues(context.TODO(), payload2) + if !category.IsValid() { + t.Errorf("expected uuid to be generated") + for _, errString := range category.GetErrors() { + t.Errorf("domain error '%s'", errString) + } + } + _, err = uuid.Parse(category.GetString("id")) + if err != nil { + t.Errorf("unexpected error parsing id as uuid: %s", err) + } + }) + t.Run("Generating id type is string and the format is not specified ", func(t *testing.T) { + contentType3 := "Archives" + entityFactory3 := new(model.DefaultEntityFactory).FromSchemaAndBuilder(contentType3, api.Swagger.Components.Schemas[contentType3].Value, schemas[contentType3]) + p3 := map[string]interface{}{"title": "old blogs"} + payload3, err := json.Marshal(p3) + if err != nil { + t.Errorf("unexpected error marshalling entity; %s", err) + } + _, err = entityFactory3.CreateEntityWithValues(context.TODO(), payload3) + if err == nil { + t.Errorf("expected error generating id") + } + }) +} diff --git a/model/domain_service.go b/model/domain_service.go index 9af6168c..68496688 100644 --- a/model/domain_service.go +++ b/model/domain_service.go @@ -56,28 +56,35 @@ func (s *DomainService) CreateBatch(ctx context.Context, payload json.RawMessage s.logger.Error(err) return nil, err } - - entity, err := entityFactory.NewEntity(ctx) if err != nil { return nil, err } if id, ok := titem.(map[string]interface{})["weos_id"]; ok { if i, ok := id.(string); ok && i != "" { - entity.ID = i + ctx = context.WithValue(ctx, weosContext.WEOS_ID, i) + } else { + entityID := ksuid.New().String() + ctx = context.WithValue(ctx, weosContext.WEOS_ID, entityID) } - } - if entity.ID == "" { - entity.ID = ksuid.New().String() - titem.(map[string]interface{})["weos_id"] = entity.ID + } else { + entityID := ksuid.New().String() + ctx = context.WithValue(ctx, weosContext.WEOS_ID, entityID) } - event := NewEntityEvent("create", entity, entity.ID, titem) - entity.NewChange(event) - err = entity.ApplyEvents([]*Event{event}) + entityPayload, err := json.Marshal(titem) + if err != nil { + return nil, err + } + entity, err := entityFactory.CreateEntityWithValues(ctx, entityPayload) + mItem, err := json.Marshal(titem) if err != nil { return nil, err } + err = json.Unmarshal(mItem, &titem) + if err != nil { + return nil, err + } err = s.ValidateUnique(ctx, entity) if err != nil { return nil, err diff --git a/model/entity_factory.go b/model/entity_factory.go index d2246968..d73d19c2 100644 --- a/model/entity_factory.go +++ b/model/entity_factory.go @@ -11,6 +11,8 @@ import ( type EntityFactory interface { FromSchemaAndBuilder(string, *openapi3.Schema, ds.Builder) EntityFactory NewEntity(ctx context.Context) (*ContentEntity, error) + //CreateEntityWithValues add an entity for the first type to the system with the following values + CreateEntityWithValues(ctx context.Context, payload []byte) (*ContentEntity, error) DynamicStruct(ctx context.Context) ds.DynamicStruct Name() string TableName() string @@ -35,6 +37,17 @@ func (d *DefaultEntityFactory) NewEntity(ctxt context.Context) (*ContentEntity, return new(ContentEntity).FromSchemaAndBuilder(ctxt, d.schema, d.builder) } +func (d *DefaultEntityFactory) CreateEntityWithValues(ctxt context.Context, payload []byte) (*ContentEntity, error) { + entity, err := new(ContentEntity).FromSchemaAndBuilder(ctxt, d.schema, d.builder) + if err != nil { + return nil, err + } + if id, ok := ctxt.Value(weosContext.WEOS_ID).(string); ok { + entity.ID = id + } + return entity.Init(ctxt, payload) +} + func (d *DefaultEntityFactory) Name() string { return d.name } diff --git a/model/event.go b/model/event.go index 49a47e3c..4eee425f 100644 --- a/model/event.go +++ b/model/event.go @@ -6,6 +6,8 @@ import ( "time" ) +const CREATE_EVENT = "create" + type Event struct { ID string `json:"id"` Type string `json:"type"` diff --git a/model/mocks_test.go b/model/mocks_test.go index 5356f7ff..7abf8fa3 100644 --- a/model/mocks_test.go +++ b/model/mocks_test.go @@ -7,7 +7,7 @@ import ( "context" "database/sql" "github.com/getkin/kin-openapi/openapi3" - "github.com/ompluscator/dynamic-struct" + ds "github.com/ompluscator/dynamic-struct" "github.com/wepala/weos/model" "gorm.io/gorm" "net/http" @@ -21,49 +21,49 @@ var _ model.EventRepository = &EventRepositoryMock{} // EventRepositoryMock is a mock implementation of model.EventRepository. // -// func TestSomethingThatUsesEventRepository(t *testing.T) { +// func TestSomethingThatUsesEventRepository(t *testing.T) { // -// // make and configure a mocked model.EventRepository -// mockedEventRepository := &EventRepositoryMock{ -// AddSubscriberFunc: func(handler model.EventHandler) { -// panic("mock out the AddSubscriber method") -// }, -// FlushFunc: func() error { -// panic("mock out the Flush method") -// }, -// GetAggregateSequenceNumberFunc: func(ID string) (int64, error) { -// panic("mock out the GetAggregateSequenceNumber method") -// }, -// GetByAggregateFunc: func(ID string) ([]*model.Event, error) { -// panic("mock out the GetByAggregate method") -// }, -// GetByAggregateAndSequenceRangeFunc: func(ID string, start int64, end int64) ([]*model.Event, error) { -// panic("mock out the GetByAggregateAndSequenceRange method") -// }, -// GetByAggregateAndTypeFunc: func(ID string, entityType string) ([]*model.Event, error) { -// panic("mock out the GetByAggregateAndType method") -// }, -// GetByEntityAndAggregateFunc: func(entityID string, entityType string, rootID string) ([]*model.Event, error) { -// panic("mock out the GetByEntityAndAggregate method") -// }, -// GetSubscribersFunc: func() ([]model.EventHandler, error) { -// panic("mock out the GetSubscribers method") -// }, -// MigrateFunc: func(ctx context.Context) error { -// panic("mock out the Migrate method") -// }, -// PersistFunc: func(ctxt context.Context, entity model.AggregateInterface) error { -// panic("mock out the Persist method") -// }, -// ReplayEventsFunc: func(ctxt context.Context, date time.Time, entityFactories map[string]model.EntityFactory, projection model.Projection) (int, int, int, []error) { -// panic("mock out the ReplayEvents method") -// }, -// } +// // make and configure a mocked model.EventRepository +// mockedEventRepository := &EventRepositoryMock{ +// AddSubscriberFunc: func(handler model.EventHandler) { +// panic("mock out the AddSubscriber method") +// }, +// FlushFunc: func() error { +// panic("mock out the Flush method") +// }, +// GetAggregateSequenceNumberFunc: func(ID string) (int64, error) { +// panic("mock out the GetAggregateSequenceNumber method") +// }, +// GetByAggregateFunc: func(ID string) ([]*model.Event, error) { +// panic("mock out the GetByAggregate method") +// }, +// GetByAggregateAndSequenceRangeFunc: func(ID string, start int64, end int64) ([]*model.Event, error) { +// panic("mock out the GetByAggregateAndSequenceRange method") +// }, +// GetByAggregateAndTypeFunc: func(ID string, entityType string) ([]*model.Event, error) { +// panic("mock out the GetByAggregateAndType method") +// }, +// GetByEntityAndAggregateFunc: func(entityID string, entityType string, rootID string) ([]*model.Event, error) { +// panic("mock out the GetByEntityAndAggregate method") +// }, +// GetSubscribersFunc: func() ([]model.EventHandler, error) { +// panic("mock out the GetSubscribers method") +// }, +// MigrateFunc: func(ctx context.Context) error { +// panic("mock out the Migrate method") +// }, +// PersistFunc: func(ctxt context.Context, entity model.AggregateInterface) error { +// panic("mock out the Persist method") +// }, +// ReplayEventsFunc: func(ctxt context.Context, date time.Time, entityFactories map[string]model.EntityFactory, projection model.Projection) (int, int, int, []error) { +// panic("mock out the ReplayEvents method") +// }, +// } // -// // use mockedEventRepository in code that requires model.EventRepository -// // and then make assertions. +// // use mockedEventRepository in code that requires model.EventRepository +// // and then make assertions. // -// } +// } type EventRepositoryMock struct { // AddSubscriberFunc mocks the AddSubscriber method. AddSubscriberFunc func(handler model.EventHandler) @@ -556,76 +556,37 @@ var _ model.Projection = &ProjectionMock{} // ProjectionMock is a mock implementation of model.Projection. // -// func TestSomethingThatUsesProjection(t *testing.T) { -// -// // make and configure a mocked model.Projection -// mockedProjection := &ProjectionMock{ -// GetByEntityIDFunc: func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { -// panic("mock out the GetByEntityID method") -// }, -// GetByIdentifiersFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { -// panic("mock out the GetByIdentifiers method") -// }, -// GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { -// panic("mock out the GetByKey method") -// }, -// GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page int, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { -// panic("mock out the GetContentEntities method") -// }, -// GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { -// panic("mock out the GetContentEntity method") -// }, -// GetEventHandlerFunc: func() model.EventHandler { -// panic("mock out the GetEventHandler method") -// }, -// MigrateFunc: func(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error { -// panic("mock out the Migrate method") -// }, -// } -// -// // use mockedProjection in code that requires model.Projection -// // and then make assertions. -// -// } -// Ensure, that ProjectionMock does implement model.Projection. -// If this is not the case, regenerate this file with moq. -// Ensure, that ProjectionMock does implement model.Projection. -// If this is not the case, regenerate this file with moq. -var _ model.Projection = &ProjectionMock{} - -// ProjectionMock is a mock implementation of model.Projection. -// -// func TestSomethingThatUsesProjection(t *testing.T) { +// func TestSomethingThatUsesProjection(t *testing.T) { // -// // make and configure a mocked model.Projection -// mockedProjection := &ProjectionMock{ -// GetByEntityIDFunc: func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { -// panic("mock out the GetByEntityID method") -// }, -// GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { -// panic("mock out the GetByKey method") -// }, -// GetByPropertiesFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { -// panic("mock out the GetByProperties method") -// }, -// GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page int, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { -// panic("mock out the GetContentEntities method") -// }, -// GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { -// panic("mock out the GetContentEntity method") -// }, -// GetEventHandlerFunc: func() model.EventHandler { -// panic("mock out the GetEventHandler method") -// }, -// MigrateFunc: func(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error { -// panic("mock out the Migrate method") -// }, -// } +// // make and configure a mocked model.Projection +// mockedProjection := &ProjectionMock{ +// GetByEntityIDFunc: func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { +// panic("mock out the GetByEntityID method") +// }, +// GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { +// panic("mock out the GetByKey method") +// }, +// GetByPropertiesFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { +// panic("mock out the GetByProperties method") +// }, +// GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page int, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { +// panic("mock out the GetContentEntities method") +// }, +// GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { +// panic("mock out the GetContentEntity method") +// }, +// GetEventHandlerFunc: func() model.EventHandler { +// panic("mock out the GetEventHandler method") +// }, +// MigrateFunc: func(ctx context.Context, builders map[string]ds.Builder, deletedFields map[string][]string) error { +// panic("mock out the Migrate method") +// }, +// } // -// // use mockedProjection in code that requires model.Projection -// // and then make assertions. +// // use mockedProjection in code that requires model.Projection +// // and then make assertions. // -// } +// } type ProjectionMock struct { // GetByEntityIDFunc mocks the GetByEntityID method. GetByEntityIDFunc func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) @@ -646,7 +607,7 @@ type ProjectionMock struct { GetEventHandlerFunc func() model.EventHandler // MigrateFunc mocks the Migrate method. - MigrateFunc func(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error + MigrateFunc func(ctx context.Context, builders map[string]ds.Builder, deletedFields map[string][]string) error // calls tracks calls to the methods. calls struct { @@ -711,7 +672,7 @@ type ProjectionMock struct { // Ctx is the ctx argument value. Ctx context.Context // Builders is the builders argument value. - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder // DeletedFields is the deletedFields argument value. DeletedFields map[string][]string } @@ -963,13 +924,13 @@ func (mock *ProjectionMock) GetEventHandlerCalls() []struct { } // Migrate calls MigrateFunc. -func (mock *ProjectionMock) Migrate(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error { +func (mock *ProjectionMock) Migrate(ctx context.Context, builders map[string]ds.Builder, deletedFields map[string][]string) error { if mock.MigrateFunc == nil { panic("ProjectionMock.MigrateFunc: method is nil but Projection.Migrate was just called") } callInfo := struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder DeletedFields map[string][]string }{ Ctx: ctx, @@ -987,12 +948,12 @@ func (mock *ProjectionMock) Migrate(ctx context.Context, builders map[string]dyn // len(mockedProjection.MigrateCalls()) func (mock *ProjectionMock) MigrateCalls() []struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder DeletedFields map[string][]string } { var calls []struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder DeletedFields map[string][]string } mock.lockMigrate.RLock() @@ -1001,61 +962,58 @@ func (mock *ProjectionMock) MigrateCalls() []struct { return calls } - - - // Ensure, that LogMock does implement model.Log. // If this is not the case, regenerate this file with moq. var _ model.Log = &LogMock{} // LogMock is a mock implementation of model.Log. // -// func TestSomethingThatUsesLog(t *testing.T) { +// func TestSomethingThatUsesLog(t *testing.T) { // -// // make and configure a mocked model.Log -// mockedLog := &LogMock{ -// DebugFunc: func(args ...interface{}) { -// panic("mock out the Debug method") -// }, -// DebugfFunc: func(format string, args ...interface{}) { -// panic("mock out the Debugf method") -// }, -// ErrorFunc: func(args ...interface{}) { -// panic("mock out the Error method") -// }, -// ErrorfFunc: func(format string, args ...interface{}) { -// panic("mock out the Errorf method") -// }, -// FatalFunc: func(args ...interface{}) { -// panic("mock out the Fatal method") -// }, -// FatalfFunc: func(format string, args ...interface{}) { -// panic("mock out the Fatalf method") -// }, -// InfoFunc: func(args ...interface{}) { -// panic("mock out the Info method") -// }, -// InfofFunc: func(format string, args ...interface{}) { -// panic("mock out the Infof method") -// }, -// PanicFunc: func(args ...interface{}) { -// panic("mock out the Panic method") -// }, -// PanicfFunc: func(format string, args ...interface{}) { -// panic("mock out the Panicf method") -// }, -// PrintFunc: func(args ...interface{}) { -// panic("mock out the Print method") -// }, -// PrintfFunc: func(format string, args ...interface{}) { -// panic("mock out the Printf method") -// }, -// } +// // make and configure a mocked model.Log +// mockedLog := &LogMock{ +// DebugFunc: func(args ...interface{}) { +// panic("mock out the Debug method") +// }, +// DebugfFunc: func(format string, args ...interface{}) { +// panic("mock out the Debugf method") +// }, +// ErrorFunc: func(args ...interface{}) { +// panic("mock out the Error method") +// }, +// ErrorfFunc: func(format string, args ...interface{}) { +// panic("mock out the Errorf method") +// }, +// FatalFunc: func(args ...interface{}) { +// panic("mock out the Fatal method") +// }, +// FatalfFunc: func(format string, args ...interface{}) { +// panic("mock out the Fatalf method") +// }, +// InfoFunc: func(args ...interface{}) { +// panic("mock out the Info method") +// }, +// InfofFunc: func(format string, args ...interface{}) { +// panic("mock out the Infof method") +// }, +// PanicFunc: func(args ...interface{}) { +// panic("mock out the Panic method") +// }, +// PanicfFunc: func(format string, args ...interface{}) { +// panic("mock out the Panicf method") +// }, +// PrintFunc: func(args ...interface{}) { +// panic("mock out the Print method") +// }, +// PrintfFunc: func(format string, args ...interface{}) { +// panic("mock out the Printf method") +// }, +// } // -// // use mockedLog in code that requires model.Log -// // and then make assertions. +// // use mockedLog in code that requires model.Log +// // and then make assertions. // -// } +// } type LogMock struct { // DebugFunc mocks the Debug method. DebugFunc func(args ...interface{}) @@ -1584,25 +1542,25 @@ var _ model.CommandDispatcher = &CommandDispatcherMock{} // CommandDispatcherMock is a mock implementation of model.CommandDispatcher. // -// func TestSomethingThatUsesCommandDispatcher(t *testing.T) { +// func TestSomethingThatUsesCommandDispatcher(t *testing.T) { // -// // make and configure a mocked model.CommandDispatcher -// mockedCommandDispatcher := &CommandDispatcherMock{ -// AddSubscriberFunc: func(command *model.Command, handler model.CommandHandler) map[string][]model.CommandHandler { -// panic("mock out the AddSubscriber method") -// }, -// DispatchFunc: func(ctx context.Context, command *model.Command, eventStore model.EventRepository, projection model.Projection, logger model.Log) error { -// panic("mock out the Dispatch method") -// }, -// GetSubscribersFunc: func() map[string][]model.CommandHandler { -// panic("mock out the GetSubscribers method") -// }, -// } +// // make and configure a mocked model.CommandDispatcher +// mockedCommandDispatcher := &CommandDispatcherMock{ +// AddSubscriberFunc: func(command *model.Command, handler model.CommandHandler) map[string][]model.CommandHandler { +// panic("mock out the AddSubscriber method") +// }, +// DispatchFunc: func(ctx context.Context, command *model.Command, eventStore model.EventRepository, projection model.Projection, logger model.Log) error { +// panic("mock out the Dispatch method") +// }, +// GetSubscribersFunc: func() map[string][]model.CommandHandler { +// panic("mock out the GetSubscribers method") +// }, +// } // -// // use mockedCommandDispatcher in code that requires model.CommandDispatcher -// // and then make assertions. +// // use mockedCommandDispatcher in code that requires model.CommandDispatcher +// // and then make assertions. // -// } +// } type CommandDispatcherMock struct { // AddSubscriberFunc mocks the AddSubscriber method. AddSubscriberFunc func(command *model.Command, handler model.CommandHandler) map[string][]model.CommandHandler @@ -1758,52 +1716,52 @@ var _ model.Service = &ServiceMock{} // ServiceMock is a mock implementation of model.Service. // -// func TestSomethingThatUsesService(t *testing.T) { +// func TestSomethingThatUsesService(t *testing.T) { // -// // make and configure a mocked model.Service -// mockedService := &ServiceMock{ -// AddProjectionFunc: func(projection model.Projection) error { -// panic("mock out the AddProjection method") -// }, -// ConfigFunc: func() *model.ServiceConfig { -// panic("mock out the Config method") -// }, -// DBFunc: func() *gorm.DB { -// panic("mock out the DB method") -// }, -// DBConnectionFunc: func() *sql.DB { -// panic("mock out the DBConnection method") -// }, -// DispatcherFunc: func() model.CommandDispatcher { -// panic("mock out the Dispatcher method") -// }, -// EventRepositoryFunc: func() model.EventRepository { -// panic("mock out the EventRepository method") -// }, -// HTTPClientFunc: func() *http.Client { -// panic("mock out the HTTPClient method") -// }, -// IDFunc: func() string { -// panic("mock out the ID method") -// }, -// LoggerFunc: func() model.Log { -// panic("mock out the Logger method") -// }, -// MigrateFunc: func(ctx context.Context, builders map[string]dynamicstruct.Builder) error { -// panic("mock out the Migrate method") -// }, -// ProjectionsFunc: func() []model.Projection { -// panic("mock out the Projections method") -// }, -// TitleFunc: func() string { -// panic("mock out the Title method") -// }, -// } +// // make and configure a mocked model.Service +// mockedService := &ServiceMock{ +// AddProjectionFunc: func(projection model.Projection) error { +// panic("mock out the AddProjection method") +// }, +// ConfigFunc: func() *model.ServiceConfig { +// panic("mock out the Config method") +// }, +// DBFunc: func() *gorm.DB { +// panic("mock out the DB method") +// }, +// DBConnectionFunc: func() *sql.DB { +// panic("mock out the DBConnection method") +// }, +// DispatcherFunc: func() model.CommandDispatcher { +// panic("mock out the Dispatcher method") +// }, +// EventRepositoryFunc: func() model.EventRepository { +// panic("mock out the EventRepository method") +// }, +// HTTPClientFunc: func() *http.Client { +// panic("mock out the HTTPClient method") +// }, +// IDFunc: func() string { +// panic("mock out the ID method") +// }, +// LoggerFunc: func() model.Log { +// panic("mock out the Logger method") +// }, +// MigrateFunc: func(ctx context.Context, builders map[string]ds.Builder) error { +// panic("mock out the Migrate method") +// }, +// ProjectionsFunc: func() []model.Projection { +// panic("mock out the Projections method") +// }, +// TitleFunc: func() string { +// panic("mock out the Title method") +// }, +// } // -// // use mockedService in code that requires model.Service -// // and then make assertions. +// // use mockedService in code that requires model.Service +// // and then make assertions. // -// } +// } type ServiceMock struct { // AddProjectionFunc mocks the AddProjection method. AddProjectionFunc func(projection model.Projection) error @@ -1833,7 +1791,7 @@ type ServiceMock struct { LoggerFunc func() model.Log // MigrateFunc mocks the Migrate method. - MigrateFunc func(ctx context.Context, builders map[string]dynamicstruct.Builder) error + MigrateFunc func(ctx context.Context, builders map[string]ds.Builder) error // ProjectionsFunc mocks the Projections method. ProjectionsFunc func() []model.Projection @@ -1877,7 +1835,7 @@ type ServiceMock struct { // Ctx is the ctx argument value. Ctx context.Context // Builders is the builders argument value. - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder } // Projections holds details about calls to the Projections method. Projections []struct { @@ -2140,13 +2098,13 @@ func (mock *ServiceMock) LoggerCalls() []struct { } // Migrate calls MigrateFunc. -func (mock *ServiceMock) Migrate(ctx context.Context, builders map[string]dynamicstruct.Builder) error { +func (mock *ServiceMock) Migrate(ctx context.Context, builders map[string]ds.Builder) error { if mock.MigrateFunc == nil { panic("ServiceMock.MigrateFunc: method is nil but Service.Migrate was just called") } callInfo := struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder }{ Ctx: ctx, Builders: builders, @@ -2162,11 +2120,11 @@ func (mock *ServiceMock) Migrate(ctx context.Context, builders map[string]dynami // len(mockedService.MigrateCalls()) func (mock *ServiceMock) MigrateCalls() []struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder } { var calls []struct { Ctx context.Context - Builders map[string]dynamicstruct.Builder + Builders map[string]ds.Builder } mock.lockMigrate.RLock() calls = mock.calls.Migrate @@ -2232,46 +2190,52 @@ var _ model.EntityFactory = &EntityFactoryMock{} // EntityFactoryMock is a mock implementation of model.EntityFactory. // -// func TestSomethingThatUsesEntityFactory(t *testing.T) { +// func TestSomethingThatUsesEntityFactory(t *testing.T) { // -// // make and configure a mocked model.EntityFactory -// mockedEntityFactory := &EntityFactoryMock{ -// BuilderFunc: func(ctx context.Context) dynamicstruct.Builder { -// panic("mock out the Builder method") -// }, -// DynamicStructFunc: func(ctx context.Context) dynamicstruct.DynamicStruct { -// panic("mock out the DynamicStruct method") -// }, -// FromSchemaAndBuilderFunc: func(in1 string, in2 *openapi3.Schema, in3 dynamicstruct.Builder) model.EntityFactory { -// panic("mock out the FromSchemaAndBuilder method") -// }, -// NameFunc: func() string { -// panic("mock out the Name method") -// }, -// NewEntityFunc: func(ctx context.Context) (*model.ContentEntity, error) { -// panic("mock out the NewEntity method") -// }, -// SchemaFunc: func() *openapi3.Schema { -// panic("mock out the Schema method") -// }, -// TableNameFunc: func() string { -// panic("mock out the TableName method") -// }, -// } +// // make and configure a mocked model.EntityFactory +// mockedEntityFactory := &EntityFactoryMock{ +// BuilderFunc: func(ctx context.Context) ds.Builder { +// panic("mock out the Builder method") +// }, +// CreateEntityWithValuesFunc: func(ctx context.Context, payload []byte) (*model.ContentEntity, error) { +// panic("mock out the CreateEntityWithValues method") +// }, +// DynamicStructFunc: func(ctx context.Context) ds.DynamicStruct { +// panic("mock out the DynamicStruct method") +// }, +// FromSchemaAndBuilderFunc: func(s string, schema *openapi3.Schema, builder ds.Builder) model.EntityFactory { +// panic("mock out the FromSchemaAndBuilder method") +// }, +// NameFunc: func() string { +// panic("mock out the Name method") +// }, +// NewEntityFunc: func(ctx context.Context) (*model.ContentEntity, error) { +// panic("mock out the NewEntity method") +// }, +// SchemaFunc: func() *openapi3.Schema { +// panic("mock out the Schema method") +// }, +// TableNameFunc: func() string { +// panic("mock out the TableName method") +// }, +// } // -// // use mockedEntityFactory in code that requires model.EntityFactory -// // and then make assertions. +// // use mockedEntityFactory in code that requires model.EntityFactory +// // and then make assertions. // -// } +// } type EntityFactoryMock struct { // BuilderFunc mocks the Builder method. - BuilderFunc func(ctx context.Context) dynamicstruct.Builder + BuilderFunc func(ctx context.Context) ds.Builder + + // CreateEntityWithValuesFunc mocks the CreateEntityWithValues method. + CreateEntityWithValuesFunc func(ctx context.Context, payload []byte) (*model.ContentEntity, error) // DynamicStructFunc mocks the DynamicStruct method. - DynamicStructFunc func(ctx context.Context) dynamicstruct.DynamicStruct + DynamicStructFunc func(ctx context.Context) ds.DynamicStruct // FromSchemaAndBuilderFunc mocks the FromSchemaAndBuilder method. - FromSchemaAndBuilderFunc func(in1 string, in2 *openapi3.Schema, in3 dynamicstruct.Builder) model.EntityFactory + FromSchemaAndBuilderFunc func(s string, schema *openapi3.Schema, builder ds.Builder) model.EntityFactory // NameFunc mocks the Name method. NameFunc func() string @@ -2292,6 +2256,13 @@ type EntityFactoryMock struct { // Ctx is the ctx argument value. Ctx context.Context } + // CreateEntityWithValues holds details about calls to the CreateEntityWithValues method. + CreateEntityWithValues []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Payload is the payload argument value. + Payload []byte + } // DynamicStruct holds details about calls to the DynamicStruct method. DynamicStruct []struct { // Ctx is the ctx argument value. @@ -2299,12 +2270,12 @@ type EntityFactoryMock struct { } // FromSchemaAndBuilder holds details about calls to the FromSchemaAndBuilder method. FromSchemaAndBuilder []struct { - // In1 is the in1 argument value. - In1 string - // In2 is the in2 argument value. - In2 *openapi3.Schema - // In3 is the in3 argument value. - In3 dynamicstruct.Builder + // S is the s argument value. + S string + // Schema is the schema argument value. + Schema *openapi3.Schema + // Builder is the builder argument value. + Builder ds.Builder } // Name holds details about calls to the Name method. Name []struct { @@ -2321,17 +2292,18 @@ type EntityFactoryMock struct { TableName []struct { } } - lockBuilder sync.RWMutex - lockDynamicStruct sync.RWMutex - lockFromSchemaAndBuilder sync.RWMutex - lockName sync.RWMutex - lockNewEntity sync.RWMutex - lockSchema sync.RWMutex - lockTableName sync.RWMutex + lockBuilder sync.RWMutex + lockCreateEntityWithValues sync.RWMutex + lockDynamicStruct sync.RWMutex + lockFromSchemaAndBuilder sync.RWMutex + lockName sync.RWMutex + lockNewEntity sync.RWMutex + lockSchema sync.RWMutex + lockTableName sync.RWMutex } // Builder calls BuilderFunc. -func (mock *EntityFactoryMock) Builder(ctx context.Context) dynamicstruct.Builder { +func (mock *EntityFactoryMock) Builder(ctx context.Context) ds.Builder { if mock.BuilderFunc == nil { panic("EntityFactoryMock.BuilderFunc: method is nil but EntityFactory.Builder was just called") } @@ -2361,8 +2333,43 @@ func (mock *EntityFactoryMock) BuilderCalls() []struct { return calls } +// CreateEntityWithValues calls CreateEntityWithValuesFunc. +func (mock *EntityFactoryMock) CreateEntityWithValues(ctx context.Context, payload []byte) (*model.ContentEntity, error) { + if mock.CreateEntityWithValuesFunc == nil { + panic("EntityFactoryMock.CreateEntityWithValuesFunc: method is nil but EntityFactory.CreateEntityWithValues was just called") + } + callInfo := struct { + Ctx context.Context + Payload []byte + }{ + Ctx: ctx, + Payload: payload, + } + mock.lockCreateEntityWithValues.Lock() + mock.calls.CreateEntityWithValues = append(mock.calls.CreateEntityWithValues, callInfo) + mock.lockCreateEntityWithValues.Unlock() + return mock.CreateEntityWithValuesFunc(ctx, payload) +} + +// CreateEntityWithValuesCalls gets all the calls that were made to CreateEntityWithValues. +// Check the length with: +// len(mockedEntityFactory.CreateEntityWithValuesCalls()) +func (mock *EntityFactoryMock) CreateEntityWithValuesCalls() []struct { + Ctx context.Context + Payload []byte +} { + var calls []struct { + Ctx context.Context + Payload []byte + } + mock.lockCreateEntityWithValues.RLock() + calls = mock.calls.CreateEntityWithValues + mock.lockCreateEntityWithValues.RUnlock() + return calls +} + // DynamicStruct calls DynamicStructFunc. -func (mock *EntityFactoryMock) DynamicStruct(ctx context.Context) dynamicstruct.DynamicStruct { +func (mock *EntityFactoryMock) DynamicStruct(ctx context.Context) ds.DynamicStruct { if mock.DynamicStructFunc == nil { panic("EntityFactoryMock.DynamicStructFunc: method is nil but EntityFactory.DynamicStruct was just called") } @@ -2393,37 +2400,37 @@ func (mock *EntityFactoryMock) DynamicStructCalls() []struct { } // FromSchemaAndBuilder calls FromSchemaAndBuilderFunc. -func (mock *EntityFactoryMock) FromSchemaAndBuilder(in1 string, in2 *openapi3.Schema, in3 dynamicstruct.Builder) model.EntityFactory { +func (mock *EntityFactoryMock) FromSchemaAndBuilder(s string, schema *openapi3.Schema, builder ds.Builder) model.EntityFactory { if mock.FromSchemaAndBuilderFunc == nil { panic("EntityFactoryMock.FromSchemaAndBuilderFunc: method is nil but EntityFactory.FromSchemaAndBuilder was just called") } callInfo := struct { - In1 string - In2 *openapi3.Schema - In3 dynamicstruct.Builder + S string + Schema *openapi3.Schema + Builder ds.Builder }{ - In1: in1, - In2: in2, - In3: in3, + S: s, + Schema: schema, + Builder: builder, } mock.lockFromSchemaAndBuilder.Lock() mock.calls.FromSchemaAndBuilder = append(mock.calls.FromSchemaAndBuilder, callInfo) mock.lockFromSchemaAndBuilder.Unlock() - return mock.FromSchemaAndBuilderFunc(in1, in2, in3) + return mock.FromSchemaAndBuilderFunc(s, schema, builder) } // FromSchemaAndBuilderCalls gets all the calls that were made to FromSchemaAndBuilder. // Check the length with: // len(mockedEntityFactory.FromSchemaAndBuilderCalls()) func (mock *EntityFactoryMock) FromSchemaAndBuilderCalls() []struct { - In1 string - In2 *openapi3.Schema - In3 dynamicstruct.Builder + S string + Schema *openapi3.Schema + Builder ds.Builder } { var calls []struct { - In1 string - In2 *openapi3.Schema - In3 dynamicstruct.Builder + S string + Schema *openapi3.Schema + Builder ds.Builder } mock.lockFromSchemaAndBuilder.RLock() calls = mock.calls.FromSchemaAndBuilder @@ -2539,153 +2546,3 @@ func (mock *EntityFactoryMock) TableNameCalls() []struct { mock.lockTableName.RUnlock() return calls } - -// Ensure, that EventDispatcherMock does implement model.EventDispatcher. -// If this is not the case, regenerate this file with moq. -var _ model.EventDispatcher = &EventDispatcherMock{} - -// EventDispatcherMock is a mock implementation of model.EventDispatcher. -// -// func TestSomethingThatUsesEventDispatcher(t *testing.T) { -// -// // make and configure a mocked model.EventDispatcher -// mockedEventDispatcher := &EventDispatcherMock{ -// AddSubscriberFunc: func(handler model.EventHandler) { -// panic("mock out the AddSubscriber method") -// }, -// DispatchFunc: func(ctx context.Context, event model.Event) { -// panic("mock out the Dispatch method") -// }, -// GetSubscribersFunc: func() []model.EventHandler { -// panic("mock out the GetSubscribers method") -// }, -// } -// -// // use mockedEventDispatcher in code that requires model.EventDispatcher -// // and then make assertions. -// -// } -type EventDispatcherMock struct { - // AddSubscriberFunc mocks the AddSubscriber method. - AddSubscriberFunc func(handler model.EventHandler) - - // DispatchFunc mocks the Dispatch method. - DispatchFunc func(ctx context.Context, event model.Event) - - // GetSubscribersFunc mocks the GetSubscribers method. - GetSubscribersFunc func() []model.EventHandler - - // calls tracks calls to the methods. - calls struct { - // AddSubscriber holds details about calls to the AddSubscriber method. - AddSubscriber []struct { - // Handler is the handler argument value. - Handler model.EventHandler - } - // Dispatch holds details about calls to the Dispatch method. - Dispatch []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // Event is the event argument value. - Event model.Event - } - // GetSubscribers holds details about calls to the GetSubscribers method. - GetSubscribers []struct { - } - } - lockAddSubscriber sync.RWMutex - lockDispatch sync.RWMutex - lockGetSubscribers sync.RWMutex -} - -// AddSubscriber calls AddSubscriberFunc. -func (mock *EventDispatcherMock) AddSubscriber(handler model.EventHandler) { - if mock.AddSubscriberFunc == nil { - panic("EventDispatcherMock.AddSubscriberFunc: method is nil but EventDispatcher.AddSubscriber was just called") - } - callInfo := struct { - Handler model.EventHandler - }{ - Handler: handler, - } - mock.lockAddSubscriber.Lock() - mock.calls.AddSubscriber = append(mock.calls.AddSubscriber, callInfo) - mock.lockAddSubscriber.Unlock() - mock.AddSubscriberFunc(handler) -} - -// AddSubscriberCalls gets all the calls that were made to AddSubscriber. -// Check the length with: -// len(mockedEventDispatcher.AddSubscriberCalls()) -func (mock *EventDispatcherMock) AddSubscriberCalls() []struct { - Handler model.EventHandler -} { - var calls []struct { - Handler model.EventHandler - } - mock.lockAddSubscriber.RLock() - calls = mock.calls.AddSubscriber - mock.lockAddSubscriber.RUnlock() - return calls -} - -// Dispatch calls DispatchFunc. -func (mock *EventDispatcherMock) Dispatch(ctx context.Context, event model.Event) { - if mock.DispatchFunc == nil { - panic("EventDispatcherMock.DispatchFunc: method is nil but EventDispatcher.Dispatch was just called") - } - callInfo := struct { - Ctx context.Context - Event model.Event - }{ - Ctx: ctx, - Event: event, - } - mock.lockDispatch.Lock() - mock.calls.Dispatch = append(mock.calls.Dispatch, callInfo) - mock.lockDispatch.Unlock() - mock.DispatchFunc(ctx, event) -} - -// DispatchCalls gets all the calls that were made to Dispatch. -// Check the length with: -// len(mockedEventDispatcher.DispatchCalls()) -func (mock *EventDispatcherMock) DispatchCalls() []struct { - Ctx context.Context - Event model.Event -} { - var calls []struct { - Ctx context.Context - Event model.Event - } - mock.lockDispatch.RLock() - calls = mock.calls.Dispatch - mock.lockDispatch.RUnlock() - return calls -} - -// GetSubscribers calls GetSubscribersFunc. -func (mock *EventDispatcherMock) GetSubscribers() []model.EventHandler { - if mock.GetSubscribersFunc == nil { - panic("EventDispatcherMock.GetSubscribersFunc: method is nil but EventDispatcher.GetSubscribers was just called") - } - callInfo := struct { - }{} - mock.lockGetSubscribers.Lock() - mock.calls.GetSubscribers = append(mock.calls.GetSubscribers, callInfo) - mock.lockGetSubscribers.Unlock() - return mock.GetSubscribersFunc() -} - -// GetSubscribersCalls gets all the calls that were made to GetSubscribers. -// Check the length with: -// len(mockedEventDispatcher.GetSubscribersCalls()) -func (mock *EventDispatcherMock) GetSubscribersCalls() []struct { -} { - var calls []struct { - } - mock.lockGetSubscribers.RLock() - calls = mock.calls.GetSubscribers - mock.lockGetSubscribers.RUnlock() - return calls -} diff --git a/model/receiver.go b/model/receiver.go index c55abdfe..cd2851b8 100644 --- a/model/receiver.go +++ b/model/receiver.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - + weosContext "github.com/wepala/weos/context" "golang.org/x/net/context" ) @@ -18,39 +18,26 @@ func CreateHandler(ctx context.Context, command *Command, eventStore EventReposi if logger == nil { return fmt.Errorf("no logger set") } - - payload, err := AddIDToPayload(command.Payload, command.Metadata.EntityID) - if err != nil { - return err - } - entityFactory := GetEntityFactory(ctx) if entityFactory == nil { - err = errors.New("no entity factory found") + err := errors.New("no entity factory found") logger.Error(err) return err } - newEntity, err := entityFactory.NewEntity(ctx) - if err != nil { + //add the weos id to the context IF it's not empty. + //TODO This is more about backward compatability and should be reconsidered in the future + if command.Metadata.EntityID != "" { + ctx = context.WithValue(ctx, weosContext.WEOS_ID, command.Metadata.EntityID) + } + newEntity, err := entityFactory.CreateEntityWithValues(ctx, command.Payload) + if errr, ok := err.(*DomainError); ok { + return errr + } else if err != nil { err = NewDomainError("unexpected error creating entity", command.Metadata.EntityType, "", err) logger.Debug(err) return err } - //use the entity id that was passed with the command - newEntity.ID = command.Metadata.EntityID - //add create event - event := NewEntityEvent("create", newEntity, newEntity.ID, payload) - //ddd userid to event - event.Meta.User = command.Metadata.UserID - newEntity.NewChange(event) - err = newEntity.ApplyEvents([]*Event{event}) - if err != nil { - err = NewDomainError("unexpected error creating entity: "+err.Error(), command.Metadata.EntityType, "", err) - logger.Debugf(err.Error()) - return err - } - domainService := NewDomainService(ctx, eventStore, projection, logger) err = domainService.ValidateUnique(ctx, newEntity) if err != nil { diff --git a/model/receiver_test.go b/model/receiver_test.go index 7b091d7e..9e6de449 100644 --- a/model/receiver_test.go +++ b/model/receiver_test.go @@ -4,6 +4,7 @@ import ( context3 "context" "encoding/json" "github.com/labstack/echo/v4" + api "github.com/wepala/weos/controllers/rest" "testing" "github.com/getkin/kin-openapi/openapi3" @@ -21,16 +22,29 @@ type Blog struct { } func TestCreateContentType(t *testing.T) { - t.SkipNow() - ctx := context.Background() - mockEntityFactory := &EntityFactoryMock{ - NewEntityFunc: func(ctx context3.Context) (*model.ContentEntity, error) { - return &model.ContentEntity{}, nil - }, + swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromFile("../controllers/rest/fixtures/blog-x-schema.yaml") + if err != nil { + t.Fatalf("unexpected error occured '%s'", err) } - ctx = context.WithValue(ctx, weosContext.ENTITY_FACTORY, mockEntityFactory) + ctx := context.Background() + ctx1 := context.Background() + ctx2 := context.Background() + contentEntity := "Author" + contentEntity1 := "Category" + contentEntity2 := "Archives" + builder := api.CreateSchema(ctx, echo.New(), swagger) + entityFactory := new(model.DefaultEntityFactory).FromSchemaAndBuilder(contentEntity, swagger.Components.Schemas[contentEntity].Value, builder[contentEntity]) + ctx = context.WithValue(ctx, weosContext.ENTITY_FACTORY, entityFactory) ctx = context.WithValue(ctx, weosContext.USER_ID, "123") + entityFactory1 := new(model.DefaultEntityFactory).FromSchemaAndBuilder(contentEntity1, swagger.Components.Schemas[contentEntity1].Value, builder[contentEntity1]) + ctx1 = context.WithValue(ctx1, weosContext.ENTITY_FACTORY, entityFactory1) + ctx1 = context.WithValue(ctx1, weosContext.USER_ID, "123") + entityFactory2 := new(model.DefaultEntityFactory).FromSchemaAndBuilder(contentEntity2, swagger.Components.Schemas[contentEntity2].Value, builder[contentEntity2]) + ctx2 = context.WithValue(ctx2, weosContext.ENTITY_FACTORY, entityFactory2) + ctx2 = context.WithValue(ctx2, weosContext.USER_ID, "123") commandDispatcher := &model.DefaultCommandDispatcher{} + commandDispatcher.AddSubscriber(model.Create(context.Background(), nil, contentEntity, ""), model.CreateHandler) + commandDispatcher.AddSubscriber(model.CreateBatch(context.Background(), nil, contentEntity), model.CreateBatchHandler) mockEventRepository := &EventRepositoryMock{ PersistFunc: func(ctxt context.Context, entity model.AggregateInterface) error { var event *model.Event @@ -50,40 +64,33 @@ func TestCreateContentType(t *testing.T) { if event.Meta.EntityType == "" { t.Errorf("expected event to be '%s', got '%s'", "", event.Type) } - + payload := entities[0].(*model.Event).Payload + entity1 := map[string]interface{}{} + err = json.Unmarshal(payload, &entity1) + if err != nil { + t.Errorf("unexpect error unmarshalling payload in event: %s", err) + } + if entity1["id"] == nil { + t.Errorf("Unexpected error expected to find id but got nil") + } return nil }, - AddSubscriberFunc: func(handler model.EventHandler) { - }, } - application := &ServiceMock{ - DispatcherFunc: func() model.CommandDispatcher { - return commandDispatcher - }, - EventRepositoryFunc: func() model.EventRepository { - return mockEventRepository - }, - ProjectionsFunc: func() []model.Projection { - return []model.Projection{} + projectionMock := &ProjectionMock{ + GetByPropertiesFunc: func(ctxt context3.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) ([]map[string]interface{}, error) { + return nil, nil }, } - err1 := model.Initialize(application) - if err1 != nil { - t.Fatalf("unexpected error setting up model '%s'", err1) - } + t.Run("Testing basic create entity with a auto generating id ksuid", func(t *testing.T) { - t.Run("Testing basic create entity", func(t *testing.T) { - t.SkipNow() - entityType := "Blog" - - mockBlog := map[string]interface{}{"weos_id": "fsdf32432", "title": "New Blog", "description": "New Description", "url": "www.NewBlog.com"} - reqBytes, err := json.Marshal(mockBlog) + mockAuthor := map[string]interface{}{"firstName": "New ", "lastName": "New nEW"} + reqBytes, err := json.Marshal(mockAuthor) if err != nil { t.Fatalf("error converting payload to bytes %s", err) } - err1 := commandDispatcher.Dispatch(ctx, model.Create(ctx, reqBytes, entityType, "fsdf32432"), nil, nil, echo.New().Logger) + err1 := commandDispatcher.Dispatch(ctx, model.Create(ctx, reqBytes, contentEntity, "fsdf32432"), mockEventRepository, projectionMock, echo.New().Logger) if err1 != nil { t.Fatalf("unexpected error dispatching command '%s'", err1) } @@ -92,27 +99,42 @@ func TestCreateContentType(t *testing.T) { t.Fatalf("expected change events to be persisted '%d' got persisted '%d' times", 1, len(mockEventRepository.PersistCalls())) } }) - t.Run("Testing basic batch create", func(t *testing.T) { - t.Skipped() - entityType := "Blog" + t.Run("Testing basic create entity with a auto generating id uuid", func(t *testing.T) { - mockBlogs := [3]map[string]interface{}{ - {"title": "Blog 1", "description": "Description testing 1", "url": "www.TestBlog1.com"}, - {"title": "Blog 2", "description": "Description testing 2", "url": "www.TestBlog2.com"}, - {"title": "Blog 3", "description": "Description testing 3", "url": "www.TestBlog3.com"}, - } - reqBytes, err := json.Marshal(mockBlogs) + mockCategory := map[string]interface{}{"title": "New Blog"} + reqBytes, err := json.Marshal(mockCategory) if err != nil { t.Fatalf("error converting payload to bytes %s", err) } - err1 := commandDispatcher.Dispatch(ctx, model.CreateBatch(ctx, reqBytes, entityType), mockEventRepository, nil, echo.New().Logger) + err1 := commandDispatcher.Dispatch(ctx1, model.Create(ctx1, reqBytes, contentEntity1, "fsdf32432"), mockEventRepository, projectionMock, echo.New().Logger) if err1 != nil { t.Fatalf("unexpected error dispatching command '%s'", err1) } - if len(mockEventRepository.PersistCalls()) != 4 { - t.Fatalf("expected change events to be persisted '%d' got persisted '%d' times", 4, len(mockEventRepository.PersistCalls())) + if len(mockEventRepository.PersistCalls()) != 2 { + t.Fatalf("expected change events to be persisted '%d' got persisted '%d' times", 2, len(mockEventRepository.PersistCalls())) + } + }) + t.Run("Testing basic batch create where the id is specified but the format is not specified", func(t *testing.T) { + + mockArchives := [3]map[string]interface{}{ + {"title": "Blog 1"}, + {"title": "Blog 2"}, + {"title": "Blog 3"}, + } + reqBytes, err := json.Marshal(mockArchives) + if err != nil { + t.Fatalf("error converting payload to bytes %s", err) + } + + err1 := commandDispatcher.Dispatch(ctx2, model.CreateBatch(ctx2, reqBytes, contentEntity2), mockEventRepository, projectionMock, echo.New().Logger) + if err1 == nil { + t.Fatalf("expected error dispatching command but got nil") + } + + if len(mockEventRepository.PersistCalls()) != 2 { + t.Fatalf("expected change events to be persisted '%d' got persisted '%d' times", 2, len(mockEventRepository.PersistCalls())) } }) } diff --git a/projections/gorm.go b/projections/gorm.go index ebd52e46..ffcf4d23 100644 --- a/projections/gorm.go +++ b/projections/gorm.go @@ -293,6 +293,8 @@ func (p *GORMDB) GetEventHandler() weos.EventHandler { return err } mapPayload["sequence_no"] = event.Meta.SequenceNo + //Adding the entityid to the payload since the event payload doesnt have it + mapPayload["weos_id"] = event.Meta.EntityID bytes, _ := json.Marshal(mapPayload) err = json.Unmarshal(bytes, &eventPayload)