1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package middleware
16
17 import (
18 stdcontext "context"
19 "net/http"
20 "net/http/httptest"
21 "sort"
22 "strings"
23 "testing"
24
25 "github.com/go-openapi/analysis"
26 "github.com/go-openapi/loads"
27 "github.com/go-openapi/runtime/internal/testing/petstore"
28 "github.com/go-openapi/runtime/middleware/untyped"
29 "github.com/stretchr/testify/assert"
30 "github.com/stretchr/testify/require"
31 )
32
33 func terminator(rw http.ResponseWriter, _ *http.Request) {
34 rw.WriteHeader(http.StatusOK)
35 }
36
37 func TestRouterMiddleware(t *testing.T) {
38 spec, api := petstore.NewAPI(t)
39 context := NewContext(spec, api, nil)
40 mw := NewRouter(context, http.HandlerFunc(terminator))
41
42 recorder := httptest.NewRecorder()
43 request, err := http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets", nil)
44 require.NoError(t, err)
45
46 mw.ServeHTTP(recorder, request)
47 assert.Equal(t, http.StatusOK, recorder.Code)
48
49 recorder = httptest.NewRecorder()
50 request, err = http.NewRequestWithContext(stdcontext.Background(), http.MethodDelete, "/api/pets", nil)
51 require.NoError(t, err)
52
53 mw.ServeHTTP(recorder, request)
54 assert.Equal(t, http.StatusMethodNotAllowed, recorder.Code)
55
56 methods := strings.Split(recorder.Header().Get("Allow"), ",")
57 sort.Strings(methods)
58 assert.Equal(t, "GET,POST", strings.Join(methods, ","))
59
60 recorder = httptest.NewRecorder()
61 request, err = http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/nopets", nil)
62 require.NoError(t, err)
63
64 mw.ServeHTTP(recorder, request)
65 assert.Equal(t, http.StatusNotFound, recorder.Code)
66
67 recorder = httptest.NewRecorder()
68 request, err = http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/pets", nil)
69 require.NoError(t, err)
70
71 mw.ServeHTTP(recorder, request)
72 assert.Equal(t, http.StatusNotFound, recorder.Code)
73
74 spec, api = petstore.NewRootAPI(t)
75 context = NewContext(spec, api, nil)
76 mw = NewRouter(context, http.HandlerFunc(terminator))
77
78 recorder = httptest.NewRecorder()
79 request, err = http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/pets", nil)
80 require.NoError(t, err)
81
82 mw.ServeHTTP(recorder, request)
83 assert.Equal(t, http.StatusOK, recorder.Code)
84
85 recorder = httptest.NewRecorder()
86 request, err = http.NewRequestWithContext(stdcontext.Background(), http.MethodDelete, "/pets", nil)
87 require.NoError(t, err)
88
89 mw.ServeHTTP(recorder, request)
90 assert.Equal(t, http.StatusMethodNotAllowed, recorder.Code)
91
92 methods = strings.Split(recorder.Header().Get("Allow"), ",")
93 sort.Strings(methods)
94 assert.Equal(t, "GET,POST", strings.Join(methods, ","))
95
96 recorder = httptest.NewRecorder()
97 request, err = http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/nopets", nil)
98 require.NoError(t, err)
99
100 mw.ServeHTTP(recorder, request)
101 assert.Equal(t, http.StatusNotFound, recorder.Code)
102 }
103
104 func TestRouterBuilder(t *testing.T) {
105 spec, api := petstore.NewAPI(t)
106 analyzed := analysis.New(spec.Spec())
107
108 assert.Len(t, analyzed.RequiredConsumes(), 3)
109 assert.Len(t, analyzed.RequiredProduces(), 5)
110 assert.Len(t, analyzed.OperationIDs(), 4)
111
112
113 builder := petAPIRouterBuilder(spec, api, analyzed)
114 getRecords := builder.records[http.MethodGet]
115 postRecords := builder.records[http.MethodPost]
116 deleteRecords := builder.records[http.MethodDelete]
117
118 assert.Len(t, getRecords, 2)
119 assert.Len(t, postRecords, 1)
120 assert.Len(t, deleteRecords, 1)
121
122 assert.Empty(t, builder.records[http.MethodPatch])
123 assert.Empty(t, builder.records[http.MethodOptions])
124 assert.Empty(t, builder.records[http.MethodHead])
125 assert.Empty(t, builder.records[http.MethodPut])
126
127 rec := postRecords[0]
128 assert.Equal(t, "/pets", rec.Key)
129 val := rec.Value.(*routeEntry)
130 assert.Len(t, val.Consumers, 2)
131 assert.Len(t, val.Producers, 2)
132 assert.Len(t, val.Consumes, 2)
133 assert.Len(t, val.Produces, 2)
134
135 assert.Contains(t, val.Consumers, "application/json")
136 assert.Contains(t, val.Producers, "application/x-yaml")
137 assert.Contains(t, val.Consumes, "application/json")
138 assert.Contains(t, val.Produces, "application/x-yaml")
139
140 assert.Len(t, val.Parameters, 1)
141
142 recG := getRecords[0]
143 assert.Equal(t, "/pets", recG.Key)
144 valG := recG.Value.(*routeEntry)
145 assert.Len(t, valG.Consumers, 2)
146 assert.Len(t, valG.Producers, 4)
147 assert.Len(t, valG.Consumes, 2)
148 assert.Len(t, valG.Produces, 4)
149
150 assert.Len(t, valG.Parameters, 2)
151 }
152
153 func TestRouterCanonicalBasePath(t *testing.T) {
154 spec, api := petstore.NewAPI(t)
155 spec.Spec().BasePath = "/api///"
156 context := NewContext(spec, api, nil)
157 mw := NewRouter(context, http.HandlerFunc(terminator))
158
159 recorder := httptest.NewRecorder()
160 request, err := http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets", nil)
161 require.NoError(t, err)
162
163 mw.ServeHTTP(recorder, request)
164 assert.Equal(t, http.StatusOK, recorder.Code)
165 }
166
167 func TestRouter_EscapedPath(t *testing.T) {
168 spec, api := petstore.NewAPI(t)
169 spec.Spec().BasePath = "/api/"
170 context := NewContext(spec, api, nil)
171 mw := NewRouter(context, http.HandlerFunc(terminator))
172
173 recorder := httptest.NewRecorder()
174 request, err := http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets/123", nil)
175 require.NoError(t, err)
176
177 mw.ServeHTTP(recorder, request)
178 assert.Equal(t, http.StatusOK, recorder.Code)
179
180 recorder = httptest.NewRecorder()
181 request, err = http.NewRequestWithContext(stdcontext.Background(), http.MethodGet, "/api/pets/abc%2Fdef", nil)
182 require.NoError(t, err)
183
184 mw.ServeHTTP(recorder, request)
185 assert.Equal(t, http.StatusOK, recorder.Code)
186 ri, _, _ := context.RouteInfo(request)
187 require.NotNil(t, ri)
188 require.NotNil(t, ri.Params)
189 assert.Equal(t, "abc/def", ri.Params.Get("id"))
190 }
191
192 func TestRouterStruct(t *testing.T) {
193 spec, api := petstore.NewAPI(t)
194 router := DefaultRouter(spec, newRoutableUntypedAPI(spec, api, new(Context)))
195
196 methods := router.OtherMethods("post", "/api/pets/{id}")
197 assert.Len(t, methods, 2)
198
199 entry, ok := router.Lookup("delete", "/api/pets/{id}")
200 assert.True(t, ok)
201 require.NotNil(t, entry)
202 assert.Len(t, entry.Params, 1)
203 assert.Equal(t, "id", entry.Params[0].Name)
204
205 _, ok = router.Lookup("delete", "/pets")
206 assert.False(t, ok)
207
208 _, ok = router.Lookup("post", "/no-pets")
209 assert.False(t, ok)
210 }
211
212 func petAPIRouterBuilder(spec *loads.Document, api *untyped.API, analyzed *analysis.Spec) *defaultRouteBuilder {
213 builder := newDefaultRouteBuilder(spec, newRoutableUntypedAPI(spec, api, new(Context)))
214 builder.AddRoute(http.MethodGet, "/pets", analyzed.AllPaths()["/pets"].Get)
215 builder.AddRoute(http.MethodPost, "/pets", analyzed.AllPaths()["/pets"].Post)
216 builder.AddRoute(http.MethodDelete, "/pets/{id}", analyzed.AllPaths()["/pets/{id}"].Delete)
217 builder.AddRoute(http.MethodGet, "/pets/{id}", analyzed.AllPaths()["/pets/{id}"].Get)
218
219 return builder
220 }
221
222 func TestPathConverter(t *testing.T) {
223 cases := []struct {
224 swagger string
225 denco string
226 }{
227 {"/", "/"},
228 {"/something", "/something"},
229 {"/{id}", "/:id"},
230 {"/{id}/something/{anotherId}", "/:id/something/:anotherId"},
231 {"/{petid}", "/:petid"},
232 {"/{pet_id}", "/:pet_id"},
233 {"/{petId}", "/:petId"},
234 {"/{pet-id}", "/:pet-id"},
235
236 {"/p_{pet_id}", "/p_:pet_id"},
237 {"/p_{petId}.{petSubId}", "/p_:petId"},
238 }
239
240 for _, tc := range cases {
241 actual := pathConverter.ReplaceAllString(tc.swagger, ":$1")
242 assert.Equal(t, tc.denco, actual, "expected swagger path %s to match %s but got %s", tc.swagger, tc.denco, actual)
243 }
244 }
245
246 func TestExtractCompositParameters(t *testing.T) {
247
248 cases := []struct {
249 name string
250 value string
251 pattern string
252 names []string
253 values []string
254 }{
255 {name: "fragment", value: "gie", pattern: "e", names: []string{"fragment"}, values: []string{"gi"}},
256 {name: "fragment", value: "t.simpson", pattern: ".{subfragment}", names: []string{"fragment", "subfragment"}, values: []string{"t", "simpson"}},
257 }
258 for _, tc := range cases {
259 names, values := decodeCompositParams(tc.name, tc.value, tc.pattern, nil, nil)
260 assert.EqualValues(t, tc.names, names)
261 assert.EqualValues(t, tc.values, values)
262 }
263 }
264
View as plain text