package tests import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests" "geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/example-project/internal/project" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "gopkg.in/mgo.v2/bson" ) // TestProjects is the entry point for the projects func TestProjects(t *testing.T) { defer tests.Recover(t) t.Run("getProjects200Empty", getProjects200Empty) t.Run("postProject400", postProject400) t.Run("postProject401", postProject401) t.Run("getProject404", getProject404) t.Run("getProject400", getProject400) t.Run("deleteProject404", deleteProject404) t.Run("putProject404", putProject404) t.Run("crudProjects", crudProject) } // getProjects200Empty validates an empty projects list can be retrieved with the endpoint. func getProjects200Empty(t *testing.T) { r := httptest.NewRequest("GET", "/v1/projects", nil) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) t.Log("Given the need to fetch an empty list of projects with the projects endpoint.") { t.Log("\tTest 0:\tWhen fetching an empty project list.") { if w.Code != http.StatusOK { t.Fatalf("\t%s\tShould receive a status code of 200 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 200 for the response.", tests.Success) recv := w.Body.String() resp := `[]` if resp != recv { t.Log("Got :", recv) t.Log("Want:", resp) t.Fatalf("\t%s\tShould get the expected result.", tests.Failed) } t.Logf("\t%s\tShould get the expected result.", tests.Success) } } } // postProject400 validates a project can't be created with the endpoint // unless a valid project document is submitted. func postProject400(t *testing.T) { r := httptest.NewRequest("POST", "/v1/projects", strings.NewReader(`{}`)) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) t.Log("Given the need to validate a new project can't be created with an invalid document.") { t.Log("\tTest 0:\tWhen using an incomplete project value.") { if w.Code != http.StatusBadRequest { t.Fatalf("\t%s\tShould receive a status code of 400 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 400 for the response.", tests.Success) // Inspect the response. var got web.ErrorResponse if err := json.NewDecoder(w.Body).Decode(&got); err != nil { t.Fatalf("\t%s\tShould be able to unmarshal the response to an error type : %v", tests.Failed, err) } t.Logf("\t%s\tShould be able to unmarshal the response to an error type.", tests.Success) // Define what we want to see. want := web.ErrorResponse{ Error: "field validation error", Fields: []web.FieldError{ {Field: "name", Error: "name is a required field"}, {Field: "cost", Error: "cost is a required field"}, {Field: "quantity", Error: "quantity is a required field"}, }, } // We can't rely on the order of the field errors so they have to be // sorted. Tell the cmp package how to sort them. sorter := cmpopts.SortSlices(func(a, b web.FieldError) bool { return a.Field < b.Field }) if diff := cmp.Diff(want, got, sorter); diff != "" { t.Fatalf("\t%s\tShould get the expected result. Diff:\n%s", tests.Failed, diff) } t.Logf("\t%s\tShould get the expected result.", tests.Success) } } } // postProject401 validates a project can't be created with the endpoint // unless the user is authenticated func postProject401(t *testing.T) { np := project.NewProject{ Name: "Comic Books", Cost: 25, Quantity: 60, } body, err := json.Marshal(&np) if err != nil { t.Fatal(err) } r := httptest.NewRequest("POST", "/v1/projects", bytes.NewBuffer(body)) w := httptest.NewRecorder() // Not setting an authorization header a.ServeHTTP(w, r) t.Log("Given the need to validate a new project can't be created with an invalid document.") { t.Log("\tTest 0:\tWhen using an incomplete project value.") { if w.Code != http.StatusUnauthorized { t.Fatalf("\t%s\tShould receive a status code of 401 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 401 for the response.", tests.Success) } } } // getProject400 validates a project request for a malformed id. func getProject400(t *testing.T) { id := "12345" r := httptest.NewRequest("GET", "/v1/projects/"+id, nil) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) t.Log("Given the need to validate getting a project with a malformed id.") { t.Logf("\tTest 0:\tWhen using the new project %s.", id) { if w.Code != http.StatusBadRequest { t.Fatalf("\t%s\tShould receive a status code of 400 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 400 for the response.", tests.Success) recv := w.Body.String() resp := `{"error":"ID is not in its proper form"}` if resp != recv { t.Log("Got :", recv) t.Log("Want:", resp) t.Fatalf("\t%s\tShould get the expected result.", tests.Failed) } t.Logf("\t%s\tShould get the expected result.", tests.Success) } } } // getProject404 validates a project request for a project that does not exist with the endpoint. func getProject404(t *testing.T) { id := bson.NewObjectId().Hex() r := httptest.NewRequest("GET", "/v1/projects/"+id, nil) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) t.Log("Given the need to validate getting a project with an unknown id.") { t.Logf("\tTest 0:\tWhen using the new project %s.", id) { if w.Code != http.StatusNotFound { t.Fatalf("\t%s\tShould receive a status code of 404 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 404 for the response.", tests.Success) recv := w.Body.String() resp := "Entity not found" if !strings.Contains(recv, resp) { t.Log("Got :", recv) t.Log("Want:", resp) t.Fatalf("\t%s\tShould get the expected result.", tests.Failed) } t.Logf("\t%s\tShould get the expected result.", tests.Success) } } } // deleteProject404 validates deleting a project that does not exist. func deleteProject404(t *testing.T) { id := bson.NewObjectId().Hex() r := httptest.NewRequest("DELETE", "/v1/projects/"+id, nil) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) t.Log("Given the need to validate deleting a project that does not exist.") { t.Logf("\tTest 0:\tWhen using the new project %s.", id) { if w.Code != http.StatusNotFound { t.Fatalf("\t%s\tShould receive a status code of 404 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 404 for the response.", tests.Success) recv := w.Body.String() resp := "Entity not found" if !strings.Contains(recv, resp) { t.Log("Got :", recv) t.Log("Want:", resp) t.Fatalf("\t%s\tShould get the expected result.", tests.Failed) } t.Logf("\t%s\tShould get the expected result.", tests.Success) } } } // putProject404 validates updating a project that does not exist. func putProject404(t *testing.T) { up := project.UpdateProject{ Name: tests.StringPointer("Nonexistent"), } id := bson.NewObjectId().Hex() body, err := json.Marshal(&up) if err != nil { t.Fatal(err) } r := httptest.NewRequest("PUT", "/v1/projects/"+id, bytes.NewBuffer(body)) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) t.Log("Given the need to validate updating a project that does not exist.") { t.Logf("\tTest 0:\tWhen using the new project %s.", id) { if w.Code != http.StatusNotFound { t.Fatalf("\t%s\tShould receive a status code of 404 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 404 for the response.", tests.Success) recv := w.Body.String() resp := "Entity not found" if !strings.Contains(recv, resp) { t.Log("Got :", recv) t.Log("Want:", resp) t.Fatalf("\t%s\tShould get the expected result.", tests.Failed) } t.Logf("\t%s\tShould get the expected result.", tests.Success) } } } // crudProject performs a complete test of CRUD against the api. func crudProject(t *testing.T) { p := postProject201(t) defer deleteProject204(t, p.ID.Hex()) getProject200(t, p.ID.Hex()) putProject204(t, p.ID.Hex()) } // postProject201 validates a project can be created with the endpoint. func postProject201(t *testing.T) project.Project { np := project.NewProject{ Name: "Comic Books", Cost: 25, Quantity: 60, } body, err := json.Marshal(&np) if err != nil { t.Fatal(err) } r := httptest.NewRequest("POST", "/v1/projects", bytes.NewBuffer(body)) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) // p is the value we will return. var p project.Project t.Log("Given the need to create a new project with the projects endpoint.") { t.Log("\tTest 0:\tWhen using the declared project value.") { if w.Code != http.StatusCreated { t.Fatalf("\t%s\tShould receive a status code of 201 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 201 for the response.", tests.Success) if err := json.NewDecoder(w.Body).Decode(&p); err != nil { t.Fatalf("\t%s\tShould be able to unmarshal the response : %v", tests.Failed, err) } // Define what we wanted to receive. We will just trust the generated // fields like ID and Dates so we copy p. want := p want.Name = "Comic Books" want.Cost = 25 want.Quantity = 60 if diff := cmp.Diff(want, p); diff != "" { t.Fatalf("\t%s\tShould get the expected result. Diff:\n%s", tests.Failed, diff) } t.Logf("\t%s\tShould get the expected result.", tests.Success) } } return p } // deleteProject200 validates deleting a project that does exist. func deleteProject204(t *testing.T, id string) { r := httptest.NewRequest("DELETE", "/v1/projects/"+id, nil) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) t.Log("Given the need to validate deleting a project that does exist.") { t.Logf("\tTest 0:\tWhen using the new project %s.", id) { if w.Code != http.StatusNoContent { t.Fatalf("\t%s\tShould receive a status code of 204 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 204 for the response.", tests.Success) } } } // getProject200 validates a project request for an existing id. func getProject200(t *testing.T, id string) { r := httptest.NewRequest("GET", "/v1/projects/"+id, nil) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) t.Log("Given the need to validate getting a project that exists.") { t.Logf("\tTest 0:\tWhen using the new project %s.", id) { if w.Code != http.StatusOK { t.Fatalf("\t%s\tShould receive a status code of 200 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 200 for the response.", tests.Success) var p project.Project if err := json.NewDecoder(w.Body).Decode(&p); err != nil { t.Fatalf("\t%s\tShould be able to unmarshal the response : %v", tests.Failed, err) } // Define what we wanted to receive. We will just trust the generated // fields like Dates so we copy p. want := p want.ID = bson.ObjectIdHex(id) want.Name = "Comic Books" want.Cost = 25 want.Quantity = 60 if diff := cmp.Diff(want, p); diff != "" { t.Fatalf("\t%s\tShould get the expected result. Diff:\n%s", tests.Failed, diff) } t.Logf("\t%s\tShould get the expected result.", tests.Success) } } } // putProject204 validates updating a project that does exist. func putProject204(t *testing.T, id string) { body := `{"name": "Graphic Novels", "cost": 100}` r := httptest.NewRequest("PUT", "/v1/projects/"+id, strings.NewReader(body)) w := httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) t.Log("Given the need to update a project with the projects endpoint.") { t.Log("\tTest 0:\tWhen using the modified project value.") { if w.Code != http.StatusNoContent { t.Fatalf("\t%s\tShould receive a status code of 204 for the response : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 204 for the response.", tests.Success) r = httptest.NewRequest("GET", "/v1/projects/"+id, nil) w = httptest.NewRecorder() r.Header.Set("Authorization", userAuthorization) a.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("\t%s\tShould receive a status code of 200 for the retrieve : %v", tests.Failed, w.Code) } t.Logf("\t%s\tShould receive a status code of 200 for the retrieve.", tests.Success) var ru project.Project if err := json.NewDecoder(w.Body).Decode(&ru); err != nil { t.Fatalf("\t%s\tShould be able to unmarshal the response : %v", tests.Failed, err) } if ru.Name != "Graphic Novels" { t.Fatalf("\t%s\tShould see an updated Name : got %q want %q", tests.Failed, ru.Name, "Graphic Novels") } t.Logf("\t%s\tShould see an updated Name.", tests.Success) } } }