From 165c285ae906c860a3b7b1d5c95bfc0627ca6076 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Thu, 23 May 2019 14:30:52 -0500 Subject: [PATCH 1/7] Add /flow/clone endpoint --- go.mod | 2 +- go.sum | 4 +-- web/flow/flow.go | 52 ++++++++++++++++++++++++++++++ web/flow/flow_test.go | 1 + web/flow/testdata/clone_valid.json | 43 ++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 web/flow/testdata/clone_valid.json diff --git a/go.mod b/go.mod index 46e8880bc..b7ea44f21 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/mattn/go-sqlite3 v1.10.0 // indirect github.com/nyaruka/ezconf v0.2.1 github.com/nyaruka/gocommon v1.0.0 - github.com/nyaruka/goflow v0.39.3 + github.com/nyaruka/goflow v0.39.4 github.com/nyaruka/librato v0.0.0-20180827155909-cacc769357b8 github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d github.com/nyaruka/null v1.1.1 diff --git a/go.sum b/go.sum index de02c15a4..552dcc002 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/nyaruka/gocommon v0.2.0 h1:1Le4Ok0Zp2RYULue0n4/02zL+1MrykN/C79HhirGeR github.com/nyaruka/gocommon v0.2.0/go.mod h1:ZrhaOKNc+kK1qWNuCuZivskT+ygLyIwu4KZVgcaC1mw= github.com/nyaruka/gocommon v1.0.0 h1:4gdAMOR4BTMHHZjOy5WhfKYGUZVmJ+3LPh1sj011Qzk= github.com/nyaruka/gocommon v1.0.0/go.mod h1:QbdU2J9WBsqBmeZRuwndf2f6O7rD7mkC0bGn5UNnwjI= -github.com/nyaruka/goflow v0.39.3 h1:rNp9htU3D8QZwhMd8TVkbQ4LL9KyfEsM5fdAwFBZR6s= -github.com/nyaruka/goflow v0.39.3/go.mod h1:QitrAujTi7Mc75aVIoCpAmFsfO794+ljo9QNGt7qSHY= +github.com/nyaruka/goflow v0.39.4 h1:1KizVSGGc8INx8n8JJr7RO0VSNFWAg1I6HgokBdN1iA= +github.com/nyaruka/goflow v0.39.4/go.mod h1:QitrAujTi7Mc75aVIoCpAmFsfO794+ljo9QNGt7qSHY= github.com/nyaruka/librato v0.0.0-20180827155909-cacc769357b8 h1:TOvxy0u6LNTWP3gwbdNVCiByXJupr9ATFdzBnBJ2TY8= github.com/nyaruka/librato v0.0.0-20180827155909-cacc769357b8/go.mod h1:huVocfMEHkttMHD4hSr/wjWNyTx/YMzwwajVzV2bq+0= github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d h1:hyp9u36KIwbTCo2JAJ+TuJcJBc+UZzEig7RI/S5Dvkc= diff --git a/web/flow/flow.go b/web/flow/flow.go index 7de9db334..84f4b2fd4 100644 --- a/web/flow/flow.go +++ b/web/flow/flow.go @@ -20,6 +20,7 @@ import ( func init() { web.RegisterJSONRoute(http.MethodPost, "/mr/flow/migrate", web.RequireAuthToken(handleMigrate)) web.RegisterJSONRoute(http.MethodPost, "/mr/flow/validate", web.RequireAuthToken(handleValidate)) + web.RegisterJSONRoute(http.MethodPost, "/mr/flow/clone", web.RequireAuthToken(handleClone)) } // Migrates a legacy flow to the new flow definition specification @@ -135,3 +136,54 @@ func handleValidate(ctx context.Context, s *web.Server, r *http.Request) (interf return flow, http.StatusOK, nil } + +// Clones a flow. +// +// { +// "dependency_mapping": { +// "4ee4189e-0c06-4b00-b54f-5621329de947": "db31d23f-65b8-4518-b0f6-45638bfbbbf2", +// "723e62d8-a544-448f-8590-1dfd0fccfcd4": "f1fd861c-9e75-4376-a829-dcf76db6e721" +// }, +// "flow": { "uuid": "468621a8-32e6-4cd2-afc1-04416f7151f0", "nodes": [...]} +// } +// +type cloneRequest struct { + DependencyMapping map[utils.UUID]utils.UUID `json:"dependency_mapping"` + Flow json.RawMessage `json:"flow" validate:"required"` +} + +func handleClone(ctx context.Context, s *web.Server, r *http.Request) (interface{}, int, error) { + request := &cloneRequest{} + body, err := ioutil.ReadAll(io.LimitReader(r.Body, web.MaxRequestBytes)) + if err != nil { + return nil, http.StatusBadRequest, err + } + + if err := r.Body.Close(); err != nil { + return nil, http.StatusInternalServerError, err + } + + if err := utils.UnmarshalAndValidate(body, request); err != nil { + return nil, http.StatusBadRequest, errors.Wrapf(err, "error unmarshalling request") + } + + var flowDef = request.Flow + + // migrate definition if it is in legacy format + if legacy.IsLegacyDefinition(flowDef) { + flowDef, err = legacy.MigrateLegacyDefinition(flowDef, "https://"+config.Mailroom.AttachmentDomain) + if err != nil { + return nil, http.StatusBadRequest, err + } + } + + // try to read the flow definition which will fail if it's invalid + flow, err := definition.ReadFlow(flowDef) + if err != nil { + return nil, http.StatusBadRequest, err + } + + clone := flow.Clone(request.DependencyMapping) + + return clone, http.StatusOK, nil +} diff --git a/web/flow/flow_test.go b/web/flow/flow_test.go index 8c37dc13f..b0f3662eb 100644 --- a/web/flow/flow_test.go +++ b/web/flow/flow_test.go @@ -65,6 +65,7 @@ func TestServer(t *testing.T) { {"/mr/flow/validate", "POST", "testdata/validate_invalid.json", 422, `isn't a known node`}, {"/mr/flow/validate", "POST", "testdata/validate_valid_without_assets.json", 200, `"type": "send_msg"`}, {"/mr/flow/validate", "POST", "testdata/validate_legacy_single_msg.json", 200, `"type": "send_msg"`}, + {"/mr/flow/clone", "POST", "testdata/clone_valid.json", 200, `"type": "send_msg"`}, } for _, tc := range tcs { diff --git a/web/flow/testdata/clone_valid.json b/web/flow/testdata/clone_valid.json new file mode 100644 index 000000000..40151577a --- /dev/null +++ b/web/flow/testdata/clone_valid.json @@ -0,0 +1,43 @@ +{ + "dependency_mapping": { + "8f107d42-7416-4cf2-9a51-9490361ad517": "1cf84575-ee14-4253-88b6-e3675c04a066", + "5e9d8fab-5e7e-4f51-b533-261af5dea70d": "ebe441b4-c581-4b03-b544-5695cfe29bc1" + }, + "flow": { + "uuid": "8f107d42-7416-4cf2-9a51-9490361ad517", + "name": "Valid Flow", + "spec_version": "13.0.0", + "language": "eng", + "type": "messaging", + "revision": 106, + "expire_after_minutes": 10080, + "localization": {}, + "nodes": [ + { + "uuid": "6fde1a09-3997-47dd-aff0-92e8aff3a642", + "actions": [ + { + "type": "add_contact_groups", + "uuid": "23337aa9-0d3d-4e70-876e-9a2633d1e5e4", + "groups": [ + { + "uuid": "5e9d8fab-5e7e-4f51-b533-261af5dea70d", + "name": "Testers" + } + ] + }, + { + "type": "send_msg", + "uuid": "05a5cb7c-bb8a-4ad9-af90-ef9887cc370e", + "text": "Your birthdate is soon" + } + ], + "exits": [ + { + "uuid": "d3f3f024-a90e-43a5-bd5a-7056f5bea699" + } + ] + } + ] + } +} \ No newline at end of file From c6ad09b7494bac33768d15b4d4c8c94b7f29b90d Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 24 May 2019 11:51:04 -0500 Subject: [PATCH 2/7] Update to latest goflow --- go.mod | 2 +- go.sum | 4 ++-- runner/runner.go | 2 +- web/flow/flow.go | 13 +++++++++---- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index b7ea44f21..f6feb3174 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/mattn/go-sqlite3 v1.10.0 // indirect github.com/nyaruka/ezconf v0.2.1 github.com/nyaruka/gocommon v1.0.0 - github.com/nyaruka/goflow v0.39.4 + github.com/nyaruka/goflow v0.40.1 github.com/nyaruka/librato v0.0.0-20180827155909-cacc769357b8 github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d github.com/nyaruka/null v1.1.1 diff --git a/go.sum b/go.sum index 552dcc002..025419788 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/nyaruka/gocommon v0.2.0 h1:1Le4Ok0Zp2RYULue0n4/02zL+1MrykN/C79HhirGeR github.com/nyaruka/gocommon v0.2.0/go.mod h1:ZrhaOKNc+kK1qWNuCuZivskT+ygLyIwu4KZVgcaC1mw= github.com/nyaruka/gocommon v1.0.0 h1:4gdAMOR4BTMHHZjOy5WhfKYGUZVmJ+3LPh1sj011Qzk= github.com/nyaruka/gocommon v1.0.0/go.mod h1:QbdU2J9WBsqBmeZRuwndf2f6O7rD7mkC0bGn5UNnwjI= -github.com/nyaruka/goflow v0.39.4 h1:1KizVSGGc8INx8n8JJr7RO0VSNFWAg1I6HgokBdN1iA= -github.com/nyaruka/goflow v0.39.4/go.mod h1:QitrAujTi7Mc75aVIoCpAmFsfO794+ljo9QNGt7qSHY= +github.com/nyaruka/goflow v0.40.1 h1:+8rvAmL5tZAbJ8o3WLCL9zEdyD8lKhcunnQpxABma00= +github.com/nyaruka/goflow v0.40.1/go.mod h1:QitrAujTi7Mc75aVIoCpAmFsfO794+ljo9QNGt7qSHY= github.com/nyaruka/librato v0.0.0-20180827155909-cacc769357b8 h1:TOvxy0u6LNTWP3gwbdNVCiByXJupr9ATFdzBnBJ2TY8= github.com/nyaruka/librato v0.0.0-20180827155909-cacc769357b8/go.mod h1:huVocfMEHkttMHD4hSr/wjWNyTx/YMzwwajVzV2bq+0= github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d h1:hyp9u36KIwbTCo2JAJ+TuJcJBc+UZzEig7RI/S5Dvkc= diff --git a/runner/runner.go b/runner/runner.go index 07c9de1f9..0574181c2 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -734,7 +734,7 @@ func validateFlow(sa flows.SessionAssets, uuid assets.FlowUUID) error { // check for missing assets and log missingDeps := make([]string, 0) - err = flow.InspectRecursively(sa, func(r assets.Reference) { + err = flow.ValidateRecursive(sa, func(r assets.Reference) { missingDeps = append(missingDeps, r.String()) }) diff --git a/web/flow/flow.go b/web/flow/flow.go index 84f4b2fd4..a1ac3e86d 100644 --- a/web/flow/flow.go +++ b/web/flow/flow.go @@ -127,14 +127,19 @@ func handleValidate(ctx context.Context, s *web.Server, r *http.Request) (interf if err != nil { return nil, http.StatusInternalServerError, errors.Wrapf(err, "unable get session assets") } + + if err := flow.Validate(sa, nil); err != nil { + return nil, http.StatusUnprocessableEntity, err + } } - // inspect the flow to get dependecies, results etc - if err := flow.Inspect(sa); err != nil { - return nil, http.StatusUnprocessableEntity, err + // this endpoint returns inspection results inside the definition + result, err := flow.MarshalWithInfo() + if err != nil { + return nil, http.StatusInternalServerError, errors.Wrapf(err, "unable to marshal flow") } - return flow, http.StatusOK, nil + return json.RawMessage(result), http.StatusOK, nil } // Clones a flow. From 943efdef555c4e5a4f8b67d2dfdcdb7a49fd2e5f Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 24 May 2019 16:45:20 -0500 Subject: [PATCH 3/7] Add inspect endpoint, improve tests --- web/flow/flow.go | 131 +++++++++++------- web/flow/flow_test.go | 9 ++ .../testdata/clone_missing_dep_mapping.json | 43 ++++++ web/flow/testdata/clone_struct_invalid.json | 27 ++++ web/flow/testdata/clone_valid.json | 7 +- web/flow/testdata/clone_valid.response.json | 14 +- web/flow/testdata/clone_valid_bad_org.json | 34 +++++ web/flow/testdata/inspect_valid.json | 45 ++++++ web/flow/testdata/inspect_valid.response.json | 17 +++ 9 files changed, 267 insertions(+), 60 deletions(-) create mode 100644 web/flow/testdata/clone_missing_dep_mapping.json create mode 100644 web/flow/testdata/clone_struct_invalid.json create mode 100644 web/flow/testdata/clone_valid_bad_org.json create mode 100644 web/flow/testdata/inspect_valid.json create mode 100644 web/flow/testdata/inspect_valid.response.json diff --git a/web/flow/flow.go b/web/flow/flow.go index a1ac3e86d..6c98a2928 100644 --- a/web/flow/flow.go +++ b/web/flow/flow.go @@ -14,12 +14,15 @@ import ( "github.com/nyaruka/mailroom/config" "github.com/nyaruka/mailroom/models" "github.com/nyaruka/mailroom/web" + + "github.com/jmoiron/sqlx" "github.com/pkg/errors" ) func init() { web.RegisterJSONRoute(http.MethodPost, "/mr/flow/migrate", web.RequireAuthToken(handleMigrate)) web.RegisterJSONRoute(http.MethodPost, "/mr/flow/validate", web.RequireAuthToken(handleValidate)) + web.RegisterJSONRoute(http.MethodPost, "/mr/flow/inspect", web.RequireAuthToken(handleInspect)) web.RegisterJSONRoute(http.MethodPost, "/mr/flow/clone", web.RequireAuthToken(handleClone)) } @@ -37,19 +40,10 @@ type migrateRequest struct { func handleMigrate(ctx context.Context, s *web.Server, r *http.Request) (interface{}, int, error) { request := &migrateRequest{} - body, err := ioutil.ReadAll(io.LimitReader(r.Body, web.MaxRequestBytes)) - if err != nil { + if err := readRequest(r, request); err != nil { return nil, http.StatusBadRequest, err } - if err := r.Body.Close(); err != nil { - return nil, http.StatusInternalServerError, err - } - - if err := utils.UnmarshalAndValidate(body, request); err != nil { - return nil, http.StatusBadRequest, errors.Wrapf(err, "error unmarshalling request") - } - legacyFlow, err := legacy.ReadLegacyFlow(request.Flow) if err != nil { return nil, http.StatusBadRequest, errors.Wrapf(err, "error reading legacy flow") @@ -86,21 +80,12 @@ type validateRequest struct { func handleValidate(ctx context.Context, s *web.Server, r *http.Request) (interface{}, int, error) { request := &validateRequest{} - body, err := ioutil.ReadAll(io.LimitReader(r.Body, web.MaxRequestBytes)) - if err != nil { + if err := readRequest(r, request); err != nil { return nil, http.StatusBadRequest, err } - if err := r.Body.Close(); err != nil { - return nil, http.StatusInternalServerError, err - } - - if err := utils.UnmarshalAndValidate(body, request); err != nil { - return nil, http.StatusBadRequest, errors.Wrapf(err, "error unmarshalling request") - } - var flowDef = request.Flow - var sa flows.SessionAssets + var err error // migrate definition if it is in legacy format if legacy.IsLegacyDefinition(flowDef) { @@ -116,20 +101,11 @@ func handleValidate(ctx context.Context, s *web.Server, r *http.Request) (interf return nil, http.StatusUnprocessableEntity, err } - // if we have an org ID, build a session assets for it + // if we have an org ID, do asset validation if request.OrgID != models.NilOrgID { - org, err := models.NewOrgAssets(s.CTX, s.DB, request.OrgID, nil) + status, err := validate(s.CTX, s.DB, request.OrgID, flow) if err != nil { - return nil, http.StatusBadRequest, err - } - - sa, err = models.NewSessionAssets(org) - if err != nil { - return nil, http.StatusInternalServerError, errors.Wrapf(err, "unable get session assets") - } - - if err := flow.Validate(sa, nil); err != nil { - return nil, http.StatusUnprocessableEntity, err + return nil, status, err } } @@ -142,53 +118,108 @@ func handleValidate(ctx context.Context, s *web.Server, r *http.Request) (interf return json.RawMessage(result), http.StatusOK, nil } -// Clones a flow. +// Inspects a flow. +// +// { +// "flow": { "uuid": "468621a8-32e6-4cd2-afc1-04416f7151f0", "nodes": [...]} +// } +// +type inspectRequest struct { + Flow json.RawMessage `json:"flow" validate:"required"` +} + +func handleInspect(ctx context.Context, s *web.Server, r *http.Request) (interface{}, int, error) { + request := &inspectRequest{} + if err := readRequest(r, request); err != nil { + return nil, http.StatusBadRequest, err + } + + // try to read the flow definition which will fail if it's invalid + flow, err := definition.ReadFlow(request.Flow) + if err != nil { + return nil, http.StatusUnprocessableEntity, err + } + + info := flow.Inspect() + + return info, http.StatusOK, nil +} + +// Clones a flow, replacing all UUIDs with either the given mapping or new random UUIDs. +// If `validate_with_org_id` is specified then the cloned flow will be validated against +// the assets of that org. // // { // "dependency_mapping": { // "4ee4189e-0c06-4b00-b54f-5621329de947": "db31d23f-65b8-4518-b0f6-45638bfbbbf2", // "723e62d8-a544-448f-8590-1dfd0fccfcd4": "f1fd861c-9e75-4376-a829-dcf76db6e721" // }, -// "flow": { "uuid": "468621a8-32e6-4cd2-afc1-04416f7151f0", "nodes": [...]} +// "flow": { "uuid": "468621a8-32e6-4cd2-afc1-04416f7151f0", "nodes": [...]}, +// "validate_with_org_id": 1 // } // type cloneRequest struct { DependencyMapping map[utils.UUID]utils.UUID `json:"dependency_mapping"` Flow json.RawMessage `json:"flow" validate:"required"` + ValidateWithOrgID models.OrgID `json:"validate_with_org_id"` } func handleClone(ctx context.Context, s *web.Server, r *http.Request) (interface{}, int, error) { request := &cloneRequest{} + if err := readRequest(r, request); err != nil { + return nil, http.StatusBadRequest, err + } + + // try to read the flow definition which will fail if it's invalid + flow, err := definition.ReadFlow(request.Flow) + if err != nil { + return nil, http.StatusUnprocessableEntity, err + } + + clone := flow.Clone(request.DependencyMapping) + + // if we have an org ID, do asset validation on the new clone + if request.ValidateWithOrgID != models.NilOrgID { + status, err := validate(s.CTX, s.DB, request.ValidateWithOrgID, clone) + if err != nil { + return nil, status, err + } + } + + return clone, http.StatusOK, nil +} + +func readRequest(r *http.Request, request interface{}) error { body, err := ioutil.ReadAll(io.LimitReader(r.Body, web.MaxRequestBytes)) if err != nil { - return nil, http.StatusBadRequest, err + return err } if err := r.Body.Close(); err != nil { - return nil, http.StatusInternalServerError, err + return err } if err := utils.UnmarshalAndValidate(body, request); err != nil { - return nil, http.StatusBadRequest, errors.Wrapf(err, "error unmarshalling request") + return errors.Wrapf(err, "error unmarshalling request") } - var flowDef = request.Flow + return nil +} - // migrate definition if it is in legacy format - if legacy.IsLegacyDefinition(flowDef) { - flowDef, err = legacy.MigrateLegacyDefinition(flowDef, "https://"+config.Mailroom.AttachmentDomain) - if err != nil { - return nil, http.StatusBadRequest, err - } +func validate(ctx context.Context, db *sqlx.DB, orgID models.OrgID, flow flows.Flow) (int, error) { + org, err := models.NewOrgAssets(ctx, db, orgID, nil) + if err != nil { + return http.StatusBadRequest, err } - // try to read the flow definition which will fail if it's invalid - flow, err := definition.ReadFlow(flowDef) + sa, err := models.NewSessionAssets(org) if err != nil { - return nil, http.StatusBadRequest, err + return http.StatusInternalServerError, errors.Wrapf(err, "unable build session assets") } - clone := flow.Clone(request.DependencyMapping) + if err := flow.Validate(sa, nil); err != nil { + return http.StatusUnprocessableEntity, err + } - return clone, http.StatusOK, nil + return 0, nil } diff --git a/web/flow/flow_test.go b/web/flow/flow_test.go index b7c088952..b4e90905a 100644 --- a/web/flow/flow_test.go +++ b/web/flow/flow_test.go @@ -46,6 +46,7 @@ func TestServer(t *testing.T) { }{ {URL: "/mr/flow/migrate", Method: "GET", Status: 405, Response: `{"error": "illegal method: GET"}`}, {URL: "/mr/flow/migrate", Method: "POST", BodyFile: "migrate_minimal_legacy.json", Status: 200, ResponseFile: "migrate_minimal_legacy.response.json"}, + {URL: "/mr/flow/validate", Method: "GET", Status: 405, Response: `{"error": "illegal method: GET"}`}, {URL: "/mr/flow/validate", Method: "POST", BodyFile: "validate_valid_legacy.json", Status: 200, ResponseFile: "validate_valid_legacy.response.json"}, {URL: "/mr/flow/validate", Method: "POST", BodyFile: "validate_invalid_legacy.json", Status: 422, ResponseFile: "validate_invalid_legacy.response.json"}, @@ -53,7 +54,15 @@ func TestServer(t *testing.T) { {URL: "/mr/flow/validate", Method: "POST", BodyFile: "validate_invalid.json", Status: 422, ResponseFile: "validate_invalid.response.json"}, {URL: "/mr/flow/validate", Method: "POST", BodyFile: "validate_valid_without_assets.json", Status: 200, ResponseFile: "validate_valid_without_assets.response.json"}, {URL: "/mr/flow/validate", Method: "POST", BodyFile: "validate_legacy_single_msg.json", Status: 200, ResponseFile: "validate_legacy_single_msg.response.json"}, + + {URL: "/mr/flow/inspect", Method: "GET", Status: 405, Response: `{"error": "illegal method: GET"}`}, + {URL: "/mr/flow/inspect", Method: "POST", BodyFile: "inspect_valid.json", Status: 200, ResponseFile: "inspect_valid.response.json"}, + + {URL: "/mr/flow/clone", Method: "GET", Status: 405, Response: `{"error": "illegal method: GET"}`}, {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_valid.json", Status: 200, ResponseFile: "clone_valid.response.json"}, + {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_struct_invalid.json", Status: 422, Response: `{"error": "unable to read node: field 'uuid' is required"}`}, + {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_missing_dep_mapping.json", Status: 422, Response: `{"error": "missing dependencies: group[uuid=59d74b86-3e2f-4a93-aece-b05d2fdcde0c,name=Testers]"}`}, + {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_valid_bad_org.json", Status: 400, Response: `{"error": "error loading environment for org 167733: no org with id: 167733"}`}, } for _, tc := range tcs { diff --git a/web/flow/testdata/clone_missing_dep_mapping.json b/web/flow/testdata/clone_missing_dep_mapping.json new file mode 100644 index 000000000..1228b660a --- /dev/null +++ b/web/flow/testdata/clone_missing_dep_mapping.json @@ -0,0 +1,43 @@ +{ + "dependency_mapping": { + "8f107d42-7416-4cf2-9a51-9490361ad517": "1cf84575-ee14-4253-88b6-e3675c04a066" + }, + "flow": { + "uuid": "8f107d42-7416-4cf2-9a51-9490361ad517", + "name": "Valid Flow", + "spec_version": "13.0.0", + "language": "eng", + "type": "messaging", + "revision": 106, + "expire_after_minutes": 10080, + "localization": {}, + "nodes": [ + { + "uuid": "6fde1a09-3997-47dd-aff0-92e8aff3a642", + "actions": [ + { + "type": "add_contact_groups", + "uuid": "23337aa9-0d3d-4e70-876e-9a2633d1e5e4", + "groups": [ + { + "uuid": "ebe441b4-c581-4b03-b544-5695cfe29bc1", + "name": "Testers" + } + ] + }, + { + "type": "send_msg", + "uuid": "05a5cb7c-bb8a-4ad9-af90-ef9887cc370e", + "text": "Your birthdate is soon" + } + ], + "exits": [ + { + "uuid": "d3f3f024-a90e-43a5-bd5a-7056f5bea699" + } + ] + } + ] + }, + "validate_with_org_id": 1 +} \ No newline at end of file diff --git a/web/flow/testdata/clone_struct_invalid.json b/web/flow/testdata/clone_struct_invalid.json new file mode 100644 index 000000000..3592d164e --- /dev/null +++ b/web/flow/testdata/clone_struct_invalid.json @@ -0,0 +1,27 @@ +{ + "dependency_mapping": { + "8f107d42-7416-4cf2-9a51-9490361ad517": "1cf84575-ee14-4253-88b6-e3675c04a066", + "ebe441b4-c581-4b03-b544-5695cfe29bc1": "5e9d8fab-5e7e-4f51-b533-261af5dea70d" + }, + "flow": { + "uuid": "8f107d42-7416-4cf2-9a51-9490361ad517", + "name": "Valid Flow", + "spec_version": "13.0.0", + "language": "eng", + "type": "messaging", + "revision": 106, + "expire_after_minutes": 10080, + "localization": {}, + "nodes": [ + { + "actions": [], + "exits": [ + { + "uuid": "d3f3f024-a90e-43a5-bd5a-7056f5bea699" + } + ] + } + ] + }, + "validate_with_org_id": 1 +} \ No newline at end of file diff --git a/web/flow/testdata/clone_valid.json b/web/flow/testdata/clone_valid.json index 40151577a..e0d44377a 100644 --- a/web/flow/testdata/clone_valid.json +++ b/web/flow/testdata/clone_valid.json @@ -1,7 +1,7 @@ { "dependency_mapping": { "8f107d42-7416-4cf2-9a51-9490361ad517": "1cf84575-ee14-4253-88b6-e3675c04a066", - "5e9d8fab-5e7e-4f51-b533-261af5dea70d": "ebe441b4-c581-4b03-b544-5695cfe29bc1" + "ebe441b4-c581-4b03-b544-5695cfe29bc1": "5e9d8fab-5e7e-4f51-b533-261af5dea70d" }, "flow": { "uuid": "8f107d42-7416-4cf2-9a51-9490361ad517", @@ -21,7 +21,7 @@ "uuid": "23337aa9-0d3d-4e70-876e-9a2633d1e5e4", "groups": [ { - "uuid": "5e9d8fab-5e7e-4f51-b533-261af5dea70d", + "uuid": "ebe441b4-c581-4b03-b544-5695cfe29bc1", "name": "Testers" } ] @@ -39,5 +39,6 @@ ] } ] - } + }, + "validate_with_org_id": 1 } \ No newline at end of file diff --git a/web/flow/testdata/clone_valid.response.json b/web/flow/testdata/clone_valid.response.json index b716d8c85..bee812761 100644 --- a/web/flow/testdata/clone_valid.response.json +++ b/web/flow/testdata/clone_valid.response.json @@ -1,12 +1,8 @@ { - "uuid": "1cf84575-ee14-4253-88b6-e3675c04a066", - "name": "Valid Flow", - "revision": 106, - "spec_version": "13.0.0", - "type": "messaging", "expire_after_minutes": 10080, "language": "eng", "localization": {}, + "name": "Valid Flow", "nodes": [ { "actions": [ @@ -14,7 +10,7 @@ "groups": [ { "name": "Testers", - "uuid": "ebe441b4-c581-4b03-b544-5695cfe29bc1" + "uuid": "5e9d8fab-5e7e-4f51-b533-261af5dea70d" } ], "type": "add_contact_groups", @@ -33,5 +29,9 @@ ], "uuid": "1ae96956-4b34-433e-8d1a-f05fe6923d6d" } - ] + ], + "revision": 106, + "spec_version": "13.0.0", + "type": "messaging", + "uuid": "1cf84575-ee14-4253-88b6-e3675c04a066" } \ No newline at end of file diff --git a/web/flow/testdata/clone_valid_bad_org.json b/web/flow/testdata/clone_valid_bad_org.json new file mode 100644 index 000000000..1afaedbc6 --- /dev/null +++ b/web/flow/testdata/clone_valid_bad_org.json @@ -0,0 +1,34 @@ +{ + "dependency_mapping": { + "8f107d42-7416-4cf2-9a51-9490361ad517": "1cf84575-ee14-4253-88b6-e3675c04a066", + "ebe441b4-c581-4b03-b544-5695cfe29bc1": "5e9d8fab-5e7e-4f51-b533-261af5dea70d" + }, + "flow": { + "uuid": "8f107d42-7416-4cf2-9a51-9490361ad517", + "name": "Valid Flow", + "spec_version": "13.0.0", + "language": "eng", + "type": "messaging", + "revision": 106, + "expire_after_minutes": 10080, + "localization": {}, + "nodes": [ + { + "uuid": "6fde1a09-3997-47dd-aff0-92e8aff3a642", + "actions": [ + { + "type": "send_msg", + "uuid": "05a5cb7c-bb8a-4ad9-af90-ef9887cc370e", + "text": "Your birthdate is soon" + } + ], + "exits": [ + { + "uuid": "d3f3f024-a90e-43a5-bd5a-7056f5bea699" + } + ] + } + ] + }, + "validate_with_org_id": 167733 +} \ No newline at end of file diff --git a/web/flow/testdata/inspect_valid.json b/web/flow/testdata/inspect_valid.json new file mode 100644 index 000000000..506dec5d6 --- /dev/null +++ b/web/flow/testdata/inspect_valid.json @@ -0,0 +1,45 @@ +{ + "dependency_mapping": { + "8f107d42-7416-4cf2-9a51-9490361ad517": "1cf84575-ee14-4253-88b6-e3675c04a066", + "ebe441b4-c581-4b03-b544-5695cfe29bc1": "5e9d8fab-5e7e-4f51-b533-261af5dea70d" + }, + "flow": { + "uuid": "8f107d42-7416-4cf2-9a51-9490361ad517", + "name": "Valid Flow", + "spec_version": "13.0.0", + "language": "eng", + "type": "messaging", + "revision": 106, + "expire_after_minutes": 10080, + "localization": {}, + "nodes": [ + { + "uuid": "6fde1a09-3997-47dd-aff0-92e8aff3a642", + "actions": [ + { + "type": "add_contact_groups", + "uuid": "23337aa9-0d3d-4e70-876e-9a2633d1e5e4", + "groups": [ + { + "uuid": "ebe441b4-c581-4b03-b544-5695cfe29bc1", + "name": "Testers" + } + ] + }, + { + "type": "set_run_result", + "uuid": "05a5cb7c-bb8a-4ad9-af90-ef9887cc370e", + "name": "Answer", + "value": "Yes" + } + ], + "exits": [ + { + "uuid": "d3f3f024-a90e-43a5-bd5a-7056f5bea699" + } + ] + } + ] + }, + "validate_with_org_id": 1 +} \ No newline at end of file diff --git a/web/flow/testdata/inspect_valid.response.json b/web/flow/testdata/inspect_valid.response.json new file mode 100644 index 000000000..9b38d03e7 --- /dev/null +++ b/web/flow/testdata/inspect_valid.response.json @@ -0,0 +1,17 @@ +{ + "dependencies": { + "groups": [ + { + "name": "Testers", + "uuid": "ebe441b4-c581-4b03-b544-5695cfe29bc1" + } + ] + }, + "results": [ + { + "key": "answer", + "name": "Answer" + } + ], + "waiting_exits": [] +} \ No newline at end of file From e8c20362a249f5f6c4198c593777e31a4ab9f63a Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 24 May 2019 17:05:06 -0500 Subject: [PATCH 4/7] Simplify endpoint handler functions --- web/flow/flow.go | 86 ++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 51 deletions(-) diff --git a/web/flow/flow.go b/web/flow/flow.go index 6c98a2928..0e11af164 100644 --- a/web/flow/flow.go +++ b/web/flow/flow.go @@ -3,8 +3,6 @@ package flow import ( "context" "encoding/json" - "io" - "io/ioutil" "net/http" "github.com/nyaruka/goflow/flows" @@ -29,31 +27,23 @@ func init() { // Migrates a legacy flow to the new flow definition specification // // { -// "flow": {"uuid": "468621a8-32e6-4cd2-afc1-04416f7151f0", "action_sets": [], ...}, -// "include_ui": false +// "flow": {"uuid": "468621a8-32e6-4cd2-afc1-04416f7151f0", "action_sets": [], ...} // } // type migrateRequest struct { Flow json.RawMessage `json:"flow" validate:"required"` - IncludeUI *bool `json:"include_ui"` + IncludeUI *bool `json:"include_ui"` // ignored } func handleMigrate(ctx context.Context, s *web.Server, r *http.Request) (interface{}, int, error) { request := &migrateRequest{} - if err := readRequest(r, request); err != nil { - return nil, http.StatusBadRequest, err + if err := utils.UnmarshalAndValidateWithLimit(r.Body, request, web.MaxRequestBytes); err != nil { + return nil, http.StatusBadRequest, errors.Wrapf(err, "request failed validation") } - legacyFlow, err := legacy.ReadLegacyFlow(request.Flow) + flow, err := readFlow(request.Flow) if err != nil { - return nil, http.StatusBadRequest, errors.Wrapf(err, "error reading legacy flow") - } - - includeUI := request.IncludeUI == nil || *request.IncludeUI - - flow, err := legacyFlow.Migrate(includeUI, "https://"+config.Mailroom.AttachmentDomain) - if err != nil { - return nil, http.StatusBadRequest, errors.Wrapf(err, "error migrating legacy flow") + return nil, http.StatusUnprocessableEntity, err } return flow, http.StatusOK, nil @@ -80,23 +70,11 @@ type validateRequest struct { func handleValidate(ctx context.Context, s *web.Server, r *http.Request) (interface{}, int, error) { request := &validateRequest{} - if err := readRequest(r, request); err != nil { - return nil, http.StatusBadRequest, err - } - - var flowDef = request.Flow - var err error - - // migrate definition if it is in legacy format - if legacy.IsLegacyDefinition(flowDef) { - flowDef, err = legacy.MigrateLegacyDefinition(flowDef, "https://"+config.Mailroom.AttachmentDomain) - if err != nil { - return nil, http.StatusUnprocessableEntity, err - } + if err := utils.UnmarshalAndValidateWithLimit(r.Body, request, web.MaxRequestBytes); err != nil { + return nil, http.StatusBadRequest, errors.Wrapf(err, "request failed validation") } - // try to read the flow definition which will fail if it's invalid - flow, err := definition.ReadFlow(flowDef) + flow, err := readFlow(request.Flow) if err != nil { return nil, http.StatusUnprocessableEntity, err } @@ -130,19 +108,17 @@ type inspectRequest struct { func handleInspect(ctx context.Context, s *web.Server, r *http.Request) (interface{}, int, error) { request := &inspectRequest{} - if err := readRequest(r, request); err != nil { - return nil, http.StatusBadRequest, err + if err := utils.UnmarshalAndValidateWithLimit(r.Body, request, web.MaxRequestBytes); err != nil { + return nil, http.StatusBadRequest, errors.Wrapf(err, "request failed validation") } - // try to read the flow definition which will fail if it's invalid + // try to read the flow definition flow, err := definition.ReadFlow(request.Flow) if err != nil { return nil, http.StatusUnprocessableEntity, err } - info := flow.Inspect() - - return info, http.StatusOK, nil + return flow.Inspect(), http.StatusOK, nil } // Clones a flow, replacing all UUIDs with either the given mapping or new random UUIDs. @@ -166,11 +142,11 @@ type cloneRequest struct { func handleClone(ctx context.Context, s *web.Server, r *http.Request) (interface{}, int, error) { request := &cloneRequest{} - if err := readRequest(r, request); err != nil { - return nil, http.StatusBadRequest, err + if err := utils.UnmarshalAndValidateWithLimit(r.Body, request, web.MaxRequestBytes); err != nil { + return nil, http.StatusBadRequest, errors.Wrapf(err, "request failed validation") } - // try to read the flow definition which will fail if it's invalid + // try to read the flow definition flow, err := definition.ReadFlow(request.Flow) if err != nil { return nil, http.StatusUnprocessableEntity, err @@ -189,21 +165,29 @@ func handleClone(ctx context.Context, s *web.Server, r *http.Request) (interface return clone, http.StatusOK, nil } -func readRequest(r *http.Request, request interface{}) error { - body, err := ioutil.ReadAll(io.LimitReader(r.Body, web.MaxRequestBytes)) - if err != nil { - return err - } +func readFlow(flowDef json.RawMessage) (flows.Flow, error) { + var flow flows.Flow + var err error - if err := r.Body.Close(); err != nil { - return err - } + if legacy.IsLegacyDefinition(flowDef) { + // migrate definition if it is in legacy format + legacyFlow, err := legacy.ReadLegacyFlow(flowDef) + if err != nil { + return nil, err + } - if err := utils.UnmarshalAndValidate(body, request); err != nil { - return errors.Wrapf(err, "error unmarshalling request") + flow, err = legacyFlow.Migrate(true, "https://"+config.Mailroom.AttachmentDomain) + if err != nil { + return nil, err + } + } else { + flow, err = definition.ReadFlow(flowDef) + if err != nil { + return nil, err + } } - return nil + return flow, nil } func validate(ctx context.Context, db *sqlx.DB, orgID models.OrgID, flow flows.Flow) (int, error) { From af5aa97c2104db6a6711ce2a782c9a77d410f5c8 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Fri, 24 May 2019 17:12:33 -0500 Subject: [PATCH 5/7] Add UI sections to flow endpoint tests to ensure it's correctly perserved/transformed --- web/flow/testdata/clone_valid.json | 14 ++++++- web/flow/testdata/clone_valid.response.json | 20 ++++++++-- web/flow/testdata/validate_valid.json | 14 ++++++- .../testdata/validate_valid.response.json | 40 ++++++++++++------- 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/web/flow/testdata/clone_valid.json b/web/flow/testdata/clone_valid.json index e0d44377a..4abb9fd68 100644 --- a/web/flow/testdata/clone_valid.json +++ b/web/flow/testdata/clone_valid.json @@ -38,7 +38,19 @@ } ] } - ] + ], + "_ui": { + "nodes": { + "6fde1a09-3997-47dd-aff0-92e8aff3a642": { + "position": { + "left": 100, + "top": 0 + }, + "type": "execute_actions" + } + }, + "stickies": {} + } }, "validate_with_org_id": 1 } \ No newline at end of file diff --git a/web/flow/testdata/clone_valid.response.json b/web/flow/testdata/clone_valid.response.json index bee812761..5edf250f5 100644 --- a/web/flow/testdata/clone_valid.response.json +++ b/web/flow/testdata/clone_valid.response.json @@ -1,4 +1,8 @@ { + "uuid": "1cf84575-ee14-4253-88b6-e3675c04a066", + "revision": 106, + "spec_version": "13.0.0", + "type": "messaging", "expire_after_minutes": 10080, "language": "eng", "localization": {}, @@ -30,8 +34,16 @@ "uuid": "1ae96956-4b34-433e-8d1a-f05fe6923d6d" } ], - "revision": 106, - "spec_version": "13.0.0", - "type": "messaging", - "uuid": "1cf84575-ee14-4253-88b6-e3675c04a066" + "_ui": { + "nodes": { + "1ae96956-4b34-433e-8d1a-f05fe6923d6d": { + "position": { + "left": 100, + "top": 0 + }, + "type": "execute_actions" + } + }, + "stickies": {} + } } \ No newline at end of file diff --git a/web/flow/testdata/validate_valid.json b/web/flow/testdata/validate_valid.json index cbde534f2..4f6148058 100644 --- a/web/flow/testdata/validate_valid.json +++ b/web/flow/testdata/validate_valid.json @@ -35,6 +35,18 @@ } ] } - ] + ], + "_ui": { + "nodes": { + "6fde1a09-3997-47dd-aff0-92e8aff3a642": { + "position": { + "left": 100, + "top": 0 + }, + "type": "execute_actions" + } + }, + "stickies": {} + } } } \ No newline at end of file diff --git a/web/flow/testdata/validate_valid.response.json b/web/flow/testdata/validate_valid.response.json index cffb08af8..fd356f607 100644 --- a/web/flow/testdata/validate_valid.response.json +++ b/web/flow/testdata/validate_valid.response.json @@ -1,14 +1,8 @@ { - "_dependencies": { - "groups": [ - { - "name": "Testers", - "uuid": "5e9d8fab-5e7e-4f51-b533-261af5dea70d" - } - ] - }, - "_results": [], - "_waiting_exits": [], + "uuid": "8f107d42-7416-4cf2-9a51-9490361ad517", + "revision": 106, + "spec_version": "13.0.0", + "type": "messaging", "expire_after_minutes": 10080, "language": "eng", "localization": {}, @@ -40,8 +34,26 @@ "uuid": "6fde1a09-3997-47dd-aff0-92e8aff3a642" } ], - "revision": 106, - "spec_version": "13.0.0", - "type": "messaging", - "uuid": "8f107d42-7416-4cf2-9a51-9490361ad517" + "_ui": { + "nodes": { + "6fde1a09-3997-47dd-aff0-92e8aff3a642": { + "position": { + "left": 100, + "top": 0 + }, + "type": "execute_actions" + } + }, + "stickies": {} + }, + "_dependencies": { + "groups": [ + { + "name": "Testers", + "uuid": "5e9d8fab-5e7e-4f51-b533-261af5dea70d" + } + ] + }, + "_results": [], + "_waiting_exits": [] } \ No newline at end of file From 78e79855d4a7c38f083c557245048dd1fd7577ee Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 27 May 2019 12:12:25 -0500 Subject: [PATCH 6/7] Fix non-determinism in tests --- go.mod | 2 +- go.sum | 4 +- web/flow/flow_test.go | 39 +++++++++------- web/flow/testdata/clone_valid.response.json | 49 --------------------- 4 files changed, 25 insertions(+), 69 deletions(-) delete mode 100644 web/flow/testdata/clone_valid.response.json diff --git a/go.mod b/go.mod index f6feb3174..9d4dbfdcf 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/mattn/go-sqlite3 v1.10.0 // indirect github.com/nyaruka/ezconf v0.2.1 github.com/nyaruka/gocommon v1.0.0 - github.com/nyaruka/goflow v0.40.1 + github.com/nyaruka/goflow v0.40.3 github.com/nyaruka/librato v0.0.0-20180827155909-cacc769357b8 github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d github.com/nyaruka/null v1.1.1 diff --git a/go.sum b/go.sum index 025419788..27389c2ad 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/nyaruka/gocommon v0.2.0 h1:1Le4Ok0Zp2RYULue0n4/02zL+1MrykN/C79HhirGeR github.com/nyaruka/gocommon v0.2.0/go.mod h1:ZrhaOKNc+kK1qWNuCuZivskT+ygLyIwu4KZVgcaC1mw= github.com/nyaruka/gocommon v1.0.0 h1:4gdAMOR4BTMHHZjOy5WhfKYGUZVmJ+3LPh1sj011Qzk= github.com/nyaruka/gocommon v1.0.0/go.mod h1:QbdU2J9WBsqBmeZRuwndf2f6O7rD7mkC0bGn5UNnwjI= -github.com/nyaruka/goflow v0.40.1 h1:+8rvAmL5tZAbJ8o3WLCL9zEdyD8lKhcunnQpxABma00= -github.com/nyaruka/goflow v0.40.1/go.mod h1:QitrAujTi7Mc75aVIoCpAmFsfO794+ljo9QNGt7qSHY= +github.com/nyaruka/goflow v0.40.3 h1:lQoBdiY1cwg+HVt/Fi+DsH27T/ys1rD6kpnAmpPkh9g= +github.com/nyaruka/goflow v0.40.3/go.mod h1:QitrAujTi7Mc75aVIoCpAmFsfO794+ljo9QNGt7qSHY= github.com/nyaruka/librato v0.0.0-20180827155909-cacc769357b8 h1:TOvxy0u6LNTWP3gwbdNVCiByXJupr9ATFdzBnBJ2TY8= github.com/nyaruka/librato v0.0.0-20180827155909-cacc769357b8/go.mod h1:huVocfMEHkttMHD4hSr/wjWNyTx/YMzwwajVzV2bq+0= github.com/nyaruka/logrus_sentry v0.8.2-0.20190129182604-c2962b80ba7d h1:hyp9u36KIwbTCo2JAJ+TuJcJBc+UZzEig7RI/S5Dvkc= diff --git a/web/flow/flow_test.go b/web/flow/flow_test.go index b4e90905a..cad1a2c1f 100644 --- a/web/flow/flow_test.go +++ b/web/flow/flow_test.go @@ -37,12 +37,13 @@ func TestServer(t *testing.T) { defer utils.SetUUIDGenerator(utils.DefaultUUIDGenerator) tcs := []struct { - URL string - Method string - BodyFile string - Status int - Response string - ResponseFile string + URL string + Method string + BodyFile string + Status int + Response string + ResponseFile string + ResponsePattern string }{ {URL: "/mr/flow/migrate", Method: "GET", Status: 405, Response: `{"error": "illegal method: GET"}`}, {URL: "/mr/flow/migrate", Method: "POST", BodyFile: "migrate_minimal_legacy.json", Status: 200, ResponseFile: "migrate_minimal_legacy.response.json"}, @@ -59,30 +60,24 @@ func TestServer(t *testing.T) { {URL: "/mr/flow/inspect", Method: "POST", BodyFile: "inspect_valid.json", Status: 200, ResponseFile: "inspect_valid.response.json"}, {URL: "/mr/flow/clone", Method: "GET", Status: 405, Response: `{"error": "illegal method: GET"}`}, - {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_valid.json", Status: 200, ResponseFile: "clone_valid.response.json"}, + {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_valid.json", Status: 200, ResponsePattern: `"uuid": "1cf84575-ee14-4253-88b6-e3675c04a066"`}, {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_struct_invalid.json", Status: 422, Response: `{"error": "unable to read node: field 'uuid' is required"}`}, - {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_missing_dep_mapping.json", Status: 422, Response: `{"error": "missing dependencies: group[uuid=59d74b86-3e2f-4a93-aece-b05d2fdcde0c,name=Testers]"}`}, + {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_missing_dep_mapping.json", Status: 422, ResponsePattern: `group\[uuid=[-0-9a-f]{36},name=Testers\]`}, {URL: "/mr/flow/clone", Method: "POST", BodyFile: "clone_valid_bad_org.json", Status: 400, Response: `{"error": "error loading environment for org 167733: no org with id: 167733"}`}, } for _, tc := range tcs { - utils.SetUUIDGenerator(utils.NewSeededUUID4Generator(12345)) + utils.SetUUIDGenerator(test.NewSeededUUIDGenerator(123456)) + time.Sleep(1 * time.Second) testID := fmt.Sprintf("%s %s %s", tc.Method, tc.URL, tc.BodyFile) var requestBody io.Reader - var expectedRespBody []byte var err error if tc.BodyFile != "" { requestBody, err = os.Open("testdata/" + tc.BodyFile) require.NoError(t, err, "unable to open %s", tc.BodyFile) } - if tc.ResponseFile != "" { - expectedRespBody, err = ioutil.ReadFile("testdata/" + tc.ResponseFile) - require.NoError(t, err, "unable to read %s", tc.ResponseFile) - } else { - expectedRespBody = []byte(tc.Response) - } req, err := http.NewRequest(tc.Method, "http://localhost:8090"+tc.URL, requestBody) assert.NoError(t, err, "error creating request in %s", testID) @@ -94,6 +89,16 @@ func TestServer(t *testing.T) { require.NoError(t, err, "error reading body in %s", testID) assert.Equal(t, tc.Status, resp.StatusCode, "unexpected status in %s (response=%s)", testID, content) - test.AssertEqualJSON(t, expectedRespBody, content, "response mismatch in %s", testID) + + if tc.ResponseFile != "" { + expectedRespBody, err := ioutil.ReadFile("testdata/" + tc.ResponseFile) + require.NoError(t, err, "unable to read %s", tc.ResponseFile) + + test.AssertEqualJSON(t, expectedRespBody, content, "response mismatch in %s", testID) + } else if tc.ResponsePattern != "" { + assert.Regexp(t, tc.ResponsePattern, string(content), "response mismatch in %s", testID) + } else { + test.AssertEqualJSON(t, []byte(tc.Response), content, "response mismatch in %s", testID) + } } } diff --git a/web/flow/testdata/clone_valid.response.json b/web/flow/testdata/clone_valid.response.json deleted file mode 100644 index 5edf250f5..000000000 --- a/web/flow/testdata/clone_valid.response.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "uuid": "1cf84575-ee14-4253-88b6-e3675c04a066", - "revision": 106, - "spec_version": "13.0.0", - "type": "messaging", - "expire_after_minutes": 10080, - "language": "eng", - "localization": {}, - "name": "Valid Flow", - "nodes": [ - { - "actions": [ - { - "groups": [ - { - "name": "Testers", - "uuid": "5e9d8fab-5e7e-4f51-b533-261af5dea70d" - } - ], - "type": "add_contact_groups", - "uuid": "e7187099-7d38-4f60-955c-325957214c42" - }, - { - "text": "Your birthdate is soon", - "type": "send_msg", - "uuid": "59d74b86-3e2f-4a93-aece-b05d2fdcde0c" - } - ], - "exits": [ - { - "uuid": "9688d21d-95aa-4bed-afc7-f31b35731a3d" - } - ], - "uuid": "1ae96956-4b34-433e-8d1a-f05fe6923d6d" - } - ], - "_ui": { - "nodes": { - "1ae96956-4b34-433e-8d1a-f05fe6923d6d": { - "position": { - "left": 100, - "top": 0 - }, - "type": "execute_actions" - } - }, - "stickies": {} - } -} \ No newline at end of file From dfe1508daa5d658a21c2ce5d627f1941cfd58e50 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Mon, 27 May 2019 12:24:34 -0500 Subject: [PATCH 7/7] Fix test --- ivr/nexmo/nexmo_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ivr/nexmo/nexmo_test.go b/ivr/nexmo/nexmo_test.go index 3b06644a9..4d9c162f3 100644 --- a/ivr/nexmo/nexmo_test.go +++ b/ivr/nexmo/nexmo_test.go @@ -10,6 +10,7 @@ import ( "github.com/nyaruka/goflow/flows/routers/waits" "github.com/nyaruka/goflow/flows/routers/waits/hints" "github.com/nyaruka/goflow/utils" + "github.com/nyaruka/goflow/test" "github.com/nyaruka/mailroom/config" "github.com/nyaruka/mailroom/models" "github.com/nyaruka/mailroom/testsuite" @@ -35,7 +36,7 @@ func TestResponseForSprint(t *testing.T) { db.MustExec(`UPDATE channels_channel SET config = '{"nexmo_app_id": "app_id", "nexmo_app_private_key": "-----BEGIN PRIVATE KEY-----\nMIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKNwapOQ6rQJHetP\nHRlJBIh1OsOsUBiXb3rXXE3xpWAxAha0MH+UPRblOko+5T2JqIb+xKf9Vi3oTM3t\nKvffaOPtzKXZauscjq6NGzA3LgeiMy6q19pvkUUOlGYK6+Xfl+B7Xw6+hBMkQuGE\nnUS8nkpR5mK4ne7djIyfHFfMu4ptAgMBAAECgYA+s0PPtMq1osG9oi4xoxeAGikf\nJB3eMUptP+2DYW7mRibc+ueYKhB9lhcUoKhlQUhL8bUUFVZYakP8xD21thmQqnC4\nf63asad0ycteJMLb3r+z26LHuCyOdPg1pyLk3oQ32lVQHBCYathRMcVznxOG16VK\nI8BFfstJTaJu0lK/wQJBANYFGusBiZsJQ3utrQMVPpKmloO2++4q1v6ZR4puDQHx\nTjLjAIgrkYfwTJBLBRZxec0E7TmuVQ9uJ+wMu/+7zaUCQQDDf2xMnQqYknJoKGq+\noAnyC66UqWC5xAnQS32mlnJ632JXA0pf9pb1SXAYExB1p9Dfqd3VAwQDwBsDDgP6\nHD8pAkEA0lscNQZC2TaGtKZk2hXkdcH1SKru/g3vWTkRHxfCAznJUaza1fx0wzdG\nGcES1Bdez0tbW4llI5By/skZc2eE3QJAFl6fOskBbGHde3Oce0F+wdZ6XIJhEgCP\niukIcKZoZQzoiMJUoVRrA5gqnmaYDI5uRRl/y57zt6YksR3KcLUIuQJAd242M/WF\n6YAZat3q/wEeETeQq1wrooew+8lHl05/Nt0cCpV48RGEhJ83pzBm3mnwHf8lTBJH\nx6XroMXsmbnsEw==\n-----END PRIVATE KEY-----", "callback_domain": "localhost:8090"}', role='SRCA' WHERE id = $1`, models.NexmoChannelID) // set our UUID generator - utils.SetUUIDGenerator(utils.NewSeededUUID4Generator(0)) + utils.SetUUIDGenerator(test.NewSeededUUIDGenerator(0)) // set our attachment domain for testing config.Mailroom.AttachmentDomain = "mailroom.io"