diff --git a/api.yaml b/api.yaml index 9c6ca107..417567b0 100755 --- a/api.yaml +++ b/api.yaml @@ -147,6 +147,15 @@ paths: operationId: Get Blogs summary: Get List of Blogs parameters: + - in: query + name: page + schema: + type: integer + - in: query + name: l + x-alias: limit + schema: + type: integer - in: query name: filters schema: diff --git a/controllers/rest/controller_standard.go b/controllers/rest/controller_standard.go index e13dc401..6f8ef72e 100644 --- a/controllers/rest/controller_standard.go +++ b/controllers/rest/controller_standard.go @@ -460,8 +460,8 @@ func (c *StandardControllers) List(app model.Service, spec *openapi3.Swagger, pa }) } //gets the limit and page from context - limit := newContext.Value("limit").(int) - page := newContext.Value("page").(int) + limit, _ := newContext.Value("limit").(int) + page, _ := newContext.Value("page").(int) if page == 0 { page = 1 } diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index ebb835b5..02e73814 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -55,7 +55,6 @@ func parseParams(c echo.Context, cc context.Context, parameter *openapi3.Paramet return nil, err } } - var val interface{} //if there is an alias name specified use that instead. The value is a json.RawMessage (not a string) if tcontextName, ok := parameter.Value.ExtensionProps.Extensions[AliasExtension]; ok { err := json.Unmarshal(tcontextName.(json.RawMessage), &contextName) @@ -63,6 +62,7 @@ func parseParams(c echo.Context, cc context.Context, parameter *openapi3.Paramet return nil, err } } + var val interface{} switch strings.ToLower(parameter.Value.In) { //parameter values stored as strings case "header": diff --git a/controllers/rest/middleware_initialize.go b/controllers/rest/middleware_initialize.go index 2306f90f..cd10808c 100644 --- a/controllers/rest/middleware_initialize.go +++ b/controllers/rest/middleware_initialize.go @@ -238,17 +238,19 @@ func AddStandardController(e *echo.Echo, pathData *openapi3.PathItem, method str //check for identifiers if identifiers != nil && len(identifiers) > 0 { for _, identifier := range identifiers { + foundIdentifier := false //check the parameters for the identifiers for _, param := range pathData.Put.Parameters { cName := param.Value.ExtensionProps.Extensions[ContextNameExtension] if identifier == param.Value.Name || (cName != nil && identifier == cName.(string)) { + foundIdentifier = true break } - if !(identifier == param.Value.Name) && !(cName != nil && identifier == cName.(string)) { - allParam = false - e.Logger.Warnf("unexpected error: a parameter for each part of the identifier must be set") - return autoConfigure, nil - } + } + if !foundIdentifier { + allParam = false + e.Logger.Warnf("unexpected error: a parameter for each part of the identifier must be set") + return autoConfigure, nil } } if allParam { @@ -356,17 +358,19 @@ func AddStandardController(e *echo.Echo, pathData *openapi3.PathItem, method str var contextName string if identifiers != nil && len(identifiers) > 0 { for _, identifier := range identifiers { + foundIdentifier := false //check the parameters for _, param := range pathData.Get.Parameters { cName := param.Value.ExtensionProps.Extensions[ContextNameExtension] if identifier == param.Value.Name || (cName != nil && identifier == cName.(string)) { + foundIdentifier = true break } - if !(identifier == param.Value.Name) && !(cName != nil && identifier == cName.(string)) { - allParam = false - e.Logger.Warnf("unexpected error: a parameter for each part of the identifier must be set") - return autoConfigure, nil - } + } + if !foundIdentifier { + allParam = false + e.Logger.Warnf("unexpected error: a parameter for each part of the identifier must be set") + return autoConfigure, nil } } } diff --git a/controllers/rest/weos_mocks_test.go b/controllers/rest/weos_mocks_test.go index 06175295..28173ad4 100644 --- a/controllers/rest/weos_mocks_test.go +++ b/controllers/rest/weos_mocks_test.go @@ -6,7 +6,7 @@ package rest_test import ( "context" "database/sql" - weoscontext "github.com/wepala/weos/context" + weosContext "github.com/wepala/weos/context" weos "github.com/wepala/weos/model" "gorm.io/gorm" "net/http" @@ -1721,10 +1721,10 @@ var _ weos.Projection = &ProjectionMock{} // } type ProjectionMock struct { // GetByEntityIDFunc mocks the GetByEntityID method. - GetByEntityIDFunc func(ctxt context.Context, contentType weoscontext.ContentType, id string) (map[string]interface{}, error) + GetByEntityIDFunc func(ctxt context.Context, contentType weosContext.ContentType, id string) (map[string]interface{}, error) // GetByKeyFunc mocks the GetByKey method. - GetByKeyFunc func(ctxt context.Context, contentType weoscontext.ContentType, identifiers map[string]interface{}) (map[string]interface{}, error) + GetByKeyFunc func(ctxt context.Context, contentType weosContext.ContentType, identifiers map[string]interface{}) (map[string]interface{}, error) // GetContentEntitiesFunc mocks the GetContentEntities method. GetContentEntitiesFunc func(ctx context.Context, page int, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) @@ -1745,7 +1745,7 @@ type ProjectionMock struct { // Ctxt is the ctxt argument value. Ctxt context.Context // ContentType is the contentType argument value. - ContentType weoscontext.ContentType + ContentType weosContext.ContentType // ID is the id argument value. ID string } @@ -1754,7 +1754,7 @@ type ProjectionMock struct { // Ctxt is the ctxt argument value. Ctxt context.Context // ContentType is the contentType argument value. - ContentType weoscontext.ContentType + ContentType weosContext.ContentType // Identifiers is the identifiers argument value. Identifiers map[string]interface{} } @@ -1798,13 +1798,13 @@ type ProjectionMock struct { } // GetByEntityID calls GetByEntityIDFunc. -func (mock *ProjectionMock) GetByEntityID(ctxt context.Context, contentType weoscontext.ContentType, id string) (map[string]interface{}, error) { +func (mock *ProjectionMock) GetByEntityID(ctxt context.Context, contentType weosContext.ContentType, id string) (map[string]interface{}, error) { if mock.GetByEntityIDFunc == nil { panic("ProjectionMock.GetByEntityIDFunc: method is nil but Projection.GetByEntityID was just called") } callInfo := struct { Ctxt context.Context - ContentType weoscontext.ContentType + ContentType weosContext.ContentType ID string }{ Ctxt: ctxt, @@ -1822,12 +1822,12 @@ func (mock *ProjectionMock) GetByEntityID(ctxt context.Context, contentType weos // len(mockedProjection.GetByEntityIDCalls()) func (mock *ProjectionMock) GetByEntityIDCalls() []struct { Ctxt context.Context - ContentType weoscontext.ContentType + ContentType weosContext.ContentType ID string } { var calls []struct { Ctxt context.Context - ContentType weoscontext.ContentType + ContentType weosContext.ContentType ID string } mock.lockGetByEntityID.RLock() @@ -1837,13 +1837,13 @@ func (mock *ProjectionMock) GetByEntityIDCalls() []struct { } // GetByKey calls GetByKeyFunc. -func (mock *ProjectionMock) GetByKey(ctxt context.Context, contentType weoscontext.ContentType, identifiers map[string]interface{}) (map[string]interface{}, error) { +func (mock *ProjectionMock) GetByKey(ctxt context.Context, contentType weosContext.ContentType, identifiers map[string]interface{}) (map[string]interface{}, error) { if mock.GetByKeyFunc == nil { panic("ProjectionMock.GetByKeyFunc: method is nil but Projection.GetByKey was just called") } callInfo := struct { Ctxt context.Context - ContentType weoscontext.ContentType + ContentType weosContext.ContentType Identifiers map[string]interface{} }{ Ctxt: ctxt, @@ -1861,12 +1861,12 @@ func (mock *ProjectionMock) GetByKey(ctxt context.Context, contentType weosconte // len(mockedProjection.GetByKeyCalls()) func (mock *ProjectionMock) GetByKeyCalls() []struct { Ctxt context.Context - ContentType weoscontext.ContentType + ContentType weosContext.ContentType Identifiers map[string]interface{} } { var calls []struct { Ctxt context.Context - ContentType weoscontext.ContentType + ContentType weosContext.ContentType Identifiers map[string]interface{} } mock.lockGetByKey.RLock() diff --git a/end2end_test.go b/end2end_test.go index c4d46881..4944e087 100644 --- a/end2end_test.go +++ b/end2end_test.go @@ -30,7 +30,6 @@ var e *echo.Echo var API api.RESTAPI var openAPI string var Developer *User -var Content *ContentType var errs error var buf bytes.Buffer var payload ContentType @@ -48,6 +47,10 @@ var binary string var dockerFile string var binaryMount string var esContainer testcontainers.Container +var limit int +var page int +var contentType string +var result api.ListApiResponse type User struct { Name string @@ -73,6 +76,7 @@ func InitializeSuite(ctx *godog.TestSuiteContext) { requests = map[string]map[string]interface{}{} contentTypeID = map[string]bool{} Developer = &User{} + result = api.ListApiResponse{} e = echo.New() e.Logger.SetOutput(&buf) os.Remove("e2e.db") @@ -122,6 +126,7 @@ func reset(ctx context.Context, sc *godog.Scenario) (context.Context, error) { requests = map[string]map[string]interface{}{} contentTypeID = map[string]bool{} Developer = &User{} + result = api.ListApiResponse{} errs = nil header = make(http.Header) rec = httptest.NewRecorder() @@ -769,6 +774,83 @@ func sojournerIsUpdatingWithId(contentType, id string) error { return nil } +func isOnTheListScreen(user, content string) error { + contentType = content + requests[strings.ToLower(contentType+"_list")] = map[string]interface{}{} + currScreen = strings.ToLower(contentType + "_list") + return nil +} + +func theItemsPerPageAre(pageLimit int) error { + limit = pageLimit + return nil +} + +func theListResultsShouldBe(details *godog.Table) error { + head := details.Rows[0].Cells + compare := map[string]interface{}{} + compareArray := []map[string]interface{}{} + + for i := 1; i < len(details.Rows); i++ { + for n, cell := range details.Rows[i].Cells { + compare[head[n].Value] = cell.Value + } + compareArray = append(compareArray, compare) + compare = map[string]interface{}{} + } + foundItems := 0 + + json.NewDecoder(rec.Body).Decode(&result) + for i, entity := range compareArray { + foundEntity := true + for key, value := range entity { + if result.Items[i][key] != value { + foundEntity = false + break + } + } + if foundEntity { + foundItems++ + } + } + if foundItems != len(compareArray) { + return fmt.Errorf("expected to find %d, got %d", len(compareArray), foundItems) + } + + return nil +} + +func thePageInTheResultShouldBe(pageResult int) error { + if result.Page != pageResult { + return fmt.Errorf("expect page to be %d, got %d", pageResult, result.Page) + } + return nil +} + +func thePageNoIs(pageNo int) error { + page = pageNo + return nil +} + +func theSearchButtonIsHit() error { + var request *http.Request + request = httptest.NewRequest("GET", "/"+strings.ToLower(contentType)+"?limit="+strconv.Itoa(limit)+"&page="+strconv.Itoa(page), nil) + request = request.WithContext(context.TODO()) + header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + request.Header = header + request.Close = true + rec = httptest.NewRecorder() + e.ServeHTTP(rec, request) + return nil +} + +func theTotalResultsShouldBe(totalResult int) error { + if result.Total != int64(totalResult) { + return fmt.Errorf("expect page to be %d, got %d", totalResult, result.Total) + } + return nil +} + func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Before(reset) //add context steps @@ -811,7 +893,13 @@ func InitializeScenario(ctx *godog.ScenarioContext) { ctx.Step(`^the service is running$`, theServiceIsRunning) ctx.Step(`^is run on the operating system "([^"]*)" as "([^"]*)"$`, isRunOnTheOperatingSystemAs) ctx.Step(`^a warning should be output because the endpoint is invalid$`, aWarningShouldBeOutputBecauseTheEndpointIsInvalid) - + ctx.Step(`^"([^"]*)" is on the "([^"]*)" list screen$`, isOnTheListScreen) + ctx.Step(`^the items per page are (\d+)$`, theItemsPerPageAre) + ctx.Step(`^the list results should be$`, theListResultsShouldBe) + ctx.Step(`^the page in the result should be (\d+)$`, thePageInTheResultShouldBe) + ctx.Step(`^the page no\. is (\d+)$`, thePageNoIs) + ctx.Step(`^the search button is hit$`, theSearchButtonIsHit) + ctx.Step(`^the total results should be (\d+)$`, theTotalResultsShouldBe) } func TestBDD(t *testing.T) { diff --git a/features/list-content.feature b/features/list-content.feature index 6986e6ac..6f38788f 100644 --- a/features/list-content.feature +++ b/features/list-content.feature @@ -57,6 +57,8 @@ Feature: List content Blog: type: object properties: + id: + type: string title: type: string description: blog title @@ -64,6 +66,8 @@ Feature: List content type: string required: - title + x-identifier: + - id Post: type: object properties: @@ -230,16 +234,17 @@ Feature: List content 200: description: Blog Deleted """ + And the service is running And blogs in the api - | id | entity id | sequence no | title | description | - | 1 | | 2 | Blog 1 | Some Blog | - | 2 | | 1 | Blog 2 | Some Blog 2 | - | 164 | | 1 | Blog 6 | Some Blog 6 | - | 3 | | 4 | Blog 3 | Some Blog 3 | - | 4 | | 1 | Blog 4 | Some Blog 4 | - | 5 | | 1 | Blog 5 | Some Blog 5 | - | 890 | | 1 | Blog 7 | Some Blog 7 | - | 1237 | | 1 | Blog 8 | Some Blog 8 | + | id | entity id | sequence no | title | description | + | 1 | | 1 | Blog 1 | Some Blog 1 | + | 2 | | 1 | Blog 2 | Some Blog 2 | + | 3 | | 1 | Blog 3 | Some Blog 3 | + | 4 | | 1 | Blog 4 | Some Blog 4 | + | 5 | | 1 | Blog 5 | Some Blog 5 | + | 6 | | 1 | Blog 6 | Some Blog 6 | + | 7 | | 1 | Blog 7 | Some Blog 7 | + | 8 | | 1 | Blog 8 | Some Blog 8 | @WEOS-1133 Scenario: Get list of items @@ -251,12 +256,12 @@ Feature: List content When the search button is hit Then a 200 response should be returned And the list results should be - | id | entity id | sequence no | title | description | - | 1 | | 2 | Blog 1 | Some Blog | - | 2 | | 1 | Blog 2 | Some Blog 2 | - | 3 | | 4 | Blog 3 | Some Blog 3 | - | 4 | | 1 | Blog 4 | Some Blog 4 | - | 5 | | 1 | Blog 5 | Some Blog 5 | + | id | title | description | + | 1 | Blog 1 | Some Blog 1 | + | 2 | Blog 2 | Some Blog 2 | + | 3 | Blog 3 | Some Blog 3 | + | 4 | Blog 4 | Some Blog 4 | + | 5 | Blog 5 | Some Blog 5 | And the total results should be 8 And the page in the result should be 1 @@ -271,9 +276,9 @@ Feature: List content When the search button is hit Then a 200 response should be returned And the list results should be - | id | entity id | sequence no | title | description | - | 3 | | 4 | Blog 3 | Some Blog 3 | - | 4 | | 1 | Blog 4 | Some Blog 4 | + | id | title | description | + | 3 | Blog 3 | Some Blog 3 | + | 4 | Blog 4 | Some Blog 4 | And the total results should be 8 And the page in the result should be 2 diff --git a/projections/gorm.go b/projections/gorm.go index 8a876b90..e9eb755b 100644 --- a/projections/gorm.go +++ b/projections/gorm.go @@ -237,7 +237,7 @@ func (p *GORMProjection) GetContentEntities(ctx context.Context, page int, limit schemes = s.Build().NewSliceOfStructs() scheme := s.Build().New() - result = p.db.Table(contentType.Name).Scopes(ContentQuery()).Model(&scheme).Count(&count).Scopes(paginate(page, limit), sort(sortOptions)).Omit("weos_id, sequence_no").Find(schemes) + result = p.db.Table(contentType.Name).Scopes(ContentQuery()).Model(&scheme).Omit("weos_id, sequence_no, table").Count(&count).Scopes(paginate(page, limit), sort(sortOptions)).Find(schemes) } bytes, err := json.Marshal(schemes) if err != nil { @@ -300,7 +300,7 @@ func NewProjection(ctx context.Context, application weos.Service, schemas map[st //https://github.com/go-gorm/gorm/issues/3585 return db } else { - return db.Preload(clause.Associations, func(tx *gorm.DB) *gorm.DB { return tx.Omit("weos_id, sequence_no") }) + return db.Preload(clause.Associations, func(tx *gorm.DB) *gorm.DB { return tx.Omit("weos_id, sequence_no, table") }) } } } diff --git a/projections/projections.go b/projections/projections.go index 8096db9c..586adb08 100644 --- a/projections/projections.go +++ b/projections/projections.go @@ -12,5 +12,5 @@ type Projection interface { type DefaultProjection struct { WEOSID string `json:"weos_id,omitempty" gorm:"unique;<-:create"` SequenceNo int64 `json:"sequence_no,omitempty"` - Table string `json:"table_alias"` + Table string `json:"table_alias,omitempty"` }