Skip to content

Commit 9ac63c3

Browse files
committed
feat: added validation method for pagination cache
1 parent a3f3f5d commit 9ac63c3

File tree

3 files changed

+121
-0
lines changed

3 files changed

+121
-0
lines changed

bluemix/configuration/config_helpers/helpers.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
package config_helpers
33

44
import (
5+
"encoding/base64"
6+
gourl "net/url"
57
"os"
68
"path/filepath"
79
"runtime"
@@ -91,3 +93,45 @@ func UserHomeDir() string {
9193

9294
return os.Getenv("HOME")
9395
}
96+
97+
// IsValidPaginationNextURL will return true if the provided nextURL has the expected queries provided
98+
func IsValidPaginationNextURL(nextURL string, cursorQueryParamName string, expectedQueries gourl.Values) bool {
99+
parsedURL, parseErr := gourl.Parse(nextURL)
100+
// NOTE: ignore handling error(s) since if there error(s)
101+
// we can assume the url is invalid
102+
if parseErr != nil {
103+
return false
104+
}
105+
106+
// retrive encoded cursor
107+
// eg. /api?cursor=<encode_string>
108+
queries := parsedURL.Query()
109+
encodedQuery := queries.Get(cursorQueryParamName)
110+
if encodedQuery == "" {
111+
return false
112+
113+
}
114+
// decode string and parse encoded queries
115+
decodedQuery, decodedErr := base64.RawURLEncoding.DecodeString(encodedQuery)
116+
if decodedErr != nil {
117+
return false
118+
}
119+
queries, parsedErr := gourl.ParseQuery(string(decodedQuery))
120+
if parsedErr != nil {
121+
return false
122+
}
123+
124+
// compare expected queries that should match
125+
// NOTE: assume queries are single value queries.
126+
// if multi-value queries will check the first query
127+
for expectedQuery := range expectedQueries {
128+
paginationQueryValue := queries.Get(expectedQuery)
129+
expectedQueryValue := expectedQueries.Get(expectedQuery)
130+
if paginationQueryValue != expectedQueryValue {
131+
return false
132+
}
133+
134+
}
135+
136+
return true
137+
}

bluemix/configuration/config_helpers/helpers_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package config_helpers
22

33
import (
4+
"encoding/base64"
45
"io/ioutil"
6+
gourl "net/url"
57
"os"
68
"path/filepath"
79
"strings"
@@ -103,3 +105,62 @@ func TestConfigDir_IbmCloudConfigHomeSet_Exists(t *testing.T) {
103105
os.Setenv("IBMCLOUD_CONFIG_HOME", userHome)
104106
assert.Equal(userHome, ConfigDir())
105107
}
108+
109+
func TestIsValidPaginationNextURL(t *testing.T) {
110+
assert := assert.New(t)
111+
112+
testCases := []struct {
113+
name string
114+
nextURL string
115+
encodedQueryParam string
116+
expectedQueries gourl.Values
117+
isValid bool
118+
}{
119+
{
120+
name: "return true for matching expected queries in pagination url",
121+
nextURL: "/api/example?cursor=" + base64.RawURLEncoding.EncodeToString([]byte("limit=100&active=true")),
122+
encodedQueryParam: "cursor",
123+
expectedQueries: gourl.Values{
124+
"limit": []string{"100"},
125+
"active": []string{"true"},
126+
},
127+
isValid: true,
128+
},
129+
{
130+
name: "return true for matching expected queries with extraneous queries in pagination url",
131+
nextURL: "/api/example?cursor=" + base64.RawURLEncoding.EncodeToString([]byte("limit=100&active=true&extra=foo")),
132+
encodedQueryParam: "cursor",
133+
expectedQueries: gourl.Values{
134+
"limit": []string{"100"},
135+
"active": []string{"true"},
136+
},
137+
isValid: true,
138+
},
139+
{
140+
name: "return false for different limit in pagination url",
141+
nextURL: "/api/example?cursor=" + base64.RawURLEncoding.EncodeToString([]byte("limit=200")),
142+
encodedQueryParam: "cursor",
143+
expectedQueries: gourl.Values{
144+
"limit": []string{"100"},
145+
},
146+
isValid: false,
147+
},
148+
{
149+
name: "return false for different query among multiple parameters in the pagination url",
150+
nextURL: "/api/example?cursor=" + base64.RawURLEncoding.EncodeToString([]byte("limit=100&active=true")),
151+
encodedQueryParam: "cursor",
152+
expectedQueries: gourl.Values{
153+
"limit": []string{"100"},
154+
"active": []string{"false"},
155+
},
156+
isValid: false,
157+
},
158+
}
159+
160+
for _, tc := range testCases {
161+
t.Run(tc.name, func(_ *testing.T) {
162+
isValid := IsValidPaginationNextURL(tc.nextURL, tc.encodedQueryParam, tc.expectedQueries)
163+
assert.Equal(tc.isValid, isValid)
164+
})
165+
}
166+
}

common/rest/request_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,13 @@ func TestCachedPaginationNextURL(t *testing.T) {
137137
name string
138138
paginationURLs []models.PaginationURL
139139
offset int
140+
limit int
140141
expectedNextURL string
141142
}{
142143
{
143144
name: "return cached next URL",
144145
offset: 200,
146+
limit: 100,
145147
paginationURLs: []models.PaginationURL{
146148
{
147149
NextURL: "/v2/example.com/stuff?limit=100",
@@ -153,6 +155,7 @@ func TestCachedPaginationNextURL(t *testing.T) {
153155
{
154156
name: "return empty string if cache URL cannot be determined",
155157
offset: 40,
158+
limit: 100,
156159
paginationURLs: []models.PaginationURL{
157160
{
158161
NextURL: "/v2/example.com/stuff?limit=100",
@@ -164,9 +167,22 @@ func TestCachedPaginationNextURL(t *testing.T) {
164167
{
165168
name: "return empty string if no cache available",
166169
offset: 40,
170+
limit: 100,
167171
paginationURLs: []models.PaginationURL{},
168172
expectedNextURL: "",
169173
},
174+
{
175+
name: "return empty string if limit in url is different than provided limit",
176+
offset: 200,
177+
limit: 200,
178+
paginationURLs: []models.PaginationURL{
179+
{
180+
NextURL: "/v2/example.com?stuff?limit=100",
181+
LastIndex: 100,
182+
},
183+
},
184+
expectedNextURL: "",
185+
},
170186
}
171187

172188
assert := assert.New(t)

0 commit comments

Comments
 (0)