Skip to content

Commit 66468d3

Browse files
committed
Go on a multi-route tangent
1 parent 9ae8650 commit 66468d3

File tree

1 file changed

+65
-38
lines changed

1 file changed

+65
-38
lines changed

coderd/coderdtest/swaggerparser.go

+65-38
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import (
1515
"golang.org/x/xerrors"
1616
)
1717

18+
type route struct {
19+
path string
20+
method string
21+
}
22+
1823
type SwaggerComment struct {
1924
summary string
2025
id string
@@ -23,8 +28,7 @@ type SwaggerComment struct {
2328
accept string
2429
produce string
2530

26-
method string
27-
router string
31+
routes []route
2832

2933
successes []response
3034
failures []response
@@ -34,6 +38,15 @@ type SwaggerComment struct {
3438
raw []*ast.Comment
3539
}
3640

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+
3750
type parameter struct {
3851
name string
3952
kind string
@@ -105,8 +118,10 @@ func parseSwaggerComment(commentGroup *ast.CommentGroup) SwaggerComment {
105118

106119
switch annotationName {
107120
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+
})
110125
case "@Success", "@Failure":
111126
var r response
112127
if len(args) > 0 {
@@ -160,7 +175,7 @@ func VerifySwaggerDefinitions(t *testing.T, router chi.Router, swaggerComments [
160175
t.Run(method+" "+route, func(t *testing.T) {
161176
t.Parallel()
162177

163-
c := findSwaggerCommentByMethodAndRoute(swaggerComments, method, route)
178+
c := findSwaggerCommentByMethodAndPath(swaggerComments, method, route)
164179
assert.NotNil(t, c, "Missing @Router annotation")
165180
if c == nil {
166181
return // do not fail next assertion for this route
@@ -184,11 +199,13 @@ func assertUniqueRoutes(t *testing.T, comments []SwaggerComment) {
184199
m := map[string]struct{}{}
185200

186201
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+
}
192209
}
193210
}
194211
}
@@ -218,15 +235,17 @@ func assertSingleAnnotations(t *testing.T, comments []SwaggerComment) {
218235

219236
for _, annotation := range uniqueAnnotations {
220237
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)
222239
}
223240
}
224241
}
225242

226-
func findSwaggerCommentByMethodAndRoute(comments []SwaggerComment, method, route string) *SwaggerComment {
243+
func findSwaggerCommentByMethodAndPath(comments []SwaggerComment, method, path string) *SwaggerComment {
227244
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+
}
230249
}
231250
}
232251
return nil
@@ -250,7 +269,7 @@ func assertRequiredAnnotations(t *testing.T, comment SwaggerComment) {
250269
assert.NotEmpty(t, comment.id, "@ID must be defined")
251270
assert.NotEmpty(t, comment.summary, "@Summary must be defined")
252271
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")
254273
}
255274

256275
func assertGoCommentFirst(t *testing.T, comment SwaggerComment) {
@@ -274,7 +293,11 @@ func assertGoCommentFirst(t *testing.T, comment SwaggerComment) {
274293
var urlParameterRegexp = regexp.MustCompile(`{[^{}]*}`)
275294

276295
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)
278301
if matches == nil {
279302
return // router does not require any parameters
280303
}
@@ -295,10 +318,10 @@ func assertPathParametersDefined(t *testing.T, comment SwaggerComment) {
295318
}
296319

297320
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") {
302325
return // endpoints do not require authorization
303326
}
304327
assert.Equal(t, "CoderSessionToken", comment.security, "@Security must be equal CoderSessionToken")
@@ -319,12 +342,14 @@ func assertAccept(t *testing.T, comment SwaggerComment) {
319342
hasAccept = true
320343
}
321344

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+
}
328353
}
329354
}
330355

@@ -339,18 +364,20 @@ func assertProduce(t *testing.T, comment SwaggerComment) {
339364
}
340365
}
341366

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+
}
353379

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+
}
355382
}
356383
}

0 commit comments

Comments
 (0)