@@ -15,6 +15,11 @@ import (
15
15
"golang.org/x/xerrors"
16
16
)
17
17
18
+ type route struct {
19
+ path string
20
+ method string
21
+ }
22
+
18
23
type SwaggerComment struct {
19
24
summary string
20
25
id string
@@ -23,8 +28,7 @@ type SwaggerComment struct {
23
28
accept string
24
29
produce string
25
30
26
- method string
27
- router string
31
+ routes []route
28
32
29
33
successes []response
30
34
failures []response
@@ -34,6 +38,15 @@ type SwaggerComment struct {
34
38
raw []* ast.Comment
35
39
}
36
40
41
+ func (s * SwaggerComment ) hasPath (path string ) bool {
42
+ for _ , route := range s .routes {
43
+ if route .path == path {
44
+ return true
45
+ }
46
+ }
47
+ return false
48
+ }
49
+
37
50
type parameter struct {
38
51
name string
39
52
kind string
@@ -105,8 +118,10 @@ func parseSwaggerComment(commentGroup *ast.CommentGroup) SwaggerComment {
105
118
106
119
switch annotationName {
107
120
case "@Router" :
108
- c .router = args [0 ]
109
- c .method = args [1 ][1 : len (args [1 ])- 1 ]
121
+ c .routes = append (c .routes , route {
122
+ path : args [0 ],
123
+ method : args [1 ][1 : len (args [1 ])- 1 ],
124
+ })
110
125
case "@Success" , "@Failure" :
111
126
var r response
112
127
if len (args ) > 0 {
@@ -160,7 +175,7 @@ func VerifySwaggerDefinitions(t *testing.T, router chi.Router, swaggerComments [
160
175
t .Run (method + " " + route , func (t * testing.T ) {
161
176
t .Parallel ()
162
177
163
- c := findSwaggerCommentByMethodAndRoute (swaggerComments , method , route )
178
+ c := findSwaggerCommentByMethodAndPath (swaggerComments , method , route )
164
179
assert .NotNil (t , c , "Missing @Router annotation" )
165
180
if c == nil {
166
181
return // do not fail next assertion for this route
@@ -184,11 +199,13 @@ func assertUniqueRoutes(t *testing.T, comments []SwaggerComment) {
184
199
m := map [string ]struct {}{}
185
200
186
201
for _ , c := range comments {
187
- key := c .method + " " + c .router
188
- _ , alreadyDefined := m [key ]
189
- assert .False (t , alreadyDefined , "defined route must be unique (method: %s, route: %s)" , c .method , c .router )
190
- if ! alreadyDefined {
191
- m [key ] = struct {}{}
202
+ for _ , r := range c .routes {
203
+ key := r .method + " " + r .path
204
+ _ , alreadyDefined := m [key ]
205
+ assert .False (t , alreadyDefined , "defined route must be unique (method: %s, route: %s)" , r .method , r .path )
206
+ if ! alreadyDefined {
207
+ m [key ] = struct {}{}
208
+ }
192
209
}
193
210
}
194
211
}
@@ -218,15 +235,17 @@ func assertSingleAnnotations(t *testing.T, comments []SwaggerComment) {
218
235
219
236
for _ , annotation := range uniqueAnnotations {
220
237
v := counters [annotation ]
221
- assert .Equal (t , 1 , v , "%s annotation for route %s must be defined only once" , annotation , comment .router )
238
+ assert .LessOrEqual (t , 1 , v , "%s annotation for route %s must be defined only once" , annotation , comment .routes )
222
239
}
223
240
}
224
241
}
225
242
226
- func findSwaggerCommentByMethodAndRoute (comments []SwaggerComment , method , route string ) * SwaggerComment {
243
+ func findSwaggerCommentByMethodAndPath (comments []SwaggerComment , method , path string ) * SwaggerComment {
227
244
for _ , c := range comments {
228
- if c .method == method && c .router == route {
229
- return & c
245
+ for _ , r := range c .routes {
246
+ if r .method == method && r .path == path {
247
+ return & c
248
+ }
230
249
}
231
250
}
232
251
return nil
@@ -250,7 +269,7 @@ func assertRequiredAnnotations(t *testing.T, comment SwaggerComment) {
250
269
assert .NotEmpty (t , comment .id , "@ID must be defined" )
251
270
assert .NotEmpty (t , comment .summary , "@Summary must be defined" )
252
271
assert .NotEmpty (t , comment .tags , "@Tags must be defined" )
253
- assert .NotEmpty (t , comment .router , "@Router must be defined" )
272
+ assert .NotEmpty (t , comment .routes , "@Router must be defined" )
254
273
}
255
274
256
275
func assertGoCommentFirst (t * testing.T , comment SwaggerComment ) {
@@ -274,7 +293,11 @@ func assertGoCommentFirst(t *testing.T, comment SwaggerComment) {
274
293
var urlParameterRegexp = regexp .MustCompile (`{[^{}]*}` )
275
294
276
295
func assertPathParametersDefined (t * testing.T , comment SwaggerComment ) {
277
- matches := urlParameterRegexp .FindAllString (comment .router , - 1 )
296
+ var paths []string
297
+ for _ , r := range comment .routes {
298
+ paths = append (paths , r .path )
299
+ }
300
+ matches := urlParameterRegexp .FindAllString (strings .Join (paths , "\n " ), - 1 )
278
301
if matches == nil {
279
302
return // router does not require any parameters
280
303
}
@@ -295,10 +318,10 @@ func assertPathParametersDefined(t *testing.T, comment SwaggerComment) {
295
318
}
296
319
297
320
func assertSecurityDefined (t * testing.T , comment SwaggerComment ) {
298
- if comment .router == "/updatecheck" ||
299
- comment .router == "/buildinfo" ||
300
- comment .router == "/" ||
301
- comment .router == "/users/login" {
321
+ if comment .hasPath ( "/updatecheck" ) ||
322
+ comment .hasPath ( "/buildinfo" ) ||
323
+ comment .hasPath ( "/" ) ||
324
+ comment .hasPath ( "/users/login" ) {
302
325
return // endpoints do not require authorization
303
326
}
304
327
assert .Equal (t , "CoderSessionToken" , comment .security , "@Security must be equal CoderSessionToken" )
@@ -319,12 +342,14 @@ func assertAccept(t *testing.T, comment SwaggerComment) {
319
342
hasAccept = true
320
343
}
321
344
322
- if comment .method == "get" {
323
- assert .Empty (t , comment .accept , "GET route does not require the @Accept annotation" )
324
- assert .False (t , hasRequestBody , "GET route does not require the request body" )
325
- } else {
326
- assert .False (t , hasRequestBody && ! hasAccept , "Route with the request body requires the @Accept annotation" )
327
- assert .False (t , ! hasRequestBody && hasAccept , "Route with @Accept annotation requires the request body or file formData parameter" )
345
+ for _ , r := range comment .routes {
346
+ if r .method == "get" {
347
+ assert .Empty (t , comment .accept , "GET route does not require the @Accept annotation" )
348
+ assert .False (t , hasRequestBody , "GET route does not require the request body" )
349
+ } else {
350
+ assert .False (t , hasRequestBody && ! hasAccept , "Route with the request body requires the @Accept annotation" )
351
+ assert .False (t , ! hasRequestBody && hasAccept , "Route with @Accept annotation requires the request body or file formData parameter" )
352
+ }
328
353
}
329
354
}
330
355
@@ -339,18 +364,20 @@ func assertProduce(t *testing.T, comment SwaggerComment) {
339
364
}
340
365
}
341
366
342
- if hasResponseModel {
343
- assert .True (t , comment .produce != "" , "Route must have @Produce annotation as it responds with a model structure" )
344
- assert .Contains (t , allowedProduceTypes , comment .produce , "@Produce value is limited to specific types: %s" , strings .Join (allowedProduceTypes , "," ))
345
- } else {
346
- if (comment .router == "/workspaceagents/me/app-health" && comment .method == "post" ) ||
347
- (comment .router == "/workspaceagents/me/startup" && comment .method == "post" ) ||
348
- (comment .router == "/workspaceagents/me/startup/logs" && comment .method == "patch" ) ||
349
- (comment .router == "/licenses/{id}" && comment .method == "delete" ) ||
350
- (comment .router == "/debug/coordinator" && comment .method == "get" ) {
351
- return // Exception: HTTP 200 is returned without response entity
352
- }
367
+ for _ , r := range comment .routes {
368
+ if hasResponseModel {
369
+ assert .True (t , comment .produce != "" , "Route must have @Produce annotation as it responds with a model structure" )
370
+ assert .Contains (t , allowedProduceTypes , comment .produce , "@Produce value is limited to specific types: %s" , strings .Join (allowedProduceTypes , "," ))
371
+ } else {
372
+ if (r .path == "/workspaceagents/me/app-health" && r .method == "post" ) ||
373
+ (r .path == "/workspaceagents/me/startup" && r .method == "post" ) ||
374
+ (r .path == "/workspaceagents/me/startup/logs" && r .method == "patch" ) ||
375
+ (r .path == "/licenses/{id}" && r .method == "delete" ) ||
376
+ (r .path == "/debug/coordinator" && r .method == "get" ) {
377
+ return // Exception: HTTP 200 is returned without response entity
378
+ }
353
379
354
- assert .True (t , comment .produce == "" , "Response model is undefined, so we can't predict the content type" , comment )
380
+ assert .True (t , comment .produce == "" , "Response model is undefined, so we can't predict the content type" , comment )
381
+ }
355
382
}
356
383
}
0 commit comments