package server import ( "encoding/json" "fmt" "io" "io/ioutil" "local/dndex/storage/entity" "net/http" "net/http/httptest" "path" "strings" "testing" ) func TestEntities(t *testing.T) { rest, authit, clean := testREST(t) defer clean() t.Run("create+get1+delete+404", func(t *testing.T) { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"name":"myname"}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "myname" }) w = testEntitiesMethod(t, authit, rest, http.MethodGet, "/"+id, ``) if w.Code != http.StatusOK { t.Fatal(w.Code) } id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "myname" }) if id2 != id { t.Fatal(id, id2) } w = testEntitiesMethod(t, authit, rest, http.MethodDelete, "/"+id, ``) if w.Code != http.StatusOK { t.Fatal(w.Code) } w = testEntitiesMethod(t, authit, rest, http.MethodGet, "/"+id, ``) if w.Code != http.StatusNotFound { t.Fatal(w.Code) } }) t.Run("get1 404", func(t *testing.T) { w := testEntitiesMethod(t, authit, rest, http.MethodGet, "/abc123", ``) if w.Code != http.StatusNotFound { t.Fatal(w.Code) } }) t.Run("create+get0", func(t *testing.T) { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"name":"myname", "attachments": {"abc": {}}, "connections": {"def": {"relationship": "ghi"}}}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "myname" && len(one.Attachments) == 1 && len(one.Connections) == 1 && one.Connections["def"].Relationship == "ghi" }) w = testEntitiesMethod(t, authit, rest, http.MethodGet, "/", ``) if w.Code != http.StatusOK { t.Fatal(w.Code) } testEntitiesGetNResponse(t, w.Body, func(one shortEntity) bool { return one.Name == "myname" && one.ID == id }) }) t.Run("create+replace+get1", func(t *testing.T) { check := func(one entity.One) bool { return one.Name == "myname" } w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"name":"myname"}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id := testEntitiesGetOneResponse(t, w.Body, check) w = testEntitiesMethod(t, authit, rest, http.MethodPut, "/"+id, `{"name":"newname"}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } w = testEntitiesMethod(t, authit, rest, http.MethodGet, "/"+id, ``) if w.Code != http.StatusOK { t.Fatal(w.Code) } id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "newname" }) if id2 != id { t.Fatal(id, id2) } }) t.Run("create+get.title", func(t *testing.T) { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"name":"myname", "title": "mytitle"}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "myname" && one.Title == "mytitle" }) w = testEntitiesMethod(t, authit, rest, http.MethodGet, "/"+id+"/title", ``) if w.Code != http.StatusOK { t.Fatal(w.Code) } testEntitiesGetOneSubResponse(t, w.Body, func(v interface{}) bool { return fmt.Sprint(v) == "mytitle" }) }) t.Run("create+update/replace", func(t *testing.T) { for _, method := range []string{http.MethodPut, http.MethodPatch} { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"name":"myname", "title": "mytitle"}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "myname" && one.Title == "mytitle" }) w = testEntitiesMethod(t, authit, rest, method, "/"+id, `{"name": "newname"}`) if w.Code != http.StatusOK { t.Fatalf("%v: %s", w.Code, w.Body.Bytes()) } id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "newname" && one.Title == "" }) if id != id2 { t.Fatal(id, id2) } } }) t.Run("create+update/replace.name", func(t *testing.T) { for _, method := range []string{http.MethodPut, http.MethodPatch} { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"name":"myname", "title": "mytitle"}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "myname" && one.Title == "mytitle" }) w = testEntitiesMethod(t, authit, rest, method, "/"+id+"/name", `"newname"`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "newname" && one.Title == "mytitle" }) if id != id2 { t.Fatal(id, id2) } } }) t.Run("create+update/replace.connection.abc.relationship", func(t *testing.T) { for _, method := range []string{http.MethodPut, http.MethodPatch} { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"connections": {"abc": {"relationship": "def"}}}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Connections["abc"].Relationship == "def" }) w = testEntitiesMethod(t, authit, rest, method, "/"+id+"/connections/abc/relationship", `"new"`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Connections["abc"].Relationship == "new" }) if id != id2 { t.Fatal(id, id2) } } }) t.Run("create+update/replace.connection.abc", func(t *testing.T) { for _, method := range []string{http.MethodPut, http.MethodPatch} { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"connections": {"abc": {"relationship": "def"}}}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Connections["abc"].Relationship == "def" }) w = testEntitiesMethod(t, authit, rest, method, "/"+id+"/connections/abc", `{"relationship": "new"}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Connections["abc"].Relationship == "new" }) if id != id2 { t.Fatal(id, id2) } } }) t.Run("create+delete.name", func(t *testing.T) { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"name":"myname", "title": "mytitle"}`) if w.Code != http.StatusOK { t.Fatal(w.Code) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "myname" && one.Title == "mytitle" }) w = testEntitiesMethod(t, authit, rest, http.MethodDelete, "/"+id+"/name", ``) if w.Code != http.StatusOK { t.Fatalf("%v: %s", w.Code, w.Body.Bytes()) } id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "" && one.Title == "mytitle" }) if id != id2 { t.Fatal(id, id2) } }) t.Run("create w connection.abc+delete.connections.abc", func(t *testing.T) { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"name":"myname", "title": "mytitle", "connections": {"abc": {"relationship": "good"}}}`) if w.Code != http.StatusOK { t.Fatalf("%v: %s", w.Code, w.Body.Bytes()) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { abc, ok := one.Connections["abc"] return one.Name == "myname" && one.Title == "mytitle" && ok && abc.Relationship == "good" }) w = testEntitiesMethod(t, authit, rest, http.MethodDelete, "/"+id+"/connections/abc", ``) if w.Code != http.StatusOK { t.Fatalf("%v: %s", w.Code, w.Body.Bytes()) } id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return one.Name == "myname" && one.Title == "mytitle" && len(one.Connections) == 0 }) if id != id2 { t.Fatal(id, id2) } }) t.Run("create w connection.abc.relationship+delete.connections.abc.relationship", func(t *testing.T) { w := testEntitiesMethod(t, authit, rest, http.MethodPost, "/", `{"connections": {"abc": {"relationship": "good"}}}`) if w.Code != http.StatusOK { t.Fatalf("%v: %s", w.Code, w.Body.Bytes()) } id := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { abc, ok := one.Connections["abc"] return ok && abc.Relationship == "good" }) w = testEntitiesMethod(t, authit, rest, http.MethodDelete, "/"+id+"/connections/abc/relationship", ``) if w.Code != http.StatusOK { t.Fatalf("%v: %s", w.Code, w.Body.Bytes()) } id2 := testEntitiesGetOneResponse(t, w.Body, func(one entity.One) bool { return len(one.Connections) == 1 && one.Connections["abc"].Relationship == "" }) if id != id2 { t.Fatal(id, id2) } }) } func testEntitiesMethod(t *testing.T, authit func(*http.Request), rest *REST, method, p, body string) *httptest.ResponseRecorder { r := httptest.NewRequest(method, p, strings.NewReader(body)) if !strings.HasPrefix(r.URL.Path, "/entities") { r.URL.Path = path.Join("/entities", r.URL.Path) } w := httptest.NewRecorder() authit(r) rest.scoped(rest.shift(rest.entities))(w, r) return w } func testEntitiesGetNResponse(t *testing.T, body io.Reader, check func(shortEntity) bool) { var resp map[string][]shortEntity if err := json.NewDecoder(body).Decode(&resp); err != nil { t.Fatal(err) } if len(resp) != 1 { t.Fatal("excess found in db") } for k := range resp { contents := resp[k] if len(contents) < 1 { t.Fatal(len(contents)) } for i := range contents { if check(contents[i]) { return } } t.Fatal(contents) } } func testEntitiesGetOneResponse(t *testing.T, body io.Reader, check func(entity.One) bool) string { b, err := ioutil.ReadAll(body) if err != nil { t.Fatal(err) } var resp map[string]entity.One if err := json.Unmarshal(b, &resp); err != nil { t.Fatalf("%v: %s", err, b) } if len(resp) != 1 { t.Fatal(len(resp)) } for k := range resp { one := resp[k] if one.ID != k { t.Fatal(k, one.ID) } if one.Modified == 0 { t.Fatal(one.Modified) } if !check(one) { t.Fatal(one) } return one.ID } panic("somehow no keys found") } func testEntitiesGetOneSubResponse(t *testing.T, body io.Reader, check func(interface{}) bool) { b, err := ioutil.ReadAll(body) if err != nil { t.Fatal(err) } var resp map[string]interface{} if err := json.Unmarshal(b, &resp); err != nil { t.Fatalf("%v: %s", err, b) } if len(resp) != 1 { t.Fatal(len(resp)) } for k := range resp { one := resp[k] if !check(one) { t.Fatal(one) } return } panic("somehow no keys found") }