diff --git a/.github/workflows/dev.yaml b/.github/workflows/dev.yaml index a3653433..20afbd0c 100644 --- a/.github/workflows/dev.yaml +++ b/.github/workflows/dev.yaml @@ -13,6 +13,7 @@ env: SLACK_ICON: https://github.com/wepala.png?size=48 SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} SLACK_FOOTER: copyright 2022 Wepala + OAUTH_TEST_KEY: ${{ secrets.OAUTH_TEST_KEY }} jobs: build-api: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 795f86b9..5c6fae0d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,6 +11,7 @@ env: SLACK_ICON: https://github.com/wepala.png?size=48 SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} SLACK_FOOTER: copyright 2022 Wepala + OAUTH_TEST_KEY: ${{ secrets.OAUTH_TEST_KEY }} jobs: build-api: diff --git a/context/context.go b/context/context.go index 5d9a5630..d665718b 100644 --- a/context/context.go +++ b/context/context.go @@ -30,6 +30,8 @@ const FILTERS ContextKey = "_filters" const SORTS ContextKey = "_sorts" const PAYLOAD ContextKey = "_payload" const SEQUENCE_NO string = "sequence_no" +const AUTHORIZATION string = "Authorization" +const USER_ID_EXTENSION ContextKey = "X-USER-ID" //Path initializers are run per path and can be used to configure routes that are not defined in the open api spec const METHODS_FOUND ContextKey = "_methods_found" diff --git a/controllers/rest/api.go b/controllers/rest/api.go index ccbfa5f6..073ca348 100644 --- a/controllers/rest/api.go +++ b/controllers/rest/api.go @@ -284,6 +284,7 @@ func (p *RESTAPI) Initialize(ctxt context.Context) error { p.RegisterController("CreateBatchController", CreateBatchController) //register standard middleware p.RegisterMiddleware("Context", Context) + p.RegisterMiddleware("AuthorizationMiddleware", AuthorizationMiddleware) p.RegisterMiddleware("CreateMiddleware", CreateMiddleware) p.RegisterMiddleware("CreateBatchMiddleware", CreateBatchMiddleware) p.RegisterMiddleware("UpdateMiddleware", UpdateMiddleware) diff --git a/controllers/rest/controller_standard.go b/controllers/rest/controller_standard.go index b4b4a4ff..8cae27d3 100644 --- a/controllers/rest/controller_standard.go +++ b/controllers/rest/controller_standard.go @@ -4,9 +4,12 @@ import ( "encoding/json" "errors" "fmt" + "github.com/coreos/go-oidc/v3/oidc" + "io/ioutil" "net/http" "strconv" "strings" + "time" "github.com/wepala/weos/projections" @@ -664,3 +667,120 @@ func HealthCheck(api *RESTAPI, projection projections.Projection, commandDispatc } } + +//AuthorizationMiddleware handling JWT in incoming Authorization header +func AuthorizationMiddleware(api *RESTAPI, projection projections.Projection, commandDispatcher model.CommandDispatcher, eventSource model.EventRepository, entityFactory model.EntityFactory, path *openapi3.PathItem, operation *openapi3.Operation) echo.MiddlewareFunc { + var openIdConnectUrl string + securityCheck := true + var verifiers []*oidc.IDTokenVerifier + algs := []string{"RS256", "RS384", "RS512", "HS256"} + if operation.Security != nil && len(*operation.Security) == 0 { + securityCheck = false + } + for _, schemes := range api.Swagger.Components.SecuritySchemes { + //get the open id connect url + if openIdUrl, ok := schemes.Value.ExtensionProps.Extensions[OPENIDCONNECTURLEXTENSION]; ok { + err := json.Unmarshal(openIdUrl.(json.RawMessage), &openIdConnectUrl) + if err != nil { + api.EchoInstance().Logger.Errorf("unable to unmarshal open id connect url '%s'", err) + } else { + //check if it is a valid open id connect url + if !strings.Contains(openIdConnectUrl, ".well-known/openid-configuration") { + api.EchoInstance().Logger.Warnf("invalid open id connect url: %s", openIdConnectUrl) + } else { + //get the Jwk url from open id connect url + jwksUrl, err := GetJwkUrl(openIdConnectUrl) + if err != nil { + api.EchoInstance().Logger.Errorf("unexpected error getting the jwks url: %s", err) + } else { + //by default skipExpiryCheck is false meaning it will not run an expiry check + skipExpiryCheck := false + //get skipexpirycheck that is an extension in the openapi spec + if expireCheck, ok := schemes.Value.ExtensionProps.Extensions[SKIPEXPIRYCHECKEXTENSION]; ok { + err := json.Unmarshal(expireCheck.(json.RawMessage), &skipExpiryCheck) + if err != nil { + api.EchoInstance().Logger.Errorf("unable to unmarshal skip expiry '%s'", err) + } + } + //create key set and verifier + keySet := oidc.NewRemoteKeySet(context.Background(), jwksUrl) + tokenVerifier := oidc.NewVerifier(openIdConnectUrl, keySet, &oidc.Config{ + ClientID: "", + SupportedSigningAlgs: algs, + SkipClientIDCheck: true, + SkipExpiryCheck: skipExpiryCheck, + SkipIssuerCheck: true, + Now: time.Now, + }) + verifiers = append(verifiers, tokenVerifier) + } + } + + } + } + + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(ctxt echo.Context) error { + var err error + if !securityCheck { + return next(ctxt) + } + if len(verifiers) == 0 { + api.e.Logger.Errorf("unexpected error no verifiers were set") + return NewControllerError("unexpected error no verifiers were set", nil, http.StatusBadRequest) + } + newContext := ctxt.Request().Context() + token, ok := newContext.Value(weoscontext.AUTHORIZATION).(string) + if !ok || token == "" { + api.e.Logger.Errorf("no JWT token was found") + return NewControllerError("no JWT token was found", nil, http.StatusUnauthorized) + } + jwtToken := strings.Replace(token, "Bearer ", "", -1) + var idToken *oidc.IDToken + for _, tokenVerifier := range verifiers { + idToken, err = tokenVerifier.Verify(newContext, jwtToken) + if err != nil || idToken == nil { + api.e.Logger.Errorf(err.Error()) + return NewControllerError("unexpected error verifying token", err, http.StatusUnauthorized) + } + } + + newContext = context.WithValue(newContext, weoscontext.USER_ID_EXTENSION, idToken.Subject) + request := ctxt.Request().WithContext(newContext) + ctxt.SetRequest(request) + return next(ctxt) + + } + } +} + +//GetJwkUrl fetches the jwk url from the open id connect url +func GetJwkUrl(openIdUrl string) (string, error) { + //fetches the response from the connect id url + resp, err := http.Get(openIdUrl) + if err != nil || resp == nil { + return "", fmt.Errorf("unexpected error fetching open id connect url: %s", err) + } + defer resp.Body.Close() + // reads the body + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("unable to read response body: %v", err) + } + //check the response status + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("expected open id connect url response code to be %d got %d ", http.StatusOK, resp.StatusCode) + } + // unmarshall the body to a struct we can use to find the jwk uri + var info map[string]interface{} + err = json.Unmarshal(body, &info) + if err != nil { + return "", fmt.Errorf("unexpected error unmarshalling open id connect url response %s", err) + } + if info["jwks_uri"] == nil || info["jwks_uri"].(string) == "" { + return "", fmt.Errorf("no jwks uri found") + } + return info["jwks_uri"].(string), nil +} diff --git a/controllers/rest/controller_standard_test.go b/controllers/rest/controller_standard_test.go index 997df94a..f551a1b4 100644 --- a/controllers/rest/controller_standard_test.go +++ b/controllers/rest/controller_standard_test.go @@ -115,7 +115,7 @@ func TestStandardControllers_Create(t *testing.T) { Property: mockPayload, } - projections := &ProjectionMock{ + projections := &GormProjectionMock{ GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { if ctx == nil { t.Errorf("expected to find context but got nil") @@ -281,7 +281,7 @@ func TestStandardControllers_CreateBatch(t *testing.T) { }, } - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return nil, nil }, @@ -457,7 +457,7 @@ func TestStandardControllers_Update(t *testing.T) { mockEntity.SequenceNo = int64(1) mockEntity.Property = mockBlog - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return nil, nil }, @@ -575,7 +575,7 @@ func TestStandardControllers_View(t *testing.T) { }} t.Run("Testing the generic view endpoint", func(t *testing.T) { - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{ "id": "1", @@ -628,7 +628,7 @@ func TestStandardControllers_View(t *testing.T) { } }) t.Run("Testing view with entity id", func(t *testing.T) { - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { if entityFactory == nil { t.Errorf("expected to find entity factory got nil") @@ -696,7 +696,7 @@ func TestStandardControllers_View(t *testing.T) { } }) t.Run("invalid entity id should return 404", func(t *testing.T) { - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{ "id": "1", @@ -771,7 +771,7 @@ func TestStandardControllers_View(t *testing.T) { } }) t.Run("invalid numeric entity id should return 404", func(t *testing.T) { - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{ "id": "1", @@ -846,7 +846,7 @@ func TestStandardControllers_View(t *testing.T) { } }) t.Run("view with sequence no", func(t *testing.T) { - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{ "id": "1", @@ -926,7 +926,7 @@ func TestStandardControllers_View(t *testing.T) { } }) t.Run("view with invalid sequence no", func(t *testing.T) { - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return map[string]interface{}{ "id": "1", @@ -1019,7 +1019,7 @@ func TestStandardControllers_List(t *testing.T) { array := []map[string]interface{}{} array = append(array, mockBlog, mockBlog1) - mockProjection := &ProjectionMock{ + mockProjection := &GormProjectionMock{ GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { return array, 2, nil }, @@ -1133,7 +1133,7 @@ func TestStandardControllers_ListFilters(t *testing.T) { array := []map[string]interface{}{} array = append(array, mockBlog, mockBlog1) - mockProjection := &ProjectionMock{ + mockProjection := &GormProjectionMock{ GetContentEntitiesFunc: func(ctx context.Context, entityFactory model.EntityFactory, page, limit int, query string, sortOptions map[string]string, filterOptions map[string]interface{}) ([]map[string]interface{}, int64, error) { if entityFactory == nil { t.Errorf("no entity factory found") @@ -1382,7 +1382,7 @@ func TestStandardControllers_FormUrlEncoded_Create(t *testing.T) { Property: mockPayload, } - projections := &ProjectionMock{ + projections := &GormProjectionMock{ GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { return mockContentEntity, nil }, @@ -1540,7 +1540,7 @@ func TestStandardControllers_FormData_Create(t *testing.T) { Property: mockPayload, } - projections := &ProjectionMock{ + projections := &GormProjectionMock{ GetContentEntityFunc: func(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { return mockContentEntity, nil }, @@ -1690,7 +1690,7 @@ func TestStandardControllers_DeleteEtag(t *testing.T) { mockEntity.SequenceNo = int64(1) mockEntity.Property = mockBlog - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return nil, nil }, @@ -1799,7 +1799,7 @@ func TestStandardControllers_DeleteID(t *testing.T) { }, } - projection := &ProjectionMock{ + projection := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return mockInterface, nil }, @@ -1852,7 +1852,7 @@ func TestStandardControllers_DeleteID(t *testing.T) { }, } - projection1 := &ProjectionMock{ + projection1 := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return mockInterface1, nil }, @@ -1907,7 +1907,7 @@ func TestStandardControllers_DeleteID(t *testing.T) { err1 := fmt.Errorf("this is an error") - projection1 := &ProjectionMock{ + projection1 := &GormProjectionMock{ GetByKeyFunc: func(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { return nil, err1 }, @@ -1948,3 +1948,61 @@ func TestStandardControllers_DeleteID(t *testing.T) { } }) } + +func TestStandardControllers_AuthenticateMiddleware(t *testing.T) { + //instantiate api + api, err := rest.New("./fixtures/blog-security.yaml") + if err != nil { + t.Fatalf("unexpected error loading api '%s'", err) + } + err = api.Initialize(context.TODO()) + if err != nil { + t.Fatalf("un expected error initializing api '%s'", err) + } + e := api.EchoInstance() + + t.Run("no jwt token added when required", func(t *testing.T) { + description := "testing 1st blog description" + mockBlog := &TestBlog{Description: &description} + reqBytes, err := json.Marshal(mockBlog) + if err != nil { + t.Fatalf("error setting up request %s", err) + } + body := bytes.NewReader(reqBytes) + resp := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/blogs", body) + req.Header.Set("Content-Type", "application/json") + e.ServeHTTP(resp, req) + if resp.Result().StatusCode != http.StatusUnauthorized { + t.Errorf("expected the response code to be %d, got %d", http.StatusUnauthorized, resp.Result().StatusCode) + } + }) + t.Run("security parameter array is empty", func(t *testing.T) { + resp := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/health", nil) + e.ServeHTTP(resp, req) + if resp.Result().StatusCode != http.StatusOK { + t.Errorf("expected the response code to be %d, got %d", http.StatusOK, resp.Result().StatusCode) + } + }) + t.Run("jwt token added", func(t *testing.T) { + description := "testing 1st blog description" + url := "www.example.com" + title := "example" + mockBlog := &TestBlog{Title: &title, Url: &url, Description: &description} + reqBytes, err := json.Marshal(mockBlog) + if err != nil { + t.Fatalf("error setting up request %s", err) + } + token := os.Getenv("OAUTH_TEST_KEY") + body := bytes.NewReader(reqBytes) + resp := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/blogs", body) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + e.ServeHTTP(resp, req) + if resp.Result().StatusCode != http.StatusCreated { + t.Errorf("expected the response code to be %d, got %d", http.StatusCreated, resp.Result().StatusCode) + } + }) +} diff --git a/controllers/rest/fixtures/blog-security.yaml b/controllers/rest/fixtures/blog-security.yaml new file mode 100644 index 00000000..2b8be9fb --- /dev/null +++ b/controllers/rest/fixtures/blog-security.yaml @@ -0,0 +1,718 @@ +openapi: 3.0.3 +info: + title: Blog + description: Blog example + version: 1.0.0 +servers: + - url: https://prod1.weos.sh/blog/dev + description: WeOS Dev + - url: https://prod1.weos.sh/blog/v1 +x-weos-config: + event-source: + - title: default + driver: service + endpoint: https://prod1.weos.sh/events/v1 + - title: event + driver: sqlite3 + database: test.db + database: + driver: sqlite3 + database: test.db + databases: + - title: default + driver: sqlite3 + database: test.db + rest: + middleware: + - RequestID + - Recover + - ZapLogger +components: + securitySchemes: + Auth0: + type: openIdConnect + openIdConnectUrl: https://dev-bhjqt6zc.us.auth0.com/.well-known/openid-configuration + skipExpiryCheck: true + schemas: + Category: + type: object + properties: + title: + type: string + description: + type: string + required: + - title + x-identifier: + - title + Author: + type: object + properties: + id: + type: string + format: ksuid + firstName: + type: string + lastName: + type: string + email: + type: string + format: email + required: + - firstName + - lastName + x-identifier: + - id + - email + Blog: + type: object + properties: + url: + type: string + format: uri + title: + type: string + description: + type: string + status: + type: string + nullable: true + enum: + - null + - unpublished + - published + image: + type: string + format: byte + categories: + type: array + items: + $ref: "#/components/schemas/Category" + posts: + type: array + items: + $ref: "#/components/schemas/Post" + lastUpdated: + type: string + format: date-time + created: + type: string + format: date-time + required: + - title + - url + Post: + type: object + properties: + title: + type: string + description: + type: string + author: + $ref: "#/components/schemas/Author" + created: + type: string + format: date-time +security: + - Auth0: ["email","name"] + +paths: + /health: + summary: Health Check + get: + security: [] + x-controller: HealthCheck + x-middleware: + - Recover + - AuthorizationMiddleware + responses: + 200: + description: Health Response + 500: + description: API Internal Error + /blogs: + parameters: + - in: header + name: someHeader + schema: + type: string + - in: header + name: someOtherHeader + schema: + type: string + x-context-name: soh + - in: header + name: X-Account-Id + schema: + type: string + x-context-name: AccountID + - in: query + name: q + schema: + type: string + - in: query + name: cost + schema: + type: number + - in: query + name: leverage + schema: + type: number + format: double + post: + operationId: Add Blog + summary: Create Blog + x-projection: Default + x-event-dispatcher: Default + x-command-disptacher: Default + parameters: + - in: header + name: Authorization + schema: + type: string + x-middleware: + - AuthorizationMiddleware + 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" + multipart/form-data: + schema: + $ref: "#/components/schemas/Blog" + responses: + 201: + description: Add Blog to Aggregator + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + get: + 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: + type: array + items: + type: object + properties: + field: + type: string + operator: + type: string + values: + type: array + items: + type: string + + required: false + description: query string + x-context: + filters: + - field: status + operator: eq + values: + - Active + - field: lastUpdated + operator: between + values: + - 2021-12-17 15:46:00 + - 2021-12-18 15:46:00 + - field: categories + operator: in + values: + - Technology + - Javascript + sorts: + - field: title + order: asc + page: 1 + limit: 10 + responses: + 200: + description: List of blogs + content: + application/json: + schema: + type: object + properties: + total: + type: integer + page: + type: integer + blogs: + type: array + x-alias: items + items: + $ref: "#/components/schemas/Blog" + put: + operationId: Import blogs + requestBody: + content: + text/csv: + schema: + type: string + format: binary + responses: + 201: + description: items created + + /blogs/{id}: + parameters: + - in: query + name: sequence_no + schema: + type: integer + - in: query + name: use_entity_id + schema: + type: boolean + get: + parameters: + - in: path + name: id + schema: + type: string + required: true + description: blog id + - in: header + name: If-Match + schema: + type: string + required: false + - in: query + name: cost + schema: + type: number + - in: query + name: leverage + schema: + type: number + format: double + summary: Get Blog by id + operationId: Get Blog + responses: + 200: + description: Blog details without any supporting collections + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + put: + parameters: + - in: path + name: id + schema: + type: string + required: true + description: blog id + - in: header + name: If-Match + schema: + type: string + - in: query + name: cost + schema: + type: number + - in: query + name: leverage + schema: + type: number + format: double + summary: Update blog details + operationId: Update Blog + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + responses: + 200: + description: Update Blog + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + delete: + parameters: + - in: path + name: id + schema: + type: string + required: true + description: blog id + - in: header + name: If-Match + schema: + type: string + x-schema: "Blog" + summary: Delete blog + operationId: Delete Blog + responses: + 200: + description: Blog Deleted + + /posts/: + post: + operationId: Create Blog Post + summary: Create Blog Post + requestBody: + description: Post details + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Post" + responses: + 201: + description: Post + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + put: + operationId: Import Blog Posts + summary: Import Blog Posts + requestBody: + description: List of posts to import + required: true + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Post" + application/x-www-form-urlencoded: + schema: + type: array + items: + $ref: "#/components/schemas/Post" + responses: + 201: + description: Post + get: + operationId: Get Posts + summary: Get a blog's list of posts + parameters: + - in: query + name: q + schema: + type: string + required: false + description: query string + responses: + 200: + description: List of blog posts + content: + application/json: + schema: + type: object + properties: + total: + type: integer + page: + type: integer + items: + type: array + items: + $ref: "#/components/schemas/Post" + + /posts/{id}: + get: + parameters: + - in: path + name: id + schema: + type: string + required: true + summary: Get blog post by id + responses: + 200: + description: Get blog post information + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + put: + parameters: + - in: path + name: id + schema: + type: string + required: true + summary: Update post + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + responses: + 200: + description: Get blog post information + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + delete: + parameters: + - in: path + name: id + schema: + type: string + required: true + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + summary: Delete post + responses: + 200: + description: Delete post + + + /categories/: + post: + operationId: Create Blog Category + summary: Create Blog Category + requestBody: + description: Post details + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Category" + responses: + 201: + description: Post + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + get: + operationId: Get Categories + summary: Get a blog's list of categories + parameters: + - in: query + name: q + schema: + type: string + required: false + description: query string + responses: + 200: + description: List of blog categories + content: + application/json: + schema: + type: object + properties: + total: + type: integer + page: + type: integer + items: + type: array + items: + $ref: "#/components/schemas/Category" + + /categories/{title}: + get: + parameters: + - in: path + name: title + schema: + type: string + required: true + summary: Get blog category by title + responses: + 200: + description: Get blog category information + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + put: + parameters: + - in: path + name: title + schema: + type: string + required: true + summary: Update category + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + responses: + 200: + description: Get blog category information + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + delete: + parameters: + - in: path + name: title + schema: + type: string + required: true + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + summary: Delete category + responses: + 200: + description: Delete category + + /authors/: + post: + operationId: Create Blog Author + summary: Create Blog Author + requestBody: + description: Author details + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Author" + responses: + 201: + description: Post + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + get: + operationId: Get Authors + summary: Get a blog's list of authors + parameters: + - in: query + name: q + schema: + type: string + required: false + description: query string + responses: + 200: + description: List of blog authors + content: + application/json: + schema: + type: object + properties: + total: + type: integer + page: + type: integer + items: + type: array + items: + $ref: "#/components/schemas/Author" + + /authors/{id}: + get: + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: header + name: email + schema: + type: string + format: email + required: true + summary: Get Author by email and id + responses: + 200: + description: Get author information + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + put: + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: header + name: email + schema: + type: string + format: email + required: true + summary: Update Author details + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + responses: + 200: + description: Author details + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + delete: + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: header + name: email + schema: + type: string + format: email + required: true + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + summary: Delete author + responses: + 200: + description: Delete author diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index 1b53ae74..ff34b1e5 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -125,7 +125,7 @@ func parseParams(c echo.Context, cc context.Context, parameter *openapi3.Paramet if err == nil { val = v } - case "If-Match", "If-None-Match": //default type is string + case "If-Match", "If-None-Match", "Authorization": //default type is string default: var filters map[string]interface{} filters = map[string]interface{}{} diff --git a/controllers/rest/openapi_extensions.go b/controllers/rest/openapi_extensions.go index 38e8981a..a7a40bda 100644 --- a/controllers/rest/openapi_extensions.go +++ b/controllers/rest/openapi_extensions.go @@ -35,3 +35,9 @@ const CommandDispatcherExtension = "x-command-dispatcher" //EventStoreExtension set custom event store const EventStoreExtension = "x-event-store" + +//OPENIDCONNECTURLEXTENSION set the open id connect url +const OPENIDCONNECTURLEXTENSION = "openIdConnectUrl" + +//SKIPEXPIRYCHECKEXTENSION set the expiry check +const SKIPEXPIRYCHECKEXTENSION = "skipExpiryCheck" diff --git a/controllers/rest/operation_initializers_test.go b/controllers/rest/operation_initializers_test.go index d6253c83..a8eef3ba 100644 --- a/controllers/rest/operation_initializers_test.go +++ b/controllers/rest/operation_initializers_test.go @@ -153,7 +153,7 @@ paths: return nil }}) api.RegisterEventStore("HealthCheck", &EventRepositoryMock{}) - api.RegisterProjection("HealthCheck", &ProjectionMock{}) + api.RegisterProjection("HealthCheck", &GormProjectionMock{}) t.Run("attach user defined controller", func(t *testing.T) { ctxt, err := rest.UserDefinedInitializer(baseCtxt, api, "/health", http.MethodGet, api.Swagger, api.Swagger.Paths["/health"], api.Swagger.Paths["/health"].Get) diff --git a/controllers/rest/weos_mocks_test.go b/controllers/rest/weos_mocks_test.go index 38685b4e..612ce8b1 100644 --- a/controllers/rest/weos_mocks_test.go +++ b/controllers/rest/weos_mocks_test.go @@ -550,41 +550,47 @@ func (mock *EventRepositoryMock) ReplayEventsCalls() []struct { return calls } -// Ensure, that ProjectionMock does implement model.Projection. +// Ensure, that GormProjectionMock does implement model.GormProjection. // If this is not the case, regenerate this file with moq. -var _ model.Projection = &ProjectionMock{} +var _ model.GormProjection = &GormProjectionMock{} -// ProjectionMock is a mock implementation of model.Projection. +// GormProjectionMock is a mock implementation of model.GormProjection. // -// func TestSomethingThatUsesProjection(t *testing.T) { +// func TestSomethingThatUsesGormProjection(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") -// }, -// 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.GormProjection +// mockedGormProjection := &GormProjectionMock{ +// DBFunc: func() *gorm.DB { +// panic("mock out the DB method") +// }, +// 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") +// }, +// 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 mockedGormProjection in code that requires model.GormProjection +// // and then make assertions. // -// } -type ProjectionMock struct { +// } +type GormProjectionMock struct { + // DBFunc mocks the DB method. + DBFunc func() *gorm.DB + // GetByEntityIDFunc mocks the GetByEntityID method. GetByEntityIDFunc func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) @@ -605,6 +611,9 @@ type ProjectionMock struct { // calls tracks calls to the methods. calls struct { + // DB holds details about calls to the DB method. + DB []struct { + } // GetByEntityID holds details about calls to the GetByEntityID method. GetByEntityID []struct { // Ctxt is the ctxt argument value. @@ -662,6 +671,7 @@ type ProjectionMock struct { DeletedFields map[string][]string } } + lockDB sync.RWMutex lockGetByEntityID sync.RWMutex lockGetByKey sync.RWMutex lockGetContentEntities sync.RWMutex @@ -670,10 +680,36 @@ type ProjectionMock struct { lockMigrate sync.RWMutex } +// DB calls DBFunc. +func (mock *GormProjectionMock) DB() *gorm.DB { + if mock.DBFunc == nil { + panic("GormProjectionMock.DBFunc: method is nil but GormProjection.DB was just called") + } + callInfo := struct { + }{} + mock.lockDB.Lock() + mock.calls.DB = append(mock.calls.DB, callInfo) + mock.lockDB.Unlock() + return mock.DBFunc() +} + +// DBCalls gets all the calls that were made to DB. +// Check the length with: +// len(mockedGormProjection.DBCalls()) +func (mock *GormProjectionMock) DBCalls() []struct { +} { + var calls []struct { + } + mock.lockDB.RLock() + calls = mock.calls.DB + mock.lockDB.RUnlock() + return calls +} + // GetByEntityID calls GetByEntityIDFunc. -func (mock *ProjectionMock) GetByEntityID(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { +func (mock *GormProjectionMock) GetByEntityID(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { if mock.GetByEntityIDFunc == nil { - panic("ProjectionMock.GetByEntityIDFunc: method is nil but Projection.GetByEntityID was just called") + panic("GormProjectionMock.GetByEntityIDFunc: method is nil but GormProjection.GetByEntityID was just called") } callInfo := struct { Ctxt context.Context @@ -692,8 +728,8 @@ func (mock *ProjectionMock) GetByEntityID(ctxt context.Context, entityFactory mo // GetByEntityIDCalls gets all the calls that were made to GetByEntityID. // Check the length with: -// len(mockedProjection.GetByEntityIDCalls()) -func (mock *ProjectionMock) GetByEntityIDCalls() []struct { +// len(mockedGormProjection.GetByEntityIDCalls()) +func (mock *GormProjectionMock) GetByEntityIDCalls() []struct { Ctxt context.Context EntityFactory model.EntityFactory ID string @@ -710,9 +746,9 @@ func (mock *ProjectionMock) GetByEntityIDCalls() []struct { } // GetByKey calls GetByKeyFunc. -func (mock *ProjectionMock) GetByKey(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { +func (mock *GormProjectionMock) GetByKey(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { if mock.GetByKeyFunc == nil { - panic("ProjectionMock.GetByKeyFunc: method is nil but Projection.GetByKey was just called") + panic("GormProjectionMock.GetByKeyFunc: method is nil but GormProjection.GetByKey was just called") } callInfo := struct { Ctxt context.Context @@ -731,8 +767,8 @@ func (mock *ProjectionMock) GetByKey(ctxt context.Context, entityFactory model.E // GetByKeyCalls gets all the calls that were made to GetByKey. // Check the length with: -// len(mockedProjection.GetByKeyCalls()) -func (mock *ProjectionMock) GetByKeyCalls() []struct { +// len(mockedGormProjection.GetByKeyCalls()) +func (mock *GormProjectionMock) GetByKeyCalls() []struct { Ctxt context.Context EntityFactory model.EntityFactory Identifiers map[string]interface{} @@ -749,9 +785,9 @@ func (mock *ProjectionMock) GetByKeyCalls() []struct { } // GetContentEntities calls GetContentEntitiesFunc. -func (mock *ProjectionMock) GetContentEntities(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) { +func (mock *GormProjectionMock) GetContentEntities(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) { if mock.GetContentEntitiesFunc == nil { - panic("ProjectionMock.GetContentEntitiesFunc: method is nil but Projection.GetContentEntities was just called") + panic("GormProjectionMock.GetContentEntitiesFunc: method is nil but GormProjection.GetContentEntities was just called") } callInfo := struct { Ctx context.Context @@ -778,8 +814,8 @@ func (mock *ProjectionMock) GetContentEntities(ctx context.Context, entityFactor // GetContentEntitiesCalls gets all the calls that were made to GetContentEntities. // Check the length with: -// len(mockedProjection.GetContentEntitiesCalls()) -func (mock *ProjectionMock) GetContentEntitiesCalls() []struct { +// len(mockedGormProjection.GetContentEntitiesCalls()) +func (mock *GormProjectionMock) GetContentEntitiesCalls() []struct { Ctx context.Context EntityFactory model.EntityFactory Page int @@ -804,9 +840,9 @@ func (mock *ProjectionMock) GetContentEntitiesCalls() []struct { } // GetContentEntity calls GetContentEntityFunc. -func (mock *ProjectionMock) GetContentEntity(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { +func (mock *GormProjectionMock) GetContentEntity(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { if mock.GetContentEntityFunc == nil { - panic("ProjectionMock.GetContentEntityFunc: method is nil but Projection.GetContentEntity was just called") + panic("GormProjectionMock.GetContentEntityFunc: method is nil but GormProjection.GetContentEntity was just called") } callInfo := struct { Ctx context.Context @@ -825,8 +861,8 @@ func (mock *ProjectionMock) GetContentEntity(ctx context.Context, entityFactory // GetContentEntityCalls gets all the calls that were made to GetContentEntity. // Check the length with: -// len(mockedProjection.GetContentEntityCalls()) -func (mock *ProjectionMock) GetContentEntityCalls() []struct { +// len(mockedGormProjection.GetContentEntityCalls()) +func (mock *GormProjectionMock) GetContentEntityCalls() []struct { Ctx context.Context EntityFactory model.EntityFactory WeosID string @@ -843,9 +879,9 @@ func (mock *ProjectionMock) GetContentEntityCalls() []struct { } // GetEventHandler calls GetEventHandlerFunc. -func (mock *ProjectionMock) GetEventHandler() model.EventHandler { +func (mock *GormProjectionMock) GetEventHandler() model.EventHandler { if mock.GetEventHandlerFunc == nil { - panic("ProjectionMock.GetEventHandlerFunc: method is nil but Projection.GetEventHandler was just called") + panic("GormProjectionMock.GetEventHandlerFunc: method is nil but GormProjection.GetEventHandler was just called") } callInfo := struct { }{} @@ -857,8 +893,8 @@ func (mock *ProjectionMock) GetEventHandler() model.EventHandler { // GetEventHandlerCalls gets all the calls that were made to GetEventHandler. // Check the length with: -// len(mockedProjection.GetEventHandlerCalls()) -func (mock *ProjectionMock) GetEventHandlerCalls() []struct { +// len(mockedGormProjection.GetEventHandlerCalls()) +func (mock *GormProjectionMock) GetEventHandlerCalls() []struct { } { var calls []struct { } @@ -869,9 +905,9 @@ func (mock *ProjectionMock) GetEventHandlerCalls() []struct { } // Migrate calls MigrateFunc. -func (mock *ProjectionMock) Migrate(ctx context.Context, builders map[string]ds.Builder, deletedFields map[string][]string) error { +func (mock *GormProjectionMock) 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") + panic("GormProjectionMock.MigrateFunc: method is nil but GormProjection.Migrate was just called") } callInfo := struct { Ctx context.Context @@ -890,8 +926,8 @@ func (mock *ProjectionMock) Migrate(ctx context.Context, builders map[string]ds. // MigrateCalls gets all the calls that were made to Migrate. // Check the length with: -// len(mockedProjection.MigrateCalls()) -func (mock *ProjectionMock) MigrateCalls() []struct { +// len(mockedGormProjection.MigrateCalls()) +func (mock *GormProjectionMock) MigrateCalls() []struct { Ctx context.Context Builders map[string]ds.Builder DeletedFields map[string][]string @@ -907,7 +943,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{} diff --git a/go.mod b/go.mod index d686961b..24abb359 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/wepala/weos go 1.16 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/jinzhu/inflection v1.0.0 diff --git a/go.sum b/go.sum index 1df06ecc..00f76fad 100644 --- a/go.sum +++ b/go.sum @@ -191,7 +191,10 @@ github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgU github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw= +github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -878,6 +881,7 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -890,6 +894,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1056,6 +1061,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -1129,6 +1135,7 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= diff --git a/model/domain_service_test.go b/model/domain_service_test.go index d03e31a7..b5f2397f 100644 --- a/model/domain_service_test.go +++ b/model/domain_service_test.go @@ -193,7 +193,7 @@ func TestDomainService_Update(t *testing.T) { } existingBlog.SequenceNo = int64(1) - projectionMock := &ProjectionMock{ + projectionMock := &GormProjectionMock{ GetContentEntityFunc: func(ctx context3.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { if entityFactory == nil { return nil, fmt.Errorf("expected entity factory got nil") @@ -342,7 +342,7 @@ func TestDomainService_UpdateCompoundPrimaryKeyID(t *testing.T) { dService := model.NewDomainService(newContext, mockEventRepository, nil, nil) existingBlog, err := dService.Create(newContext, reqBytes, entityType) - projectionMock := &ProjectionMock{ + projectionMock := &GormProjectionMock{ GetContentEntityFunc: func(ctx context3.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { return existingBlog, nil }, @@ -444,7 +444,7 @@ func TestDomainService_UpdateCompoundPrimaryKeyGuidTitle(t *testing.T) { dService := model.NewDomainService(newContext, mockEventRepository, nil, nil) existingBlog, err := dService.Create(newContext, reqBytes, entityType) - projectionMock := &ProjectionMock{ + projectionMock := &GormProjectionMock{ GetContentEntityFunc: func(ctx context3.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { return existingBlog, nil }, @@ -553,7 +553,7 @@ func TestDomainService_UpdateWithoutIdentifier(t *testing.T) { dService := model.NewDomainService(newContext, mockEventRepository, nil, nil) existingBlog, err := dService.Create(newContext, reqBytes, entityType) - projectionMock := &ProjectionMock{ + projectionMock := &GormProjectionMock{ GetContentEntityFunc: func(ctx context3.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { return existingBlog, nil }, @@ -631,7 +631,7 @@ func TestDomainService_Delete(t *testing.T) { dService := model.NewDomainService(newContext, mockEventRepository, nil, nil) existingBlog, _ := dService.Create(newContext, reqBytes, entityType) - projectionMock := &ProjectionMock{ + projectionMock := &GormProjectionMock{ GetContentEntityFunc: func(ctx context3.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { return existingBlog, nil }, diff --git a/model/interfaces.go b/model/interfaces.go index 9fd5fb41..18ea5479 100644 --- a/model/interfaces.go +++ b/model/interfaces.go @@ -1,11 +1,11 @@ package model -//go:generate moq -out temp_mocks_test.go -pkg model_test . Projection +//go:generate moq -out temp_mocks_test.go -pkg model_test . GormProjection import ( ds "github.com/ompluscator/dynamic-struct" "golang.org/x/net/context" - "time" "gorm.io/gorm" + "time" ) type CommandDispatcher interface { diff --git a/model/mocks_test.go b/model/mocks_test.go index 4d6df5c5..f0046990 100644 --- a/model/mocks_test.go +++ b/model/mocks_test.go @@ -550,41 +550,47 @@ func (mock *EventRepositoryMock) ReplayEventsCalls() []struct { return calls } -// Ensure, that ProjectionMock does implement model.Projection. +// Ensure, that GormProjectionMock does implement model.GormProjection. // If this is not the case, regenerate this file with moq. -var _ model.Projection = &ProjectionMock{} +var _ model.GormProjection = &GormProjectionMock{} -// ProjectionMock is a mock implementation of model.Projection. +// GormProjectionMock is a mock implementation of model.GormProjection. // -// func TestSomethingThatUsesProjection(t *testing.T) { +// func TestSomethingThatUsesGormProjection(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") -// }, -// 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.GormProjection +// mockedGormProjection := &GormProjectionMock{ +// DBFunc: func() *gorm.DB { +// panic("mock out the DB method") +// }, +// 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") +// }, +// 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 mockedGormProjection in code that requires model.GormProjection +// // and then make assertions. // -// } -type ProjectionMock struct { +// } +type GormProjectionMock struct { + // DBFunc mocks the DB method. + DBFunc func() *gorm.DB + // GetByEntityIDFunc mocks the GetByEntityID method. GetByEntityIDFunc func(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) @@ -605,6 +611,9 @@ type ProjectionMock struct { // calls tracks calls to the methods. calls struct { + // DB holds details about calls to the DB method. + DB []struct { + } // GetByEntityID holds details about calls to the GetByEntityID method. GetByEntityID []struct { // Ctxt is the ctxt argument value. @@ -662,6 +671,7 @@ type ProjectionMock struct { DeletedFields map[string][]string } } + lockDB sync.RWMutex lockGetByEntityID sync.RWMutex lockGetByKey sync.RWMutex lockGetContentEntities sync.RWMutex @@ -670,10 +680,36 @@ type ProjectionMock struct { lockMigrate sync.RWMutex } +// DB calls DBFunc. +func (mock *GormProjectionMock) DB() *gorm.DB { + if mock.DBFunc == nil { + panic("GormProjectionMock.DBFunc: method is nil but GormProjection.DB was just called") + } + callInfo := struct { + }{} + mock.lockDB.Lock() + mock.calls.DB = append(mock.calls.DB, callInfo) + mock.lockDB.Unlock() + return mock.DBFunc() +} + +// DBCalls gets all the calls that were made to DB. +// Check the length with: +// len(mockedGormProjection.DBCalls()) +func (mock *GormProjectionMock) DBCalls() []struct { +} { + var calls []struct { + } + mock.lockDB.RLock() + calls = mock.calls.DB + mock.lockDB.RUnlock() + return calls +} + // GetByEntityID calls GetByEntityIDFunc. -func (mock *ProjectionMock) GetByEntityID(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { +func (mock *GormProjectionMock) GetByEntityID(ctxt context.Context, entityFactory model.EntityFactory, id string) (map[string]interface{}, error) { if mock.GetByEntityIDFunc == nil { - panic("ProjectionMock.GetByEntityIDFunc: method is nil but Projection.GetByEntityID was just called") + panic("GormProjectionMock.GetByEntityIDFunc: method is nil but GormProjection.GetByEntityID was just called") } callInfo := struct { Ctxt context.Context @@ -692,8 +728,8 @@ func (mock *ProjectionMock) GetByEntityID(ctxt context.Context, entityFactory mo // GetByEntityIDCalls gets all the calls that were made to GetByEntityID. // Check the length with: -// len(mockedProjection.GetByEntityIDCalls()) -func (mock *ProjectionMock) GetByEntityIDCalls() []struct { +// len(mockedGormProjection.GetByEntityIDCalls()) +func (mock *GormProjectionMock) GetByEntityIDCalls() []struct { Ctxt context.Context EntityFactory model.EntityFactory ID string @@ -710,9 +746,9 @@ func (mock *ProjectionMock) GetByEntityIDCalls() []struct { } // GetByKey calls GetByKeyFunc. -func (mock *ProjectionMock) GetByKey(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { +func (mock *GormProjectionMock) GetByKey(ctxt context.Context, entityFactory model.EntityFactory, identifiers map[string]interface{}) (map[string]interface{}, error) { if mock.GetByKeyFunc == nil { - panic("ProjectionMock.GetByKeyFunc: method is nil but Projection.GetByKey was just called") + panic("GormProjectionMock.GetByKeyFunc: method is nil but GormProjection.GetByKey was just called") } callInfo := struct { Ctxt context.Context @@ -731,8 +767,8 @@ func (mock *ProjectionMock) GetByKey(ctxt context.Context, entityFactory model.E // GetByKeyCalls gets all the calls that were made to GetByKey. // Check the length with: -// len(mockedProjection.GetByKeyCalls()) -func (mock *ProjectionMock) GetByKeyCalls() []struct { +// len(mockedGormProjection.GetByKeyCalls()) +func (mock *GormProjectionMock) GetByKeyCalls() []struct { Ctxt context.Context EntityFactory model.EntityFactory Identifiers map[string]interface{} @@ -749,9 +785,9 @@ func (mock *ProjectionMock) GetByKeyCalls() []struct { } // GetContentEntities calls GetContentEntitiesFunc. -func (mock *ProjectionMock) GetContentEntities(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) { +func (mock *GormProjectionMock) GetContentEntities(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) { if mock.GetContentEntitiesFunc == nil { - panic("ProjectionMock.GetContentEntitiesFunc: method is nil but Projection.GetContentEntities was just called") + panic("GormProjectionMock.GetContentEntitiesFunc: method is nil but GormProjection.GetContentEntities was just called") } callInfo := struct { Ctx context.Context @@ -778,8 +814,8 @@ func (mock *ProjectionMock) GetContentEntities(ctx context.Context, entityFactor // GetContentEntitiesCalls gets all the calls that were made to GetContentEntities. // Check the length with: -// len(mockedProjection.GetContentEntitiesCalls()) -func (mock *ProjectionMock) GetContentEntitiesCalls() []struct { +// len(mockedGormProjection.GetContentEntitiesCalls()) +func (mock *GormProjectionMock) GetContentEntitiesCalls() []struct { Ctx context.Context EntityFactory model.EntityFactory Page int @@ -804,9 +840,9 @@ func (mock *ProjectionMock) GetContentEntitiesCalls() []struct { } // GetContentEntity calls GetContentEntityFunc. -func (mock *ProjectionMock) GetContentEntity(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { +func (mock *GormProjectionMock) GetContentEntity(ctx context.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { if mock.GetContentEntityFunc == nil { - panic("ProjectionMock.GetContentEntityFunc: method is nil but Projection.GetContentEntity was just called") + panic("GormProjectionMock.GetContentEntityFunc: method is nil but GormProjection.GetContentEntity was just called") } callInfo := struct { Ctx context.Context @@ -825,8 +861,8 @@ func (mock *ProjectionMock) GetContentEntity(ctx context.Context, entityFactory // GetContentEntityCalls gets all the calls that were made to GetContentEntity. // Check the length with: -// len(mockedProjection.GetContentEntityCalls()) -func (mock *ProjectionMock) GetContentEntityCalls() []struct { +// len(mockedGormProjection.GetContentEntityCalls()) +func (mock *GormProjectionMock) GetContentEntityCalls() []struct { Ctx context.Context EntityFactory model.EntityFactory WeosID string @@ -843,9 +879,9 @@ func (mock *ProjectionMock) GetContentEntityCalls() []struct { } // GetEventHandler calls GetEventHandlerFunc. -func (mock *ProjectionMock) GetEventHandler() model.EventHandler { +func (mock *GormProjectionMock) GetEventHandler() model.EventHandler { if mock.GetEventHandlerFunc == nil { - panic("ProjectionMock.GetEventHandlerFunc: method is nil but Projection.GetEventHandler was just called") + panic("GormProjectionMock.GetEventHandlerFunc: method is nil but GormProjection.GetEventHandler was just called") } callInfo := struct { }{} @@ -857,8 +893,8 @@ func (mock *ProjectionMock) GetEventHandler() model.EventHandler { // GetEventHandlerCalls gets all the calls that were made to GetEventHandler. // Check the length with: -// len(mockedProjection.GetEventHandlerCalls()) -func (mock *ProjectionMock) GetEventHandlerCalls() []struct { +// len(mockedGormProjection.GetEventHandlerCalls()) +func (mock *GormProjectionMock) GetEventHandlerCalls() []struct { } { var calls []struct { } @@ -869,9 +905,9 @@ 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 *GormProjectionMock) Migrate(ctx context.Context, builders map[string]dynamicstruct.Builder, deletedFields map[string][]string) error { if mock.MigrateFunc == nil { - panic("ProjectionMock.MigrateFunc: method is nil but Projection.Migrate was just called") + panic("GormProjectionMock.MigrateFunc: method is nil but GormProjection.Migrate was just called") } callInfo := struct { Ctx context.Context @@ -890,8 +926,8 @@ func (mock *ProjectionMock) Migrate(ctx context.Context, builders map[string]dyn // MigrateCalls gets all the calls that were made to Migrate. // Check the length with: -// len(mockedProjection.MigrateCalls()) -func (mock *ProjectionMock) MigrateCalls() []struct { +// len(mockedGormProjection.MigrateCalls()) +func (mock *GormProjectionMock) MigrateCalls() []struct { Ctx context.Context Builders map[string]dynamicstruct.Builder DeletedFields map[string][]string @@ -907,7 +943,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{} diff --git a/model/module_test.go b/model/module_test.go index 229c4a69..d535337c 100644 --- a/model/module_test.go +++ b/model/module_test.go @@ -217,7 +217,7 @@ func TestWeOSApp_AddProjection(t *testing.T) { Formatter: "text", }, } - mockProjection := &ProjectionMock{ + mockProjection := &GormProjectionMock{ GetEventHandlerFunc: func() weos.EventHandler { return func(ctx context.Context, event weos.Event) error { return nil diff --git a/model/receiver_test.go b/model/receiver_test.go index 7b091d7e..978aeaea 100644 --- a/model/receiver_test.go +++ b/model/receiver_test.go @@ -173,7 +173,7 @@ func TestUpdateContentType(t *testing.T) { event := model.NewEntityEvent("update", existingBlog, existingBlog.ID, existingPayload) existingBlog.NewChange(event) - projectionMock := &ProjectionMock{ + projectionMock := &GormProjectionMock{ GetContentEntityFunc: func(ctx context3.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { return existingBlog, nil }, @@ -283,7 +283,7 @@ func TestDeleteContentType(t *testing.T) { event := model.NewEntityEvent("delete", existingBlog, existingBlog.ID, existingPayload) existingBlog.NewChange(event) - projectionMock := &ProjectionMock{ + projectionMock := &GormProjectionMock{ GetContentEntityFunc: func(ctx context3.Context, entityFactory model.EntityFactory, weosID string) (*model.ContentEntity, error) { return existingBlog, nil },