package view import ( "bytes" "context" "encoding/json" "encoding/xml" "fmt" "io/ioutil" "local/dndex/config" "local/dndex/storage" "local/dndex/storage/entity" "net/http" "net/http/httptest" "net/url" "os" "path" "regexp" "sort" "strings" "testing" "time" "github.com/buger/jsonparser" "github.com/google/uuid" ) func TestWho(t *testing.T) { os.Args = os.Args[:1] if _, ok := os.LookupEnv("DBURI"); !ok { f, err := ioutil.TempFile(os.TempDir(), "pattern*") if err != nil { t.Fatal(err) } f.Close() defer os.Remove(f.Name()) os.Setenv("DBURI", f.Name()) } t.Logf("config: %+v", config.New()) g := storage.NewRateLimitedGraph() ones := fillDB(t, g) want := ones[len(ones)-1] reset := func() { if err := g.Delete(context.TODO(), "col", map[string]string{}); err != nil { t.Fatal(err) } ones = fillDB(t, g) want = ones[len(ones)-1] } handler := jsonHandler(g) t.Log(handler, want) t.Run("get no namespace is 404", func(t *testing.T) { reset() iwant := want r := httptest.NewRequest(http.MethodGet, "/who?id="+iwant.Name, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusNotFound { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } }) t.Run("get fake", func(t *testing.T) { reset() iwant := want r := httptest.NewRequest(http.MethodGet, "/who?namespace=col&id=FAKER"+iwant.Name, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusNotFound { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } }) t.Run("get real", func(t *testing.T) { reset() iwant := want r := httptest.NewRequest(http.MethodGet, "/who?namespace=col&id="+iwant.Name, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if fmt.Sprint(o) == fmt.Sprint(iwant) { t.Fatal(o, iwant) } if len(o.Connections) != len(iwant.Connections) { t.Fatal(len(o.Connections), len(iwant.Connections)) } if len(o.Attachments) != len(iwant.Attachments) { t.Fatal(len(o.Attachments), len(iwant.Attachments)) } for k := range o.Attachments { if _, ok := iwant.Attachments[k]; !ok { t.Fatal(k, ok) } if o.Attachments[k] == iwant.Attachments[k] { t.Fatal(k, o.Attachments[k], iwant.Attachments[k]) } if !strings.HasSuffix(o.Attachments[k], path.Join(config.New().FilePrefix, "col", iwant.Attachments[k])) { t.Fatal(k, o.Attachments[k], iwant.Attachments[k]) } if !strings.HasPrefix(o.Attachments[k], "http://") { t.Fatal(k, o.Attachments[k], iwant.Attachments[k]) } } iwant.Attachments = o.Attachments iwant.Connections = o.Connections iwant.Modified = 0 o.Modified = 0 if fmt.Sprint(o) != fmt.Sprint(iwant) { t.Fatalf("after resolving connections and modified, iwant and got differ: \nwant %+v\n got %+v", iwant, o) } b, _ := json.MarshalIndent(o, "", " ") t.Logf("POST GET:\n%s", b) }) t.Run("get real md", func(t *testing.T) { reset() iwant := want r := httptest.NewRequest(http.MethodGet, "/who?namespace=col&md&id="+iwant.Name, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if fmt.Sprint(o) == fmt.Sprint(iwant) { t.Fatal(o, iwant) } if len(o.Connections) != len(iwant.Connections) { t.Fatal(len(o.Connections), len(iwant.Connections)) } iwant.Connections = o.Connections iwant.Attachments = o.Attachments iwant.Modified = 0 o.Modified = 0 if fmt.Sprint(o) != fmt.Sprint(iwant) { t.Fatalf("after resolving connections and modified, iwant and got differ: \nwant %+v\n got %+v", iwant, o) } var paragraphContent string if v, err := jsonparser.GetString(w.Body.Bytes(), "md"); err != nil { t.Fatal(err) } else if err := xml.Unmarshal([]byte(v), ¶graphContent); err != nil { t.Fatal(err) } else if paragraphContent != iwant.Text { t.Fatal(iwant.Text, paragraphContent, v) } b, _ := json.MarshalIndent(o, "", " ") t.Logf("POST GET:\n%s", b) }) t.Run("get real light", func(t *testing.T) { reset() iwant := want r := httptest.NewRequest(http.MethodGet, "/who?namespace=col&light&id="+iwant.Name, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if fmt.Sprint(o) == fmt.Sprint(iwant) { t.Fatal(o, iwant) } if len(o.Connections) != len(iwant.Connections) { t.Fatal(len(o.Connections), len(iwant.Connections)) } iwant.Connections = o.Connections iwant.Attachments = o.Attachments iwant.Modified = 0 o.Modified = 0 if fmt.Sprint(o) != fmt.Sprint(iwant) { t.Fatalf("after resolving connections and modified, iwant and got differ: \nwant %+v\n got %+v", iwant, o) } for _, connection := range iwant.Connections { if len(connection.Connections) != 0 { t.Fatal(connection) } } b, _ := json.MarshalIndent(o, "", " ") t.Logf("POST GET:\n%s", b) }) t.Run("get real but case is wrong", func(t *testing.T) { reset() iwant := want iwantName := strings.ToUpper(iwant.Name) r := httptest.NewRequest(http.MethodGet, "/who?namespace=col&id="+iwantName, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if fmt.Sprint(o) == fmt.Sprint(iwant) { t.Fatal(o, iwant) } if len(o.Connections) != len(iwant.Connections) { t.Fatal(len(o.Connections), len(iwant.Connections)) } iwant.Connections = o.Connections iwant.Attachments = o.Attachments iwant.Modified = 0 o.Modified = 0 if fmt.Sprint(o) != fmt.Sprint(iwant) { t.Fatalf("after resolving connections and modified, iwant and got differ: \nwant %+v\n got %+v", iwant, o) } b, _ := json.MarshalIndent(o, "", " ") t.Logf("POST GET:\n%s", b) }) t.Run("get sorted type asc/desc", func(t *testing.T) { for _, order := range []string{"1", "-1"} { reset() want := ones[len(ones)-1] r := httptest.NewRequest(http.MethodGet, "/who?namespace=col&light&sort=type&order="+order+"&id="+want.Name, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if len(o.Connections) < 5 { t.Fatal(len(o.Connections)) } if len(o.Connections) != len(want.Connections) { t.Fatal(len(want.Connections), len(o.Connections)) } titles := []string{} for _, v := range want.Connections { if len(v.Type) == 0 { t.Fatal(v.Type) } titles = append(titles, v.Type) } sort.Strings(titles) if order == "-1" { for i := 0; i < len(titles)/2; i++ { tmp := titles[len(titles)-1-i] titles[len(titles)-1-i] = titles[i] titles[i] = tmp } } pattern := strings.Join(titles, ".*") pattern = strings.Replace(pattern, "-", ".", -1) if !regexp.MustCompile(pattern).Match(bytes.Replace(w.Body.Bytes(), []byte("\n"), []byte(" "), -1)) { t.Fatal(order, pattern, string(w.Body.Bytes())) } } }) t.Run("put fake", func(t *testing.T) { reset() iwant := want r := httptest.NewRequest(http.MethodPut, "/who?namespace=col&id=FAKER"+iwant.Name, strings.NewReader(`{"title":"this should fail to find someone"}`)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusNotFound { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } }) t.Run("put real", func(t *testing.T) { reset() iwant := want r := httptest.NewRequest(http.MethodPut, "/who?namespace=col&id="+iwant.Name, strings.NewReader(`{"title":"this should work"}`)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if len(o.Connections) != len(iwant.Connections) { t.Fatalf("wrong number of connections returned: want %v, got %v", len(iwant.Connections), len(o.Connections)) } if o.Title != "this should work" { t.Fatalf("failed to PUT a new title: %+v", o) } b, _ := json.MarshalIndent(o, "", " ") t.Logf("POST PUT:\n%s", b) }) t.Run("post exists", func(t *testing.T) { reset() iwant := want iwant.Name = "" r := httptest.NewRequest(http.MethodPost, "/who?namespace=col&id="+want.Name, strings.NewReader(`{"title":"this should fail to insert"}`)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusConflict { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } }) t.Run("post real", func(t *testing.T) { reset() iwant := want iwant.Name = "" b, err := json.Marshal(iwant) if err != nil { t.Fatal(err) } r := httptest.NewRequest(http.MethodPost, "/who?namespace=col&id=NEWBIE"+want.Name, bytes.NewReader(b)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if len(o.Connections) != len(iwant.Connections) { t.Fatalf("wrong number of connections returned: want %v, got %v", len(iwant.Connections), len(o.Connections)) } if o.Name != "NEWBIE"+want.Name { t.Fatalf("failed to POST specified name: %+v", o) } b, _ = json.MarshalIndent(o, "", " ") t.Logf("POST POST:\n%s", b) }) t.Run("post real with spaces, #, and special chars in name", func(t *testing.T) { reset() want.Name = "hello world #1 e ę" want.Connections = nil b, err := json.Marshal(want) if err != nil { t.Fatal(err) } url := url.URL{ Scheme: "http", Host: "me.me.me", Path: "/who", RawQuery: (url.Values{"namespace": []string{"col"}, "id": []string{want.Name}}).Encode(), } r := httptest.NewRequest(http.MethodPost, url.String(), bytes.NewReader(b)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if o.Name != want.Name { t.Fatalf("failed to POST specified name: %+v", o) } b, _ = json.MarshalIndent(o, "", " ") t.Logf("POST POST:\n%q\n%s", url.String(), b) }) t.Run("delete real", func(t *testing.T) { reset() r := httptest.NewRequest(http.MethodDelete, "/who?namespace=col&id=NEWBIE"+want.Name, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } r = httptest.NewRequest(http.MethodGet, "/who?namespace=col&id=NEWBIE"+want.Name, nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusNotFound { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } }) t.Run("delete real with %20%23 (' #')", func(t *testing.T) { reset() want.Name = "hello world #4" want.Connections = nil b, err := json.Marshal(want) if err != nil { t.Fatal(err) } url := url.URL{ Scheme: "http", Host: "me.me.me", Path: "/who", RawQuery: "namespace=col&id=hello%20world%20%234", } t.Logf("using url %s", url.String()) r := httptest.NewRequest(http.MethodPost, url.String(), bytes.NewReader(b)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("(%d) %s", w.Code, w.Body.Bytes()) } r = httptest.NewRequest(http.MethodDelete, url.String(), nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } r = httptest.NewRequest(http.MethodGet, url.String(), nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusNotFound { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } t.Logf("can post+del %s", url.String()) }) t.Run("delete real with special chars", func(t *testing.T) { reset() want.Name = "hello world #1 e ę" want.Connections = nil b, err := json.Marshal(want) if err != nil { t.Fatal(err) } url := url.URL{ Scheme: "http", Host: "me.me.me", Path: "/who", RawQuery: (url.Values{"namespace": []string{"col"}, "id": []string{want.Name}}).Encode(), } r := httptest.NewRequest(http.MethodPost, url.String(), bytes.NewReader(b)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatal(w.Code) } r = httptest.NewRequest(http.MethodDelete, url.String(), nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } r = httptest.NewRequest(http.MethodGet, url.String(), nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusNotFound { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } t.Logf("can post+del %s", url.String()) }) t.Run("delete fake", func(t *testing.T) { reset() r := httptest.NewRequest(http.MethodDelete, "/who?namespace=col&id=FAKER"+want.Name, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } }) t.Run("delete regexp should be sanitized", func(t *testing.T) { reset() r := httptest.NewRequest(http.MethodDelete, "/who?namespace=col&id=.*", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } r = httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } var v []string if err := json.Unmarshal(w.Body.Bytes(), &v); err != nil { t.Fatalf("%v: %s", err, w.Body.Bytes()) } if len(v) < 5 { t.Fatal(len(v)) } t.Logf("%+v", v) }) t.Run("patch fake", func(t *testing.T) { reset() r := httptest.NewRequest(http.MethodPatch, "/who?namespace=col&id=FAKER"+want.Name, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code == http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } }) want = ones[4] t.Run("patch real against existing", func(t *testing.T) { reset() from := ones[4] push := ones[10].Peer() push.Relationship = "spawn" b, err := json.Marshal(push) if err != nil { t.Fatal(err) } r := httptest.NewRequest(http.MethodPatch, "/who?namespace=col&id="+from.Name, bytes.NewReader(b)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } got := entity.One{} if err := json.NewDecoder(w.Body).Decode(&got); err != nil { t.Fatal(err) } if fmt.Sprint(got) == fmt.Sprint(from) { t.Fatal(got) } got.Modified = 0 from.Modified = 0 if len(got.Connections) != len(from.Connections)+1 { t.Fatal(len(got.Connections), len(from.Connections)+1) } gotPush, ok := got.Connections[push.Name] if !ok { t.Fatal("cant find pushed connection from remote") } got.Connections = nil from.Connections = nil got.Attachments = nil from.Attachments = nil if fmt.Sprint(got) != fmt.Sprint(from) { t.Fatalf("without connections and modified, got != want: want \n %+v, got \n %+v", from, got) } pushPeer := push.Peer() gotPush = gotPush.Peer() pushPeer.Modified = 0 gotPush.Modified = 0 if fmt.Sprint(gotPush) != fmt.Sprint(pushPeer) { t.Fatal("\n", gotPush, "\n", pushPeer) } t.Logf("%s", w.Body.Bytes()) }) want = ones[2] t.Run("patch real", func(t *testing.T) { reset() iwant := want iwant.Relationship = "spawn" iwant.Name = "child of " + want.Name b, err := json.Marshal(iwant) if err != nil { t.Fatal(err) } r := httptest.NewRequest(http.MethodPatch, "/who?namespace=col&id="+want.Name, bytes.NewReader(b)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } got := entity.One{} if err := json.NewDecoder(w.Body).Decode(&got); err != nil { t.Fatal(err) } if fmt.Sprint(got) == fmt.Sprint(entity.One{}) { t.Fatal(got) } iwant = want if len(got.Connections) != len(want.Connections)+1 { t.Fatal(len(got.Connections), len(want.Connections)+1) } got.Connections = want.Connections got.Attachments = want.Attachments got.Modified = 0 want.Modified = 0 if fmt.Sprint(got) != fmt.Sprint(want) { t.Fatalf("without connections and modified, got != want: want \n %+v, got \n %+v", want, got) } t.Logf("%s", w.Body.Bytes()) }) t.Run("trace fake", func(t *testing.T) { reset() r := httptest.NewRequest(http.MethodTrace, "/who?namespace=notcol", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } }) t.Run("trace real", func(t *testing.T) { reset() r := httptest.NewRequest(http.MethodTrace, "/who?namespace=col", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } var v []string if err := json.Unmarshal(w.Body.Bytes(), &v); err != nil { t.Fatalf("%v: %s", err, w.Body.Bytes()) } if len(v) < 5 { t.Fatal(len(v)) } t.Logf("%+v", v) }) t.Run("get without id == trace real", func(t *testing.T) { reset() r := httptest.NewRequest(http.MethodGet, "/who?namespace=col", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } var v []string if err := json.Unmarshal(w.Body.Bytes(), &v); err != nil { t.Fatalf("%v: %s", err, w.Body.Bytes()) } if len(v) < 5 { t.Fatal(len(v)) } t.Logf("%+v", v) }) t.Run("trace real sorted asc/desc name", func(t *testing.T) { reset() for _, order := range []string{"1", "-1"} { r := httptest.NewRequest(http.MethodTrace, "/who?namespace=col&sort=name&order="+order, nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } var v []string if err := json.Unmarshal(w.Body.Bytes(), &v); err != nil { t.Fatalf("%v: %s", err, w.Body.Bytes()) } if len(v) < 5 { t.Fatal(len(v)) } for i := range v { if i == 0 { continue } if (v[i] < v[i-1] && order == "1") || (v[i] > v[i-1] && order == "-1") { t.Fatalf("not sorted: %s: %+v", order, v) } } } }) t.Run("delete connection 1 of 0 noop but ok", func(t *testing.T) { reset() want := ones[0] if len(want.Connections) > 0 { t.Fatal(want) } r := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/who?namespace=col&id=%s&connection=%s", want.Name, "fake"), nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } r = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/who?namespace=col&id=%s", want.Name), nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if len(o.Connections) > 0 { t.Fatal(o.Connections) } }) t.Run("delete connection 1 of 1 ok", func(t *testing.T) { reset() deleted := want.Peers()[0] r := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/who?namespace=col&id=%s&connection=%s", want.Name, deleted), nil) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } r = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/who?namespace=col&id=%s", want.Name), nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if _, ok := o.Connections[deleted]; ok { t.Fatal(deleted, o.Connections) } }) t.Run("delete connection 1 of 4 ok", func(t *testing.T) { reset() want := ones[0] put := entity.One{ Name: want.Name, Connections: map[string]entity.One{ ones[1].Name: ones[1].Peer(), ones[2].Name: ones[2].Peer(), ones[3].Name: ones[3].Peer(), ones[4].Name: ones[4].Peer(), }, } b, err := json.Marshal(put) if err != nil { t.Fatal(err) } r := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/who?namespace=col&id=%s", want.Name), bytes.NewReader(b)) w := httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } want.Connections = put.Connections r = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/who?namespace=col&id=%s&light", want.Name), nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o := entity.One{} if err := json.NewDecoder(w.Body).Decode(&o); err != nil { t.Fatal(err) } o.Modified = 0 want.Modified = 0 if len(o.Connections) != len(put.Connections) { t.Fatal(o.Connections) } for k := range o.Connections { a := want.Connections[k] a.Modified = 0 want.Connections[k] = a b := o.Connections[k] b.Modified = 0 o.Connections[k] = b } o.Attachments = want.Attachments if fmt.Sprint(o) != fmt.Sprint(want) { t.Fatalf("GET put != expected: want:\n%+v, got \n%+v", want, o) } forget := want.Peers()[0] r = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/who?namespace=col&id=%s&connection=%s", want.Name, forget), nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } r = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/who?namespace=col&id=%s", want.Name), nil) w = httptest.NewRecorder() handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Fatalf("%d: %s", w.Code, w.Body.Bytes()) } o = entity.One{} if err := json.Unmarshal(w.Body.Bytes(), &o); err != nil { t.Fatal(err) } if len(o.Connections) != len(put.Connections)-1 { t.Fatalf("should've deleted %q but got %+v", forget, o.Connections) } }) } func fillDB(t *testing.T, g storage.RateLimitedGraph) []entity.One { ones := make([]entity.One, 13) for i := range ones { ones[i] = randomOne() for j := 0; j < i; j++ { ones[i].Connections[ones[j].Name] = entity.One{ Name: ones[j].Name, Type: ones[j].Type, Relationship: ":D", } } } for i := range ones { if err := g.Insert(context.TODO(), "col", ones[i]); err != nil { t.Fatal(err) } if results, err := g.List(context.TODO(), "col", ones[i].Name); err != nil { t.Fatal(err) } else if len(results) != 1 { t.Fatal(len(results)) } else if len(results[0].Connections) != len(ones[i].Connections) { t.Fatal(len(results[0].Connections), len(ones[i].Connections)) } else if len(results[0].Connections) > 0 { for k, v := range results[0].Connections { if k == "" || v.Name == "" { t.Fatalf("name is gone: %q:%+v", k, v) } } } } return ones } func randomOne() entity.One { return entity.One{ Name: "name-" + uuid.New().String()[:5], Type: "type-" + uuid.New().String()[:5], Title: "titl-" + uuid.New().String()[:5], Text: "text-" + uuid.New().String()[:5], Modified: time.Now().UnixNano(), Connections: map[string]entity.One{}, Attachments: map[string]string{ uuid.New().String()[:5]: uuid.New().String()[:5], }, } } func TestSortOnes(t *testing.T) { oneA := entity.One{Name: "A", Title: "c", Modified: 2} oneB := entity.One{Name: "B", Title: "b", Modified: 1} oneC := entity.One{Name: "C", Title: "a", Modified: 3} cases := map[string]struct { ones []entity.One sort string order string test func(entity.One, entity.One) bool }{ "nothing to sort": {}, "all the same": { ones: []entity.One{oneA, oneA, oneA}, test: func(a, b entity.One) bool { return fmt.Sprint(a) == fmt.Sprint(b) }, }, "default: modified desc, but already ordered": { ones: []entity.One{oneC, oneB, oneA}, test: func(a, b entity.One) bool { return a.Modified >= b.Modified }, }, "default: modified desc": { ones: []entity.One{oneA, oneB, oneC}, test: func(a, b entity.One) bool { return a.Modified >= b.Modified }, }, "default: modified desc, custom entries": { ones: []entity.One{ entity.One{Modified: 2}, entity.One{Modified: 5}, entity.One{Modified: 7}, entity.One{Modified: 3}, entity.One{Modified: 4}, entity.One{Modified: 1}, entity.One{Modified: 6}, }, test: func(a, b entity.One) bool { return a.Modified >= b.Modified }, }, "default=modified set=desc": { ones: []entity.One{oneA, oneB, oneC}, order: "-1", test: func(a, b entity.One) bool { return a.Modified >= b.Modified }, }, "set=modified default=desc": { ones: []entity.One{oneA, oneB, oneC}, sort: entity.Modified, test: func(a, b entity.One) bool { return a.Modified >= b.Modified }, }, "set=modified set=asc": { ones: []entity.One{oneA, oneB, oneC}, sort: entity.Modified, order: "1", test: func(a, b entity.One) bool { return a.Modified <= b.Modified }, }, "set=title set=desc": { ones: []entity.One{oneA, oneB, oneC}, sort: entity.Title, order: "-1", test: func(a, b entity.One) bool { return a.Title >= b.Title }, }, "set=title set=asc": { ones: []entity.One{oneA, oneB, oneC}, sort: entity.Title, order: "1", test: func(a, b entity.One) bool { return a.Title <= b.Title }, }, "set=name set=desc": { ones: []entity.One{oneA, oneB, oneC}, sort: entity.Name, order: "-1", test: func(a, b entity.One) bool { return a.Name >= b.Name }, }, "set=name set=asc": { ones: []entity.One{oneA, oneB, oneC}, sort: entity.Name, order: "1", test: func(a, b entity.One) bool { return a.Name <= b.Name }, }, } for name, d := range cases { c := d t.Run(name, func(t *testing.T) { q := url.Values{} q.Set("sort", c.sort) q.Set("order", c.order) url := url.URL{ Path: "/", RawQuery: q.Encode(), } r := httptest.NewRequest("GET", url.String(), nil) ones := sortOnes(c.ones, r) for i := range ones { if i == 0 { continue } if ok := c.test(ones[i-1], ones[i]); !ok { t.Fatal(ok, ones[i-1], ones[i]) } } }) } }