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  }