Skip to content

Commit dad242a

Browse files
authored
feat: Add more swagger checks (#5707)
1 parent 54cc587 commit dad242a

File tree

4 files changed

+39
-2
lines changed

4 files changed

+39
-2
lines changed

coderd/coderdtest/swagger_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func TestEndpointsDocumented(t *testing.T) {
1818

1919
swaggerComments, err := coderdtest.ParseSwaggerComments("..")
2020
require.NoError(t, err, "can't parse swagger comments")
21+
require.NotEmpty(t, swaggerComments, "swagger comments must be present")
2122

2223
_, _, api := coderdtest.NewWithAPI(t, nil)
2324
coderdtest.VerifySwaggerDefinitions(t, api.APIHandler, swaggerComments)

coderd/coderdtest/swaggerparser.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ func parseSwaggerComment(commentGroup *ast.CommentGroup) SwaggerComment {
149149

150150
func VerifySwaggerDefinitions(t *testing.T, router chi.Router, swaggerComments []SwaggerComment) {
151151
assertUniqueRoutes(t, swaggerComments)
152+
assertSingleAnnotations(t, swaggerComments)
152153

153154
err := chi.Walk(router, func(method, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
154155
method = strings.ToLower(method)
@@ -192,6 +193,36 @@ func assertUniqueRoutes(t *testing.T, comments []SwaggerComment) {
192193
}
193194
}
194195

196+
var uniqueAnnotations = []string{"@ID", "@Summary", "@Tags", "@Router"}
197+
198+
func assertSingleAnnotations(t *testing.T, comments []SwaggerComment) {
199+
for _, comment := range comments {
200+
counters := map[string]int{}
201+
202+
for _, line := range comment.raw {
203+
splitN := strings.SplitN(strings.TrimSpace(line.Text), " ", 3)
204+
if len(splitN) < 2 {
205+
continue // comment prefix without any content
206+
}
207+
208+
if !strings.HasPrefix(splitN[1], "@") {
209+
continue // not a swagger annotation
210+
}
211+
212+
annotation := splitN[1]
213+
if _, ok := counters[annotation]; !ok {
214+
counters[annotation] = 0
215+
}
216+
counters[annotation]++
217+
}
218+
219+
for _, annotation := range uniqueAnnotations {
220+
v := counters[annotation]
221+
assert.Equal(t, 1, v, "%s annotation for route %s must be defined only once", annotation, comment.router)
222+
}
223+
}
224+
}
225+
195226
func findSwaggerCommentByMethodAndRoute(comments []SwaggerComment, method, route string) *SwaggerComment {
196227
for _, c := range comments {
197228
if c.method == method && c.router == route {
@@ -219,6 +250,7 @@ func assertRequiredAnnotations(t *testing.T, comment SwaggerComment) {
219250
assert.NotEmpty(t, comment.id, "@ID must be defined")
220251
assert.NotEmpty(t, comment.summary, "@Summary must be defined")
221252
assert.NotEmpty(t, comment.tags, "@Tags must be defined")
253+
assert.NotEmpty(t, comment.router, "@Router must be defined")
222254
}
223255

224256
func assertGoCommentFirst(t *testing.T, comment SwaggerComment) {
@@ -295,6 +327,8 @@ func assertAccept(t *testing.T, comment SwaggerComment) {
295327
}
296328
}
297329

330+
var allowedProduceTypes = []string{"json", "text/event-stream"}
331+
298332
func assertProduce(t *testing.T, comment SwaggerComment) {
299333
var hasResponseModel bool
300334
for _, r := range comment.successes {
@@ -306,6 +340,7 @@ func assertProduce(t *testing.T, comment SwaggerComment) {
306340

307341
if hasResponseModel {
308342
assert.True(t, comment.produce != "", "Route must have @Produce annotation as it responds with a model structure")
343+
assert.Contains(t, allowedProduceTypes, comment.produce, "@Produce value is limited to specific types: %s", strings.Join(allowedProduceTypes, ","))
309344
} else {
310345
if (comment.router == "/workspaceagents/me/app-health" && comment.method == "post") ||
311346
(comment.router == "/workspaceagents/me/version" && comment.method == "post") ||

coderd/workspaceagents.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,7 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordin
826826
// @ID submit-workspace-agent-stats
827827
// @Security CoderSessionToken
828828
// @Accept json
829-
// @Produce application/json
829+
// @Produce json
830830
// @Tags Agents
831831
// @Param request body codersdk.AgentStats true "Stats request"
832832
// @Success 200 {object} codersdk.AgentStatsResponse
@@ -904,7 +904,7 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques
904904
// @ID submit-workspace-agent-application-health
905905
// @Security CoderSessionToken
906906
// @Accept json
907-
// @Produce application/json
907+
// @Produce json
908908
// @Tags Agents
909909
// @Param request body codersdk.PostWorkspaceAppHealthsRequest true "Application health request"
910910
// @Success 200

enterprise/coderd/coderdenttest/swagger_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func TestEnterpriseEndpointsDocumented(t *testing.T) {
1414

1515
swaggerComments, err := coderdtest.ParseSwaggerComments("..", "../../../coderd")
1616
require.NoError(t, err, "can't parse swagger comments")
17+
require.NotEmpty(t, swaggerComments, "swagger comments must be present")
1718

1819
_, _, api := coderdenttest.NewWithAPI(t, nil)
1920
coderdtest.VerifySwaggerDefinitions(t, api.AGPL.APIHandler, swaggerComments)

0 commit comments

Comments
 (0)