github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/jsonapi/jsonapi_test.go (about) 1 package jsonapi 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/http/httptest" 8 "testing" 9 10 "github.com/cozy/cozy-stack/pkg/config/config" 11 "github.com/cozy/cozy-stack/pkg/couchdb" 12 "github.com/labstack/echo/v4" 13 "github.com/stretchr/testify/assert" 14 ) 15 16 func TestJsonapi(t *testing.T) { 17 config.UseTestFile(t) 18 19 router := echo.New() 20 router.GET("/foos/courge", func(c echo.Context) error { 21 courge := &Foo{FID: "courge", FRev: "1-abc", Bar: "baz"} 22 return Data(c, 200, courge, nil) 23 }) 24 25 router.GET("/paginated", func(c echo.Context) error { 26 cursor, err := ExtractPaginationCursor(c, 13, 1000) 27 if err != nil { 28 return err 29 } 30 31 if c2, ok := cursor.(*couchdb.SkipCursor); ok { 32 return c.JSON(200, fmt.Sprintf("key %d %d", c2.Limit, c2.Skip)) 33 } 34 35 if c3, ok := cursor.(*couchdb.StartKeyCursor); ok { 36 return c.JSON(200, fmt.Sprintf("key %d %s %s", c3.Limit, c3.NextKey, c3.NextDocID)) 37 } 38 39 return fmt.Errorf("Wrong cursor type") 40 }) 41 42 ts := httptest.NewServer(router) 43 t.Cleanup(ts.Close) 44 45 t.Run("ObjectMarshalling", func(t *testing.T) { 46 foo := &Foo{FID: "courge", FRev: "1-abc", Bar: "baz"} 47 raw, err := MarshalObject(foo) 48 assert.NoError(t, err) 49 var data map[string]interface{} 50 err = json.Unmarshal(raw, &data) 51 52 assert.NoError(t, err) 53 assert.Equal(t, data["type"], "io.cozy.foos") 54 assert.Equal(t, data["id"], "courge") 55 assert.Contains(t, data, "meta") 56 meta, _ := data["meta"].(map[string]interface{}) 57 assert.Equal(t, meta["rev"], "1-abc") 58 assert.Contains(t, data, "attributes") 59 attrs, _ := data["attributes"].(map[string]interface{}) 60 assert.Equal(t, attrs["bar"], "baz") 61 assert.Contains(t, data, "links") 62 links, _ := data["links"].(map[string]interface{}) 63 assert.Equal(t, links["self"], "/foos/courge") 64 65 assert.Contains(t, data, "relationships") 66 rels, _ := data["relationships"].(map[string]interface{}) 67 assert.Contains(t, rels, "single") 68 single, _ := rels["single"].(map[string]interface{}) 69 assert.Contains(t, single, "links") 70 links1, _ := single["links"].(map[string]interface{}) 71 assert.Equal(t, links1["related"], "/foos/courge/single") 72 assert.Contains(t, single, "data") 73 data1, _ := single["data"].(map[string]interface{}) 74 assert.Equal(t, data1["type"], "io.cozy.foos") 75 assert.Equal(t, data1["id"], "qux") 76 77 assert.Contains(t, rels, "multiple") 78 multiple, _ := rels["multiple"].(map[string]interface{}) 79 assert.Contains(t, multiple, "links") 80 links2, _ := multiple["links"].(map[string]interface{}) 81 assert.Equal(t, links2["related"], "/foos/courge/multiple") 82 assert.Contains(t, multiple, "data") 83 data2, _ := multiple["data"].([]interface{}) 84 assert.Len(t, data2, 1) 85 qux, _ := data2[0].(map[string]interface{}) 86 assert.Equal(t, qux["type"], "io.cozy.foos") 87 assert.Equal(t, qux["id"], "qux") 88 }) 89 90 t.Run("Data", func(t *testing.T) { 91 res, err := http.Get(ts.URL + "/foos/courge") 92 assert.NoError(t, err) 93 assert.Equal(t, "200 OK", res.Status, "should get a 200") 94 assert.Equal(t, "application/vnd.api+json", res.Header.Get("Content-Type")) 95 defer res.Body.Close() 96 var body map[string]interface{} 97 assert.NoError(t, json.NewDecoder(res.Body).Decode(&body)) 98 99 assert.Contains(t, body, "data") 100 data := body["data"].(map[string]interface{}) 101 assert.Equal(t, data["type"], "io.cozy.foos") 102 assert.Equal(t, data["id"], "courge") 103 assert.Contains(t, data, "attributes") 104 assert.Contains(t, data, "relationships") 105 assert.Contains(t, data, "links") 106 107 assert.Contains(t, body, "included") 108 included := body["included"].([]interface{}) 109 assert.Len(t, included, 1) 110 qux, _ := included[0].(map[string]interface{}) 111 assert.Equal(t, qux["type"], "io.cozy.foos") 112 assert.Equal(t, qux["id"], "qux") 113 }) 114 115 t.Run("Pagination", func(t *testing.T) { 116 res, err := http.Get(ts.URL + "/paginated") 117 assert.NoError(t, err) 118 defer res.Body.Close() 119 var c string 120 assert.NoError(t, json.NewDecoder(res.Body).Decode(&c)) 121 assert.Equal(t, "key 13 %!s(<nil>) ", c) 122 }) 123 124 t.Run("PaginationCustomLimit", func(t *testing.T) { 125 res, err := http.Get(ts.URL + "/paginated?page[limit]=7") 126 assert.NoError(t, err) 127 defer res.Body.Close() 128 var c string 129 assert.NoError(t, json.NewDecoder(res.Body).Decode(&c)) 130 assert.Equal(t, "key 7 %!s(<nil>) ", c) 131 }) 132 133 t.Run("PaginationBadNumber", func(t *testing.T) { 134 res, err := http.Get(ts.URL + "/paginated?page[limit]=notnumber") 135 assert.NoError(t, err) 136 defer res.Body.Close() 137 assert.NotEqual(t, http.StatusOK, res.StatusCode, "should give an error") 138 }) 139 140 t.Run("PaginationWithCursor", func(t *testing.T) { 141 res, err := http.Get(ts.URL + "/paginated?page[cursor]=%5B%5B%22a%22%2C%20%22b%22%5D%2C%20%22c%22%5D") 142 assert.NoError(t, err) 143 defer res.Body.Close() 144 var c string 145 assert.NoError(t, json.NewDecoder(res.Body).Decode(&c)) 146 assert.Equal(t, "key 13 [a b] c", c) 147 }) 148 } 149 150 type Foo struct { 151 FID string `json:"-"` 152 FRev string `json:"-"` 153 Bar string `json:"bar"` 154 } 155 156 func (f *Foo) ID() string { 157 return f.FID 158 } 159 160 func (f *Foo) Rev() string { 161 return f.FRev 162 } 163 164 func (f *Foo) DocType() string { 165 return "io.cozy.foos" 166 } 167 168 func (f *Foo) Clone() couchdb.Doc { 169 return f 170 } 171 172 func (f *Foo) SetID(id string) { 173 f.FID = id 174 } 175 176 func (f *Foo) SetRev(rev string) { 177 f.FRev = rev 178 } 179 180 func (f *Foo) Links() *LinksList { 181 return &LinksList{Self: "/foos/" + f.FID} 182 } 183 184 func (f *Foo) Relationships() RelationshipMap { 185 qux := map[string]string{ 186 "type": "io.cozy.foos", 187 "id": "qux", 188 } 189 single := Relationship{ 190 Links: &LinksList{ 191 Related: "/foos/" + f.FID + "/single", 192 }, 193 Data: qux, 194 } 195 multiple := Relationship{ 196 Links: &LinksList{ 197 Related: "/foos/" + f.FID + "/multiple", 198 }, 199 Data: []map[string]string{qux}, 200 } 201 return RelationshipMap{ 202 "single": single, 203 "multiple": multiple, 204 } 205 } 206 207 func (f *Foo) Included() []Object { 208 qux := &Foo{FID: "qux", FRev: "42-xyz", Bar: "quux"} 209 return []Object{qux} 210 }