From 741a608ba95111a14833591525e25ed5e2aa1ffd Mon Sep 17 00:00:00 2001 From: Fabio Bonelli Date: Fri, 25 Aug 2023 17:10:31 +0200 Subject: [PATCH] feat: fail when the JSON input has extra fields --- internal/common/validator.go | 8 +- internal/handlers/logs.go | 40 ++--- internal/handlers/software.go | 28 ++- internal/handlers/webhooks.go | 46 ++--- internal/jsondecoder/jsondecoder.go | 43 +++++ main.go | 4 + main_test.go | 261 +++++++++++++--------------- 7 files changed, 215 insertions(+), 215 deletions(-) create mode 100644 internal/jsondecoder/jsondecoder.go diff --git a/internal/common/validator.go b/internal/common/validator.go index 5a03202..e61c8c3 100644 --- a/internal/common/validator.go +++ b/internal/common/validator.go @@ -9,6 +9,8 @@ import ( "github.com/gofiber/fiber/v2" "github.com/go-playground/validator/v10" + + "github.com/italia/developers-italia-api/internal/jsondecoder" ) const ( @@ -63,7 +65,11 @@ func ValidateStruct(validateStruct interface{}) []ValidationError { func ValidateRequestEntity(ctx *fiber.Ctx, request interface{}, errorMessage string) error { if err := ctx.BodyParser(request); err != nil { - return Error(fiber.StatusBadRequest, errorMessage, "invalid json") + if errors.Is(err, jsondecoder.ErrUnknownField) { + return Error(fiber.StatusUnprocessableEntity, errorMessage, err.Error()) + } + + return Error(fiber.StatusBadRequest, errorMessage, "invalid or malformed JSON") } if err := ValidateStruct(request); err != nil { diff --git a/internal/handlers/logs.go b/internal/handlers/logs.go index 7deefdf..c5af3c7 100644 --- a/internal/handlers/logs.go +++ b/internal/handlers/logs.go @@ -84,20 +84,18 @@ func (p *Log) GetLog(ctx *fiber.Ctx) error { // PostLog creates a new log. func (p *Log) PostLog(ctx *fiber.Ctx) error { - logReq := new(common.Log) + const errMsg = "can't create Log" - if err := ctx.BodyParser(&logReq); err != nil { - return common.Error(fiber.StatusBadRequest, "can't create Log", "invalid json") - } + logReq := new(common.Log) - if err := common.ValidateStruct(*logReq); err != nil { - return common.ErrorWithValidationErrors(fiber.StatusUnprocessableEntity, "can't create Log", err) + if err := common.ValidateRequestEntity(ctx, logReq, errMsg); err != nil { + return err //nolint:wrapcheck } log := models.Log{ID: utils.UUIDv4(), Message: logReq.Message} if err := p.db.Create(&log).Error; err != nil { - return common.Error(fiber.StatusInternalServerError, "can't create Log", "db error") + return common.Error(fiber.StatusInternalServerError, errMsg, "db error") } return ctx.JSON(&log) @@ -105,30 +103,28 @@ func (p *Log) PostLog(ctx *fiber.Ctx) error { // PatchLog updates the log with the given ID. func (p *Log) PatchLog(ctx *fiber.Ctx) error { - logReq := new(common.Log) + const errMsg = "can't update Log" - if err := ctx.BodyParser(logReq); err != nil { - return common.Error(fiber.StatusBadRequest, "can't update Log", "invalid json") - } + logReq := new(common.Log) - if err := common.ValidateStruct(*logReq); err != nil { - return common.ErrorWithValidationErrors(fiber.StatusUnprocessableEntity, "can't update Log", err) + if err := common.ValidateRequestEntity(ctx, logReq, errMsg); err != nil { + return err //nolint:wrapcheck } log := models.Log{} if err := p.db.First(&log, "id = ?", ctx.Params("id")).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return common.Error(fiber.StatusNotFound, "can't update Log", "Log was not found") + return common.Error(fiber.StatusNotFound, errMsg, "Log was not found") } - return common.Error(fiber.StatusInternalServerError, "can't update Log", "internal server error") + return common.Error(fiber.StatusInternalServerError, errMsg, "internal server error") } log.Message = logReq.Message if err := p.db.Updates(&log).Error; err != nil { - return common.Error(fiber.StatusInternalServerError, "can't update Log", "db error") + return common.Error(fiber.StatusInternalServerError, errMsg, "db error") } return ctx.JSON(&log) @@ -198,6 +194,8 @@ func (p *Log) GetSoftwareLogs(ctx *fiber.Ctx) error { // PostSoftwareLog creates a new log associated to a Software with the given ID and returns any error encountered. func (p *Log) PostSoftwareLog(ctx *fiber.Ctx) error { + const errMsg = "can't create Log" + logReq := new(common.Log) software := models.Software{} @@ -213,12 +211,8 @@ func (p *Log) PostSoftwareLog(ctx *fiber.Ctx) error { ) } - if err := ctx.BodyParser(&logReq); err != nil { - return common.Error(fiber.StatusBadRequest, "can't create Log", "invalid json") - } - - if err := common.ValidateStruct(*logReq); err != nil { - return common.ErrorWithValidationErrors(fiber.StatusUnprocessableEntity, "can't create Log", err) + if err := common.ValidateRequestEntity(ctx, logReq, errMsg); err != nil { + return err //nolint:wrapcheck } table := models.Software{}.TableName() @@ -231,7 +225,7 @@ func (p *Log) PostSoftwareLog(ctx *fiber.Ctx) error { } if err := p.db.Create(&log).Error; err != nil { - return common.Error(fiber.StatusInternalServerError, "can't create Log", "db error") + return common.Error(fiber.StatusInternalServerError, errMsg, "db error") } return ctx.JSON(&log) diff --git a/internal/handlers/software.go b/internal/handlers/software.go index c3eb58f..38c25d3 100644 --- a/internal/handlers/software.go +++ b/internal/handlers/software.go @@ -149,16 +149,12 @@ func (p *Software) GetSoftware(ctx *fiber.Ctx) error { // PostSoftware creates a new software. func (p *Software) PostSoftware(ctx *fiber.Ctx) error { - softwareReq := new(common.SoftwarePost) + const errMsg = "can't create Software" - if err := ctx.BodyParser(&softwareReq); err != nil { - return common.Error(fiber.StatusBadRequest, "can't create Software", "invalid json") - } + softwareReq := new(common.SoftwarePost) - if err := common.ValidateStruct(*softwareReq); err != nil { - return common.ErrorWithValidationErrors( - fiber.StatusUnprocessableEntity, "can't create Software", err, - ) + if err := common.ValidateRequestEntity(ctx, softwareReq, errMsg); err != nil { + return err //nolint:wrapcheck } aliases := []models.SoftwareURL{} @@ -180,14 +176,16 @@ func (p *Software) PostSoftware(ctx *fiber.Ctx) error { } if err := p.db.Create(&software).Error; err != nil { - return common.Error(fiber.StatusInternalServerError, "can't create Software", err.Error()) + return common.Error(fiber.StatusInternalServerError, errMsg, err.Error()) } return ctx.JSON(&software) } // PatchSoftware updates the software with the given ID. -func (p *Software) PatchSoftware(ctx *fiber.Ctx) error { //nolint:cyclop // mostly error handling ifs +func (p *Software) PatchSoftware(ctx *fiber.Ctx) error { + const errMsg = "can't update Software" + softwareReq := new(common.SoftwarePatch) software := models.Software{} @@ -202,14 +200,8 @@ func (p *Software) PatchSoftware(ctx *fiber.Ctx) error { //nolint:cyclop // most return common.Error(fiber.StatusInternalServerError, "can't update Software", "internal server error") } - if err := ctx.BodyParser(softwareReq); err != nil { - return common.Error(fiber.StatusBadRequest, "can't update Software", "invalid json") - } - - if err := common.ValidateStruct(*softwareReq); err != nil { - return common.ErrorWithValidationErrors( - fiber.StatusUnprocessableEntity, "can't update Software", err, - ) + if err := common.ValidateRequestEntity(ctx, softwareReq, errMsg); err != nil { + return err //nolint:wrapcheck } // Slice of urls that we expect in the database after the PATCH (url + aliases) diff --git a/internal/handlers/webhooks.go b/internal/handlers/webhooks.go index 146f24f..0378bab 100644 --- a/internal/handlers/webhooks.go +++ b/internal/handlers/webhooks.go @@ -114,18 +114,14 @@ func (p *Webhook[T]) GetSingleResourceWebhooks(ctx *fiber.Ctx) error { // PostSingleResourceWebhook creates a new webhook associated to resources // (fe. Software, Publishers) and returns any error encountered. func (p *Webhook[T]) PostResourceWebhook(ctx *fiber.Ctx) error { + const errMsg = "can't create Webhook" + webhookReq := new(common.Webhook) var resource T - if err := ctx.BodyParser(&webhookReq); err != nil { - return common.Error(fiber.StatusBadRequest, "can't create Webhook", "invalid json") - } - - if err := common.ValidateStruct(*webhookReq); err != nil { - return common.ErrorWithValidationErrors( - fiber.StatusUnprocessableEntity, "can't create Webhook", err, - ) + if err := common.ValidateRequestEntity(ctx, webhookReq, errMsg); err != nil { + return err //nolint:wrapcheck } webhook := models.Webhook{ @@ -137,7 +133,7 @@ func (p *Webhook[T]) PostResourceWebhook(ctx *fiber.Ctx) error { } if err := p.db.Create(&webhook).Error; err != nil { - return common.Error(fiber.StatusInternalServerError, "can't create Webhook", "db error") + return common.Error(fiber.StatusInternalServerError, errMsg, "db error") } return ctx.JSON(&webhook) @@ -146,6 +142,8 @@ func (p *Webhook[T]) PostResourceWebhook(ctx *fiber.Ctx) error { // PostResourceWebhook creates a new webhook associated to a resource with the given ID // (fe. a specific Software or Publisher) and returns any error encountered. func (p *Webhook[T]) PostSingleResourceWebhook(ctx *fiber.Ctx) error { + const errMsg = "can't create Webhook" + webhookReq := new(common.Webhook) var resource T @@ -162,14 +160,8 @@ func (p *Webhook[T]) PostSingleResourceWebhook(ctx *fiber.Ctx) error { ) } - if err := ctx.BodyParser(&webhookReq); err != nil { - return common.Error(fiber.StatusBadRequest, "can't create Webhook", "invalid json") - } - - if err := common.ValidateStruct(*webhookReq); err != nil { - return common.ErrorWithValidationErrors( - fiber.StatusUnprocessableEntity, "can't create Webhook", err, - ) + if err := common.ValidateRequestEntity(ctx, webhookReq, errMsg); err != nil { + return err //nolint:wrapcheck } webhook := models.Webhook{ @@ -181,7 +173,7 @@ func (p *Webhook[T]) PostSingleResourceWebhook(ctx *fiber.Ctx) error { } if err := p.db.Create(&webhook).Error; err != nil { - return common.Error(fiber.StatusInternalServerError, "can't create Webhook", "db error") + return common.Error(fiber.StatusInternalServerError, errMsg, "db error") } return ctx.JSON(&webhook) @@ -189,28 +181,24 @@ func (p *Webhook[T]) PostSingleResourceWebhook(ctx *fiber.Ctx) error { // PatchWebhook updates the webhook with the given ID. func (p *Webhook[T]) PatchWebhook(ctx *fiber.Ctx) error { - webhookReq := new(common.Webhook) + const errMsg = "can't update Webhook" - if err := ctx.BodyParser(webhookReq); err != nil { - return common.Error(fiber.StatusBadRequest, "can't update Webhook", "invalid json") - } + webhookReq := new(common.Webhook) - if err := common.ValidateStruct(*webhookReq); err != nil { - return common.ErrorWithValidationErrors( - fiber.StatusUnprocessableEntity, "can't update Webhook", err, - ) + if err := common.ValidateRequestEntity(ctx, webhookReq, errMsg); err != nil { + return err //nolint:wrapcheck } webhook := models.Webhook{} if err := p.db.First(&webhook, "id = ?", ctx.Params("id")).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return common.Error(fiber.StatusNotFound, "can't update Webhook", "Webhook was not found") + return common.Error(fiber.StatusNotFound, errMsg, "Webhook was not found") } return common.Error( fiber.StatusInternalServerError, - "can't update Webhook", + errMsg, fiber.ErrInternalServerError.Message, ) } @@ -218,7 +206,7 @@ func (p *Webhook[T]) PatchWebhook(ctx *fiber.Ctx) error { webhook.URL = webhookReq.URL if err := p.db.Updates(&webhook).Error; err != nil { - return common.Error(fiber.StatusInternalServerError, "can't update Webhook", "db error") + return common.Error(fiber.StatusInternalServerError, errMsg, "db error") } return ctx.JSON(&webhook) diff --git a/internal/jsondecoder/jsondecoder.go b/internal/jsondecoder/jsondecoder.go new file mode 100644 index 0000000..1744a2d --- /dev/null +++ b/internal/jsondecoder/jsondecoder.go @@ -0,0 +1,43 @@ +package jsondecoder + +import ( + "bytes" + "encoding/json" + "errors" + "strings" +) + +var ( + ErrExtraDataAfterDecoding = errors.New("extra data after decoding") + ErrUnknownField = errors.New("unknown field in JSON input") +) + +// UnmarshalDisallowUnknownFieldsUnmarshal parses the JSON-encoded data +// and stores the result in the value pointed to by v like json.Unmarshal, +// but with DisallowUnknownFields() set by default for extra security. +func UnmarshalDisallowUnknownFields(data []byte, v interface{}) error { + dec := json.NewDecoder(bytes.NewReader(data)) + dec.DisallowUnknownFields() + + if err := dec.Decode(v); err != nil { + // Ugly, but the encoding/json uses a dynamic error here + if strings.HasPrefix(err.Error(), "json: unknown field ") { + return ErrUnknownField + } + + // we want to provide an alternative implementation, with the + // unwrapped errors + //nolint:wrapcheck + return err + } + + // Check if there's any data left in the decoder's buffer. + // This ensures that there's no extra JSON after the main object + // otherwise something like '{"foo": 1}{"bar": 2}' or even '{}garbage' + // will not error out. + if dec.More() { + return ErrExtraDataAfterDecoding + } + + return nil +} diff --git a/main.go b/main.go index 5331e95..4dad7b6 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "github.com/italia/developers-italia-api/internal/common" "github.com/italia/developers-italia-api/internal/database" "github.com/italia/developers-italia-api/internal/handlers" + "github.com/italia/developers-italia-api/internal/jsondecoder" "github.com/italia/developers-italia-api/internal/middleware" "github.com/italia/developers-italia-api/internal/models" "github.com/italia/developers-italia-api/internal/webhooks" @@ -54,6 +55,9 @@ func Setup() *fiber.App { app := fiber.New(fiber.Config{ ErrorHandler: common.CustomErrorHandler, + // Fiber doesn't set DisallowUnknownFields by default + // (https://github.com/gofiber/fiber/issues/2601) + JSONDecoder: jsondecoder.UnmarshalDisallowUnknownFields, }) // Automatically recover panics in handlers diff --git a/main_test.go b/main_test.go index e76e774..5858ef9 100644 --- a/main_test.go +++ b/main_test.go @@ -685,14 +685,14 @@ func TestPublishersEndpoints(t *testing.T) { { description: "POST publishers with invalid payload", query: "POST /v1/publishers", - body: `{"url": "-"}`, + body: `{"description": "-"}`, headers: map[string][]string{ "Authorization": {goodToken}, "Content-Type": {"application/json"}, }, expectedCode: 422, expectedContentType: "application/problem+json", - expectedBody: `{"title":"can't create Publisher","detail":"invalid format: codeHosting is required, description is required","status":422,"validationErrors":[{"field":"codeHosting","rule":"required","value":""},{"field":"description","rule":"required","value":""}]}`, + expectedBody: `{"title":"can't create Publisher","detail":"invalid format: codeHosting is required","status":422,"validationErrors":[{"field":"codeHosting","rule":"required","value":""}]}`, }, { description: "POST publishers - wrong token", @@ -717,7 +717,7 @@ func TestPublishersEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Publisher`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, { @@ -793,7 +793,7 @@ func TestPublishersEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Publisher`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, @@ -957,25 +957,21 @@ func TestPublishersEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't update Publisher`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, - // TODO: make this pass - // { - // description: "PATCH publishers with JSON with extra fields", - // query: "PATCH /v1/publishers", - // body: `{"description": "new description", EXTRA_FIELD: "extra field not in schema"}`, - // headers: map[string][]string{ - // "Authorization": {goodToken}, - // "Content-Type": {"application/json"}, - // }, - // expectedCode: 422, - // expectedContentType: "application/problem+json", - // validateFunc: func(t *testing.T, response map[string]interface{}) { - // assert.Equal(t, `can't create Publisher`, response["title"]) - // assert.Equal(t, "invalid json", response["detail"]) - // }, - // }, + { + description: "PATCH publishers with JSON with extra fields", + query: "PATCH /v1/publishers/2ded32eb-c45e-4167-9166-a44e18b8adde", + body: `{"description": "new description", "EXTRA_FIELD": "extra field not in schema"}`, + headers: map[string][]string{ + "Authorization": {goodToken}, + "Content-Type": {"application/json"}, + }, + expectedCode: 422, + expectedContentType: "application/problem+json", + expectedBody: `{"title":"can't update Publisher","detail":"unknown field in JSON input","status":422}`, + }, { description: "PATCH publisher with validation errors", query: "PATCH /v1/publishers/2ded32eb-c45e-4167-9166-a44e18b8adde", @@ -998,7 +994,7 @@ func TestPublishersEndpoints(t *testing.T) { }, expectedCode: 400, expectedContentType: "application/problem+json", - expectedBody: `{"title":"can't update Publisher","detail":"invalid json","status":400}`, + expectedBody: `{"title":"can't update Publisher","detail":"invalid or malformed JSON","status":400}`, }, // TODO: enforce this? // { @@ -1201,24 +1197,21 @@ func TestPublishersEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Webhook`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, - // TODO: make this pass - // { - // description: "POST /v1/publishers/98a069f7-57b0-464d-b300-4b4b336297a0/webhooks with JSON with extra fields", - // body: `{"url": "https://new.example.org", EXTRA_FIELD: "extra field not in schema"}`, - // headers: map[string][]string{ - // "Authorization": {goodToken}, - // "Content-Type": {"application/json"}, - // }, - // expectedCode: 422, - // expectedContentType: "application/problem+json", - // validateFunc: func(t *testing.T, response map[string]interface{}) { - // assert.Equal(t, `can't create Webhook`, response["title"]) - // assert.Equal(t, "invalid json", response["detail"]) - // }, - // }, + { + description: "POST /v1/publishers/98a069f7-57b0-464d-b300-4b4b336297a0/webhooks with JSON with extra fields", + query: "POST /v1/publishers/98a069f7-57b0-464d-b300-4b4b336297a0/webhooks", + body: `{"url": "https://new.example.org", "EXTRA_FIELD": "extra field not in schema"}`, + headers: map[string][]string{ + "Authorization": {goodToken}, + "Content-Type": {"application/json"}, + }, + expectedCode: 422, + expectedContentType: "application/problem+json", + expectedBody: `{"title":"can't create Webhook","detail":"unknown field in JSON input","status":422}`, + }, { description: "POST webhook with validation errors", query: "POST /v1/publishers/98a069f7-57b0-464d-b300-4b4b336297a0/webhooks", @@ -1257,7 +1250,7 @@ func TestPublishersEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Webhook`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, // TODO: enforce this? @@ -1748,25 +1741,21 @@ func TestSoftwareEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Software`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, - // TODO: make this pass - // { - // descrption: "POST /v1/software with JSON with extra fields", - // query: "POST /v1/software", - // body: `{"publiccodeYml": "-", EXTRA_FIELD: "extra field not in schema"}`, - // headers: map[string][]string{ - // "Authorization": {goodToken}, - // "Content-Type": {"application/json"}, - // }, - // expectedCode: 422, - // expectedContentType: "application/problem+json", - // validateFunc: func(t *testing.T, response map[string]interface{}) { - // assert.Equal(t, `can't create Software`, response["title"]) - // assert.Equal(t, "invalid json", response["detail"]) - // }, - // }, + { + description: "POST /v1/software with JSON with extra fields", + query: "POST /v1/software", + body: `{"publiccodeYml": "-", "EXTRA_FIELD": "extra field not in schema"}`, + headers: map[string][]string{ + "Authorization": {goodToken}, + "Content-Type": {"application/json"}, + }, + expectedCode: 422, + expectedContentType: "application/problem+json", + expectedBody: `{"title":"can't create Software","detail":"unknown field in JSON input","status":422}`, + }, { description: "POST software with optional boolean field set to false", query: "POST /v1/software", @@ -1819,7 +1808,7 @@ func TestSoftwareEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Software`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, // TODO: enforce this? @@ -1976,25 +1965,21 @@ func TestSoftwareEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't update Software`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, - // TODO: make this pass - // { - // description: "PATCH software with JSON with extra fields", - // query: "PATCH /v1/software", - // body: `{"publiccodeYml": "-", EXTRA_FIELD: "extra field not in schema"}`, - // headers: map[string][]string{ - // "Authorization": {goodToken}, - // "Content-Type": {"application/json"}, - // }, - // expectedCode: 422, - // expectedContentType: "application/problem+json", - // validateFunc: func(t *testing.T, response map[string]interface{}) { - // assert.Equal(t, `can't create Software`, response["title"]) - // assert.Equal(t, "invalid json", response["detail"]) - // }, - // }, + { + description: "PATCH software with JSON with extra fields", + query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a", + body: `{"publiccodeYml": "-", "EXTRA_FIELD": "extra field not in schema"}`, + headers: map[string][]string{ + "Authorization": {goodToken}, + "Content-Type": {"application/json"}, + }, + expectedCode: 422, + expectedContentType: "application/problem+json", + expectedBody: `{"title":"can't update Software","detail":"unknown field in JSON input","status":422}`, + }, { description: "PATCH software with validation errors", query: "PATCH /v1/software/59803fb7-8eec-4fe5-a354-8926009c364a", @@ -2033,7 +2018,7 @@ func TestSoftwareEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't update Software`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, // TODO: enforce this? @@ -2231,24 +2216,21 @@ func TestSoftwareEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Log`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, - // TODO: make this pass - // { - // description: "POST /v1/software/c353756e-8597-4e46-a99b-7da2e141603b/logs with JSON with extra fields", - // body: `{"message": "new log", EXTRA_FIELD: "extra field not in schema"}`, - // headers: map[string][]string{ - // "Authorization": {goodToken}, - // "Content-Type": {"application/json"}, - // }, - // expectedCode: 422, - // expectedContentType: "application/problem+json", - // validateFunc: func(t *testing.T, response map[string]interface{}) { - // assert.Equal(t, `can't create Log`, response["title"]) - // assert.Equal(t, "invalid json", response["detail"]) - // }, - // }, + { + description: "POST /v1/software/c353756e-8597-4e46-a99b-7da2e141603b/logs with JSON with extra fields", + query: "POST /v1/software/c353756e-8597-4e46-a99b-7da2e141603b/logs", + body: `{"message": "new log", "EXTRA_FIELD": "extra field not in schema"}`, + headers: map[string][]string{ + "Authorization": {goodToken}, + "Content-Type": {"application/json"}, + }, + expectedCode: 422, + expectedContentType: "application/problem+json", + expectedBody: `{"title":"can't create Log","detail":"unknown field in JSON input","status":422}`, + }, { description: "POST log with validation errors", query: "POST /v1/software/c353756e-8597-4e46-a99b-7da2e141603b/logs", @@ -2287,7 +2269,7 @@ func TestSoftwareEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Log`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, // TODO: enforce this? @@ -2444,24 +2426,21 @@ func TestSoftwareEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Webhook`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, - // TODO: make this pass - // { - // description: "POST /v1/software/c5dec6fa-8a01-4881-9e7d-132770d4214d/webhooks with JSON with extra fields", - // body: `{"url": "https://new.example.org", EXTRA_FIELD: "extra field not in schema"}`, - // headers: map[string][]string{ - // "Authorization": {goodToken}, - // "Content-Type": {"application/json"}, - // }, - // expectedCode: 422, - // expectedContentType: "application/problem+json", - // validateFunc: func(t *testing.T, response map[string]interface{}) { - // assert.Equal(t, `can't create Webhook`, response["title"]) - // assert.Equal(t, "invalid json", response["detail"]) - // }, - // }, + { + description: "POST /v1/software/c5dec6fa-8a01-4881-9e7d-132770d4214d/webhooks with JSON with extra fields", + query: "POST /v1/software/c5dec6fa-8a01-4881-9e7d-132770d4214d/webhooks", + body: `{"url": "https://new.example.org", "EXTRA_FIELD": "extra field not in schema"}`, + headers: map[string][]string{ + "Authorization": {goodToken}, + "Content-Type": {"application/json"}, + }, + expectedCode: 422, + expectedContentType: "application/problem+json", + expectedBody: `{"title":"can't create Webhook","detail":"unknown field in JSON input","status":422}`, + }, { description: "POST webhook with validation errors", query: "POST /v1/software/c5dec6fa-8a01-4881-9e7d-132770d4214d/webhooks", @@ -2500,7 +2479,7 @@ func TestSoftwareEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Webhook`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, // TODO: enforce this? @@ -2754,7 +2733,7 @@ func TestLogsEndpoints(t *testing.T) { }, expectedCode: 422, expectedContentType: "application/problem+json", - expectedBody: `{"title":"can't create Log","detail":"invalid format: message is required","status":422,"validationErrors":[{"field":"message","rule":"required","value":""}]}`, + expectedBody: `{"title":"can't create Log","detail":"unknown field in JSON input","status":422}`, }, { description: "POST log - wrong token", @@ -2780,24 +2759,21 @@ func TestLogsEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Log`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, - // TODO: make this pass - // { - // query: "POST /v1/logs with JSON with extra fields", - // body: `{"message": "new log", EXTRA_FIELD: "extra field not in schema"}`, - // headers: map[string][]string{ - // "Authorization": {goodToken}, - // "Content-Type": {"application/json"}, - // }, - // expectedCode: 422, - // expectedContentType: "application/problem+json", - // validateFunc: func(t *testing.T, response map[string]interface{}) { - // assert.Equal(t, `can't create Log`, response["title"]) - // assert.Equal(t, "invalid json", response["detail"]) - // }, - // }, + { + description: "POST /v1/logs with JSON with extra fields", + query: "POST /v1/logs", + body: `{"message": "new log", "EXTRA_FIELD": "extra field not in schema"}`, + headers: map[string][]string{ + "Authorization": {goodToken}, + "Content-Type": {"application/json"}, + }, + expectedCode: 422, + expectedContentType: "application/problem+json", + expectedBody: `{"title":"can't create Log","detail":"unknown field in JSON input","status":422}`, + }, { description: "POST log with validation errors", query: "POST /v1/logs", @@ -2836,7 +2812,7 @@ func TestLogsEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't create Log`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, // TODO: enforce this? @@ -2918,24 +2894,21 @@ func TestWebhooksEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't update Webhook`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, - // TODO: make this pass - // { - // query: "PATCH /v1/webhooks/007bc84a-7e2d-43a0-b7e1-a256d4114aa7 with JSON with extra fields", - // body: `{"url": "https://new.example.org/receiver", EXTRA_FIELD: "extra field not in schema"}`, - // headers: map[string][]string{ - // "Authorization": {goodToken}, - // "Content-Type": {"application/json"}, - // }, - // expectedCode: 422, - // expectedContentType: "application/problem+json", - // validateFunc: func(t *testing.T, response map[string]interface{}) { - // assert.Equal(t, `can't create Webhook`, response["title"]) - // assert.Equal(t, "invalid json", response["detail"]) - // }, - // }, + { + description: "PATCH /v1/webhooks/007bc84a-7e2d-43a0-b7e1-a256d4114aa7 with JSON with extra fields", + query: "PATCH /v1/webhooks/007bc84a-7e2d-43a0-b7e1-a256d4114aa7", + body: `{"url": "https://new.example.org/receiver", "EXTRA_FIELD": "extra field not in schema"}`, + headers: map[string][]string{ + "Authorization": {goodToken}, + "Content-Type": {"application/json"}, + }, + expectedCode: 422, + expectedContentType: "application/problem+json", + expectedBody: `{"title":"can't update Webhook","detail":"unknown field in JSON input","status":422}`, + }, { description: "PATCH webhook with validation errors", query: "PATCH /v1/webhooks/007bc84a-7e2d-43a0-b7e1-a256d4114aa7", @@ -2974,7 +2947,7 @@ func TestWebhooksEndpoints(t *testing.T) { expectedContentType: "application/problem+json", validateFunc: func(t *testing.T, response map[string]interface{}) { assert.Equal(t, `can't update Webhook`, response["title"]) - assert.Equal(t, "invalid json", response["detail"]) + assert.Equal(t, "invalid or malformed JSON", response["detail"]) }, }, // TODO: enforce this?