github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/apiv3/route/paginator_test.go (about)

     1  package route
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"net/url"
     8  	"testing"
     9  
    10  	"github.com/evergreen-ci/evergreen/apiv3"
    11  	"github.com/evergreen-ci/evergreen/apiv3/model"
    12  	"github.com/evergreen-ci/evergreen/apiv3/servicecontext"
    13  	"github.com/gorilla/context"
    14  	. "github.com/smartystreets/goconvey/convey"
    15  )
    16  
    17  func TestMakePaginationHeader(t *testing.T) {
    18  	testURL := "http://evergreenurl.com"
    19  	testRoute := "test/route"
    20  	testPrefix := "rest"
    21  
    22  	testPrevKey := "prevkey"
    23  	testNextKey := "nextkey"
    24  	testNextLimit := 100
    25  	testPrevLimit := 105
    26  
    27  	testLimitQueryParam := "limit"
    28  	testKeyQueryParam := "key"
    29  
    30  	Convey("When there is an http response writer", t, func() {
    31  		w := httptest.NewRecorder()
    32  		Convey("creating a PaginationMetadata with both a NextKey and PrevKey"+
    33  			" should create 2 links in the header", func() {
    34  			testPages := &PageResult{
    35  				Next: &Page{
    36  					Key:      testNextKey,
    37  					Relation: "next",
    38  					Limit:    testNextLimit,
    39  				},
    40  				Prev: &Page{
    41  					Key:      testPrevKey,
    42  					Relation: "prev",
    43  					Limit:    testPrevLimit,
    44  				},
    45  			}
    46  
    47  			pm := &PaginationMetadata{
    48  				Pages:           testPages,
    49  				KeyQueryParam:   testKeyQueryParam,
    50  				LimitQueryParam: testLimitQueryParam,
    51  			}
    52  
    53  			err := pm.MakeHeader(w, testPrefix, testURL, testRoute)
    54  			So(err, ShouldBeNil)
    55  			linksHeader, ok := w.Header()["Link"]
    56  			So(ok, ShouldBeTrue)
    57  			So(len(linksHeader), ShouldEqual, 1)
    58  			pmRes, err := ParsePaginationHeader(linksHeader[0], testKeyQueryParam, testLimitQueryParam)
    59  			So(err, ShouldBeNil)
    60  
    61  			So(pmRes.Pages, ShouldNotBeNil)
    62  			So(pmRes.Pages.Next, ShouldResemble, testPages.Next)
    63  			So(pmRes.Pages.Prev, ShouldResemble, testPages.Prev)
    64  
    65  		})
    66  		Convey("creating a PaginationMetadata with just a NextKey"+
    67  			" should creating 1 links in the header", func() {
    68  			testPages := &PageResult{
    69  				Next: &Page{
    70  					Key:      testNextKey,
    71  					Relation: "next",
    72  					Limit:    testNextLimit,
    73  				},
    74  			}
    75  
    76  			pm := &PaginationMetadata{
    77  				Pages: testPages,
    78  
    79  				KeyQueryParam:   testKeyQueryParam,
    80  				LimitQueryParam: testLimitQueryParam,
    81  			}
    82  
    83  			err := pm.MakeHeader(w, testPrefix, testURL, testRoute)
    84  			So(err, ShouldBeNil)
    85  			linksHeader, ok := w.Header()["Link"]
    86  			So(ok, ShouldBeTrue)
    87  			So(len(linksHeader), ShouldEqual, 1)
    88  			pmRes, err := ParsePaginationHeader(linksHeader[0], testKeyQueryParam, testLimitQueryParam)
    89  			So(err, ShouldBeNil)
    90  
    91  			So(pmRes.Pages, ShouldNotBeNil)
    92  			So(pmRes.Pages.Next, ShouldResemble, testPages.Next)
    93  
    94  		})
    95  		Convey("creating a page with a limit of 0 should parse as 0", func() {
    96  			testPages := &PageResult{
    97  				Next: &Page{
    98  					Key:      testNextKey,
    99  					Relation: "next",
   100  				},
   101  			}
   102  
   103  			pm := &PaginationMetadata{
   104  				Pages: testPages,
   105  
   106  				KeyQueryParam:   testKeyQueryParam,
   107  				LimitQueryParam: testLimitQueryParam,
   108  			}
   109  
   110  			err := pm.MakeHeader(w, testPrefix, testURL, testRoute)
   111  			So(err, ShouldBeNil)
   112  			linksHeader, ok := w.Header()["Link"]
   113  			So(ok, ShouldBeTrue)
   114  			So(len(linksHeader), ShouldEqual, 1)
   115  			pmRes, err := ParsePaginationHeader(linksHeader[0], testKeyQueryParam, testLimitQueryParam)
   116  			So(err, ShouldBeNil)
   117  
   118  			So(pmRes.Pages, ShouldNotBeNil)
   119  			So(pmRes.Pages.Next, ShouldResemble, testPages.Next)
   120  		})
   121  	})
   122  }
   123  
   124  type testPaginationRequestHandler struct {
   125  	*PaginationExecutor
   126  }
   127  
   128  func (prh *testPaginationRequestHandler) Handler() RequestHandler {
   129  	return prh
   130  }
   131  
   132  func TestPaginationExecutor(t *testing.T) {
   133  	curPage := Page{
   134  		Limit:    10,
   135  		Key:      "cur_key",
   136  		Relation: "current",
   137  	}
   138  	nextPage := Page{
   139  		Limit:    10,
   140  		Key:      "next_key",
   141  		Relation: "next",
   142  	}
   143  	prevPage := Page{
   144  		Limit:    7,
   145  		Key:      "prev_key",
   146  		Relation: "prev",
   147  	}
   148  	numModels := 10
   149  	testKeyQueryParam := "key"
   150  	testLimitQueryParam := "limit"
   151  	testModels := []model.Model{}
   152  	for i := 0; i < numModels; i++ {
   153  		nextModel := &model.MockModel{
   154  			FieldId:   fmt.Sprintf("model_%d", i),
   155  			FieldInt1: i,
   156  		}
   157  		testModels = append(testModels, nextModel)
   158  	}
   159  	Convey("When paginating with a paginator func and executor", t, func() {
   160  		testPaginatorFunc := mockPaginatorFuncGenerator(testModels, nextPage.Key, prevPage.Key,
   161  			nextPage.Limit, prevPage.Limit, nil)
   162  
   163  		executor := &PaginationExecutor{
   164  			KeyQueryParam:   testKeyQueryParam,
   165  			LimitQueryParam: testLimitQueryParam,
   166  
   167  			Paginator: testPaginatorFunc,
   168  		}
   169  
   170  		prh := &testPaginationRequestHandler{
   171  			executor,
   172  		}
   173  
   174  		mh := MethodHandler{
   175  			MethodType:     "GET",
   176  			Authenticator:  &NoAuthAuthenticator{},
   177  			RequestHandler: prh,
   178  		}
   179  		Convey("A request with key and limit should return a correctly formed result",
   180  			func() {
   181  
   182  				queryParams := fmt.Sprintf("%s=%s&%s=%d", testKeyQueryParam, curPage.Key,
   183  					testLimitQueryParam, curPage.Limit)
   184  
   185  				expectedPages := PageResult{&nextPage, &prevPage}
   186  
   187  				hcf := func(header map[string][]string, t *testing.T) {
   188  					linkHeader := header["Link"]
   189  					So(len(linkHeader), ShouldEqual, 1)
   190  					pmRes, err := ParsePaginationHeader(linkHeader[0], testKeyQueryParam, testLimitQueryParam)
   191  					So(err, ShouldBeNil)
   192  
   193  					So(pmRes.Pages, ShouldNotBeNil)
   194  					So(pmRes.Pages.Next, ShouldResemble, expectedPages.Next)
   195  					So(pmRes.Pages.Prev, ShouldResemble, expectedPages.Prev)
   196  				}
   197  
   198  				checkResultMatches(mh, nil,
   199  					testModels, hcf, queryParams, http.StatusOK, t)
   200  			})
   201  		Convey("A request without a non number limit should return an error",
   202  			func() {
   203  
   204  				queryParams := fmt.Sprintf("%s=%s", testLimitQueryParam, "garbage")
   205  
   206  				expectedError := apiv3.APIError{
   207  					StatusCode: http.StatusBadRequest,
   208  					Message: fmt.Sprintf("Value '%v' provided for '%v' must be integer",
   209  						"garbage", testLimitQueryParam),
   210  				}
   211  
   212  				checkResultMatches(mh, expectedError,
   213  					nil, nil, queryParams, http.StatusBadRequest, t)
   214  			})
   215  		Convey("an errored executor should return an error",
   216  			func() {
   217  				errToReturn := fmt.Errorf("not found in DB")
   218  
   219  				executor.Paginator = mockPaginatorFuncGenerator(nil, "", "", 0, 0, errToReturn)
   220  				checkResultMatches(mh, errToReturn,
   221  					nil, nil, "", http.StatusInternalServerError, t)
   222  			})
   223  	})
   224  }
   225  
   226  type testAdditionalArgsPaginator struct {
   227  	*PaginationExecutor
   228  }
   229  
   230  func (tap *testAdditionalArgsPaginator) Handler() RequestHandler {
   231  	return tap
   232  }
   233  
   234  type ArgHolder struct {
   235  	Arg1 int
   236  	Arg2 string
   237  }
   238  
   239  const (
   240  	testArg1Holder = "arg1"
   241  	testArg2Holder = "arg2"
   242  )
   243  
   244  func (tap *testAdditionalArgsPaginator) ParseAndValidate(r *http.Request) error {
   245  	arg1 := context.Get(r, testArg1Holder)
   246  	castArg1, ok := arg1.(int)
   247  	if !ok {
   248  		return fmt.Errorf("incorrect type for arg1. found %v", arg1)
   249  	}
   250  	arg2 := context.Get(r, testArg2Holder)
   251  	castArg2, ok := arg2.(string)
   252  	if !ok {
   253  		return fmt.Errorf("incorrect type for arg2. found %v", arg2)
   254  	}
   255  
   256  	ah := ArgHolder{
   257  		Arg1: castArg1,
   258  		Arg2: castArg2,
   259  	}
   260  	tap.Args = &ah
   261  	return tap.PaginationExecutor.ParseAndValidate(r)
   262  }
   263  
   264  func TestPaginatorAdditionalArgs(t *testing.T) {
   265  	Convey("When paginating with a additional args paginator func and args", t, func() {
   266  		arg1 := 7
   267  		arg2 := "testArg1"
   268  
   269  		sc := servicecontext.MockServiceContext{}
   270  
   271  		r := &http.Request{
   272  			URL: &url.URL{
   273  				RawQuery: "key=key&limit=100",
   274  			},
   275  		}
   276  
   277  		context.Set(r, testArg1Holder, arg1)
   278  		context.Set(r, testArg2Holder, arg2)
   279  
   280  		pf := func(key string, limit int, args interface{},
   281  			sc servicecontext.ServiceContext) ([]model.Model, *PageResult, error) {
   282  			castArgs, ok := args.(*ArgHolder)
   283  			So(ok, ShouldBeTrue)
   284  			So(castArgs.Arg1, ShouldEqual, arg1)
   285  			So(castArgs.Arg2, ShouldEqual, arg2)
   286  			So(key, ShouldEqual, "key")
   287  			So(limit, ShouldEqual, 100)
   288  			return []model.Model{}, nil, nil
   289  		}
   290  
   291  		executor := &PaginationExecutor{
   292  			KeyQueryParam:   "key",
   293  			LimitQueryParam: "limit",
   294  
   295  			Paginator: pf,
   296  		}
   297  		tap := testAdditionalArgsPaginator{executor}
   298  
   299  		Convey("parsing should succeed and args should be set", func() {
   300  			err := tap.ParseAndValidate(r)
   301  			So(err, ShouldBeNil)
   302  			_, err = tap.Execute(&sc)
   303  			So(err, ShouldBeNil)
   304  		})
   305  	})
   306  }