package swag import ( "encoding/json" "fmt" "go/ast" goparser "go/parser" "go/token" "os" "path/filepath" "testing" "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" ) func TestParseEmptyComment(t *testing.T) { t.Parallel() operation := NewOperation(nil) err := operation.ParseComment("//", nil) assert.NoError(t, err) } func TestParseTagsComment(t *testing.T) { t.Parallel() operation := NewOperation(nil) err := operation.ParseComment(`/@Tags pet, store,user`, nil) assert.NoError(t, err) assert.Equal(t, operation.Tags, []string{"pet", "store", "user"}) } func TestParseAcceptComment(t *testing.T) { t.Parallel() comment := `/@Accept json,xml,plain,html,mpfd,x-www-form-urlencoded,json-api,json-stream,octet-stream,png,jpeg,gif,application/xhtml+xml,application/health+json` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Equal(t, operation.Consumes, []string{ "application/json", "text/xml", "text/plain", "text/html", "multipart/form-data", "application/x-www-form-urlencoded", "application/vnd.api+json", "application/x-json-stream", "application/octet-stream", "image/png", "image/jpeg", "image/gif", "application/xhtml+xml", "application/health+json", }) } func TestParseAcceptCommentErr(t *testing.T) { t.Parallel() comment := `/@Accept unknown` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseProduceComment(t *testing.T) { t.Parallel() expected := `{ "produces": [ "application/json", "text/xml", "text/plain", "text/html", "multipart/form-data", "application/x-www-form-urlencoded", "application/vnd.api+json", "application/x-json-stream", "application/octet-stream", "image/png", "image/jpeg", "image/gif", "application/health+json" ] }` comment := `/@Produce json,xml,plain,html,mpfd,x-www-form-urlencoded,json-api,json-stream,octet-stream,png,jpeg,gif,application/health+json` operation := new(Operation) err := operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") b, _ := json.MarshalIndent(operation, "", " ") assert.JSONEq(t, expected, string(b)) } func TestParseProduceCommentErr(t *testing.T) { t.Parallel() operation := new(Operation) err := operation.ParseComment("/@Produce foo", nil) assert.Error(t, err) } func TestParseRouterComment(t *testing.T) { t.Parallel() comment := `/@Router /customer/get-wishlist/{wishlist_id} [get]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Len(t, operation.RouterProperties, 1) assert.Equal(t, "/customer/get-wishlist/{wishlist_id}", operation.RouterProperties[0].Path) assert.Equal(t, "GET", operation.RouterProperties[0].HTTPMethod) comment = `/@Router /customer/get-wishlist/{wishlist_id} [unknown]` operation = NewOperation(nil) err = operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseRouterMultipleComments(t *testing.T) { t.Parallel() comment := `/@Router /customer/get-wishlist/{wishlist_id} [get]` anotherComment := `/@Router /customer/get-the-wishlist/{wishlist_id} [post]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) err = operation.ParseComment(anotherComment, nil) assert.NoError(t, err) assert.Len(t, operation.RouterProperties, 2) assert.Equal(t, "/customer/get-wishlist/{wishlist_id}", operation.RouterProperties[0].Path) assert.Equal(t, "GET", operation.RouterProperties[0].HTTPMethod) assert.Equal(t, "/customer/get-the-wishlist/{wishlist_id}", operation.RouterProperties[1].Path) assert.Equal(t, "POST", operation.RouterProperties[1].HTTPMethod) } func TestParseRouterOnlySlash(t *testing.T) { t.Parallel() comment := `// @Router / [get]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Len(t, operation.RouterProperties, 1) assert.Equal(t, "/", operation.RouterProperties[0].Path) assert.Equal(t, "GET", operation.RouterProperties[0].HTTPMethod) } func TestParseRouterCommentWithPlusSign(t *testing.T) { t.Parallel() comment := `/@Router /customer/get-wishlist/{proxy+} [post]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Len(t, operation.RouterProperties, 1) assert.Equal(t, "/customer/get-wishlist/{proxy+}", operation.RouterProperties[0].Path) assert.Equal(t, "POST", operation.RouterProperties[0].HTTPMethod) } func TestParseRouterCommentWithDollarSign(t *testing.T) { t.Parallel() comment := `/@Router /customer/get-wishlist/{wishlist_id}$move [post]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Len(t, operation.RouterProperties, 1) assert.Equal(t, "/customer/get-wishlist/{wishlist_id}$move", operation.RouterProperties[0].Path) assert.Equal(t, "POST", operation.RouterProperties[0].HTTPMethod) } func TestParseRouterCommentWithParens(t *testing.T) { t.Parallel() comment := `/@Router /customer({id}) [get]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Len(t, operation.RouterProperties, 1) assert.Equal(t, "/customer({id})", operation.RouterProperties[0].Path) assert.Equal(t, "GET", operation.RouterProperties[0].HTTPMethod) } func TestParseRouterCommentNoDollarSignAtPathStartErr(t *testing.T) { t.Parallel() comment := `/@Router $customer/get-wishlist/{wishlist_id}$move [post]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseRouterCommentWithColonSign(t *testing.T) { t.Parallel() comment := `/@Router /customer/get-wishlist/{wishlist_id}:move [post]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Len(t, operation.RouterProperties, 1) assert.Equal(t, "/customer/get-wishlist/{wishlist_id}:move", operation.RouterProperties[0].Path) assert.Equal(t, "POST", operation.RouterProperties[0].HTTPMethod) } func TestParseRouterCommentNoColonSignAtPathStartErr(t *testing.T) { t.Parallel() comment := `/@Router :customer/get-wishlist/{wishlist_id}:move [post]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseRouterCommentMethodSeparationErr(t *testing.T) { t.Parallel() comment := `/@Router /api/{id}|,*[get` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseRouterCommentMethodMissingErr(t *testing.T) { t.Parallel() comment := `/@Router /customer/get-wishlist/{wishlist_id}` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.Error(t, err) } func TestOperation_ParseResponseWithDefault(t *testing.T) { t.Parallel() comment := `@Success default {object} nil "An empty response"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Equal(t, "An empty response", operation.Responses.Default.Description) comment = `@Success 200,default {string} Response "A response"` operation = NewOperation(nil) err = operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Equal(t, "A response", operation.Responses.Default.Description) assert.Equal(t, "A response", operation.Responses.StatusCodeResponses[200].Description) } func TestParseResponseSuccessCommentWithEmptyResponse(t *testing.T) { t.Parallel() comment := `@Success 200 {object} nil "An empty response"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `An empty response`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "An empty response" } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseFailureCommentWithEmptyResponse(t *testing.T) { t.Parallel() comment := `@Failure 500 {object} nil` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "500": { "description": "Internal Server Error" } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithObjectType(t *testing.T) { t.Parallel() comment := `@Success 200 {object} model.OrderRow "Error message, if code != 200` operation := NewOperation(nil) operation.parser.addTestType("model.OrderRow") err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "$ref": "#/definitions/model.OrderRow" } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithNestedPrimitiveType(t *testing.T) { t.Parallel() comment := `@Success 200 {object} model.CommonHeader{data=string,data2=int} "Error message, if code != 200` operation := NewOperation(nil) operation.parser.addTestType("model.CommonHeader") err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "allOf": [ { "$ref": "#/definitions/model.CommonHeader" }, { "type": "object", "properties": { "data": { "type": "string" }, "data2": { "type": "integer" } } } ] } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithNestedPrimitiveArrayType(t *testing.T) { t.Parallel() comment := `@Success 200 {object} model.CommonHeader{data=[]string,data2=[]int} "Error message, if code != 200` operation := NewOperation(nil) operation.parser.addTestType("model.CommonHeader") err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "allOf": [ { "$ref": "#/definitions/model.CommonHeader" }, { "type": "object", "properties": { "data": { "type": "array", "items": { "type": "string" } }, "data2": { "type": "array", "items": { "type": "integer" } } } } ] } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithNestedObjectType(t *testing.T) { t.Parallel() comment := `@Success 200 {object} model.CommonHeader{data=model.Payload,data2=model.Payload2} "Error message, if code != 200` operation := NewOperation(nil) operation.parser.addTestType("model.CommonHeader") operation.parser.addTestType("model.Payload") operation.parser.addTestType("model.Payload2") err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "allOf": [ { "$ref": "#/definitions/model.CommonHeader" }, { "type": "object", "properties": { "data": { "$ref": "#/definitions/model.Payload" }, "data2": { "$ref": "#/definitions/model.Payload2" } } } ] } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithNestedArrayObjectType(t *testing.T) { t.Parallel() comment := `@Success 200 {object} model.CommonHeader{data=[]model.Payload,data2=[]model.Payload2} "Error message, if code != 200` operation := NewOperation(nil) operation.parser.addTestType("model.CommonHeader") operation.parser.addTestType("model.Payload") operation.parser.addTestType("model.Payload2") err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "allOf": [ { "$ref": "#/definitions/model.CommonHeader" }, { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/definitions/model.Payload" } }, "data2": { "type": "array", "items": { "$ref": "#/definitions/model.Payload2" } } } } ] } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithNestedFields(t *testing.T) { t.Parallel() comment := `@Success 200 {object} model.CommonHeader{data1=int,data2=[]int,data3=model.Payload,data4=[]model.Payload} "Error message, if code != 200` operation := NewOperation(nil) operation.parser.addTestType("model.CommonHeader") operation.parser.addTestType("model.Payload") err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "allOf": [ { "$ref": "#/definitions/model.CommonHeader" }, { "type": "object", "properties": { "data1": { "type": "integer" }, "data2": { "type": "array", "items": { "type": "integer" } }, "data3": { "$ref": "#/definitions/model.Payload" }, "data4": { "type": "array", "items": { "$ref": "#/definitions/model.Payload" } } } } ] } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithDeepNestedFields(t *testing.T) { t.Parallel() comment := `@Success 200 {object} model.CommonHeader{data1=int,data2=[]int,data3=model.Payload{data1=int,data2=model.DeepPayload},data4=[]model.Payload{data1=[]int,data2=[]model.DeepPayload}} "Error message, if code != 200` operation := NewOperation(nil) operation.parser.addTestType("model.CommonHeader") operation.parser.addTestType("model.Payload") operation.parser.addTestType("model.DeepPayload") err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "allOf": [ { "$ref": "#/definitions/model.CommonHeader" }, { "type": "object", "properties": { "data1": { "type": "integer" }, "data2": { "type": "array", "items": { "type": "integer" } }, "data3": { "allOf": [ { "$ref": "#/definitions/model.Payload" }, { "type": "object", "properties": { "data1": { "type": "integer" }, "data2": { "$ref": "#/definitions/model.DeepPayload" } } } ] }, "data4": { "type": "array", "items": { "allOf": [ { "$ref": "#/definitions/model.Payload" }, { "type": "object", "properties": { "data1": { "type": "array", "items": { "type": "integer" } }, "data2": { "type": "array", "items": { "$ref": "#/definitions/model.DeepPayload" } } } } ] } } } } ] } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithNestedArrayMapFields(t *testing.T) { t.Parallel() comment := `@Success 200 {object} []map[string]model.CommonHeader{data1=[]map[string]model.Payload,data2=map[string][]int} "Error message, if code != 200` operation := NewOperation(nil) operation.parser.addTestType("model.CommonHeader") operation.parser.addTestType("model.Payload") err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "type": "array", "items": { "type": "object", "additionalProperties": { "allOf": [ { "$ref": "#/definitions/model.CommonHeader" }, { "type": "object", "properties": { "data1": { "type": "array", "items": { "type": "object", "additionalProperties": { "$ref": "#/definitions/model.Payload" } } }, "data2": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "integer" } } } } } ] } } } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithObjectTypeInSameFile(t *testing.T) { t.Parallel() comment := `@Success 200 {object} testOwner "Error message, if code != 200"` operation := NewOperation(nil) operation.parser.addTestType("swag.testOwner") fset := token.NewFileSet() astFile, err := goparser.ParseFile(fset, "operation_test.go", `package swag type testOwner struct { } `, goparser.ParseComments) assert.NoError(t, err) err = operation.ParseComment(comment, astFile) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "$ref": "#/definitions/swag.testOwner" } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithObjectTypeAnonymousField(t *testing.T) { // TODO: test Anonymous } func TestParseResponseCommentWithObjectTypeErr(t *testing.T) { t.Parallel() comment := `@Success 200 {object} model.OrderRow "Error message, if code != 200"` operation := NewOperation(nil) operation.parser.addTestType("model.notexist") err := operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseResponseCommentWithArrayType(t *testing.T) { t.Parallel() comment := `@Success 200 {array} model.OrderRow "Error message, if code != 200` operation := NewOperation(nil) operation.parser.addTestType("model.OrderRow") err := operation.ParseComment(comment, nil) assert.NoError(t, err) response := operation.Responses.StatusCodeResponses[200] assert.Equal(t, `Error message, if code != 200`, response.Description) assert.Equal(t, spec.StringOrArray{"array"}, response.Schema.Type) b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "Error message, if code != 200", "schema": { "type": "array", "items": { "$ref": "#/definitions/model.OrderRow" } } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithBasicType(t *testing.T) { t.Parallel() comment := `@Success 200 {string} string "it's ok'"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "it's ok'", "schema": { "type": "string" } } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithBasicTypeAndCodes(t *testing.T) { t.Parallel() comment := `@Success 200,201,default {string} string "it's ok"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "it's ok", "schema": { "type": "string" } }, "201": { "description": "it's ok", "schema": { "type": "string" } }, "default": { "description": "it's ok", "schema": { "type": "string" } } } }` assert.Equal(t, expected, string(b)) } func TestParseEmptyResponseComment(t *testing.T) { t.Parallel() comment := `@Success 200 "it is ok"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "it is ok" } } }` assert.Equal(t, expected, string(b)) } func TestParseEmptyResponseCommentWithCodes(t *testing.T) { t.Parallel() comment := `@Success 200,201,default "it is ok"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "it is ok" }, "201": { "description": "it is ok" }, "default": { "description": "it is ok" } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentWithHeader(t *testing.T) { t.Parallel() operation := NewOperation(nil) err := operation.ParseComment(`@Success 200 "it's ok"`, nil) assert.NoError(t, err, "ParseComment should not fail") err = operation.ParseComment(`@Header 200 {string} Token "qwerty"`, nil) assert.NoError(t, err, "ParseComment should not fail") b, err := json.MarshalIndent(operation, "", " ") assert.NoError(t, err) expected := `{ "responses": { "200": { "description": "it's ok", "headers": { "Token": { "type": "string", "description": "qwerty" } } } } }` assert.Equal(t, expected, string(b)) err = operation.ParseComment(`@Header 200 "Mallformed"`, nil) assert.Error(t, err, "ParseComment should not fail") err = operation.ParseComment(`@Header 200,asdsd {string} Token "qwerty"`, nil) assert.Error(t, err, "ParseComment should not fail") } func TestParseResponseCommentWithHeaderForCodes(t *testing.T) { t.Parallel() operation := NewOperation(nil) comment := `@Success 200,201,default "it's ok"` err := operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") comment = `@Header 200,201,default {string} Token "qwerty"` err = operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") comment = `@Header all {string} Token2 "qwerty"` err = operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") b, err := json.MarshalIndent(operation, "", " ") assert.NoError(t, err) expected := `{ "responses": { "200": { "description": "it's ok", "headers": { "Token": { "type": "string", "description": "qwerty" }, "Token2": { "type": "string", "description": "qwerty" } } }, "201": { "description": "it's ok", "headers": { "Token": { "type": "string", "description": "qwerty" }, "Token2": { "type": "string", "description": "qwerty" } } }, "default": { "description": "it's ok", "headers": { "Token": { "type": "string", "description": "qwerty" }, "Token2": { "type": "string", "description": "qwerty" } } } } }` assert.Equal(t, expected, string(b)) comment = `@Header 200 "Mallformed"` err = operation.ParseComment(comment, nil) assert.Error(t, err, "ParseComment should not fail") } func TestParseResponseCommentWithHeaderOnlyAll(t *testing.T) { t.Parallel() operation := NewOperation(nil) comment := `@Success 200,201,default "it's ok"` err := operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") comment = `@Header all {string} Token "qwerty"` err = operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") b, err := json.MarshalIndent(operation, "", " ") assert.NoError(t, err) expected := `{ "responses": { "200": { "description": "it's ok", "headers": { "Token": { "type": "string", "description": "qwerty" } } }, "201": { "description": "it's ok", "headers": { "Token": { "type": "string", "description": "qwerty" } } }, "default": { "description": "it's ok", "headers": { "Token": { "type": "string", "description": "qwerty" } } } } }` assert.Equal(t, expected, string(b)) comment = `@Header 200 "Mallformed"` err = operation.ParseComment(comment, nil) assert.Error(t, err, "ParseComment should not fail") } func TestParseEmptyResponseOnlyCode(t *testing.T) { t.Parallel() operation := NewOperation(nil) err := operation.ParseComment(`@Success 200`, nil) assert.NoError(t, err, "ParseComment should not fail") b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "OK" } } }` assert.Equal(t, expected, string(b)) } func TestParseEmptyResponseOnlyCodes(t *testing.T) { t.Parallel() comment := `@Success 200,201,default` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err, "ParseComment should not fail") b, _ := json.MarshalIndent(operation, "", " ") expected := `{ "responses": { "200": { "description": "OK" }, "201": { "description": "Created" }, "default": { "description": "" } } }` assert.Equal(t, expected, string(b)) } func TestParseResponseCommentParamMissing(t *testing.T) { t.Parallel() operation := NewOperation(nil) paramLenErrComment := `@Success notIntCode` paramLenErr := operation.ParseComment(paramLenErrComment, nil) assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode"`) paramLenErrComment = `@Success notIntCode {string} string "it ok"` paramLenErr = operation.ParseComment(paramLenErrComment, nil) assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode {string} string "it ok""`) paramLenErrComment = `@Success notIntCode "it ok"` paramLenErr = operation.ParseComment(paramLenErrComment, nil) assert.EqualError(t, paramLenErr, `can not parse response comment "notIntCode "it ok""`) } func TestOperation_ParseParamComment(t *testing.T) { t.Parallel() t.Run("integer", func(t *testing.T) { t.Parallel() for _, paramType := range []string{"header", "path", "query", "formData"} { t.Run(paramType, func(t *testing.T) { o := NewOperation(nil) err := o.ParseComment(`@Param some_id `+paramType+` int true "Some ID"`, nil) assert.NoError(t, err) assert.Equal(t, o.Parameters, []spec.Parameter{{ SimpleSchema: spec.SimpleSchema{ Type: "integer", }, ParamProps: spec.ParamProps{ Name: "some_id", Description: "Some ID", In: paramType, Required: true, }, }}) }) } }) t.Run("string", func(t *testing.T) { t.Parallel() for _, paramType := range []string{"header", "path", "query", "formData"} { t.Run(paramType, func(t *testing.T) { o := NewOperation(nil) err := o.ParseComment(`@Param some_string `+paramType+` string true "Some String"`, nil) assert.NoError(t, err) assert.Equal(t, o.Parameters, []spec.Parameter{{ SimpleSchema: spec.SimpleSchema{ Type: "string", }, ParamProps: spec.ParamProps{ Name: "some_string", Description: "Some String", In: paramType, Required: true, }, }}) }) } }) t.Run("object", func(t *testing.T) { t.Parallel() for _, paramType := range []string{"header", "path", "query", "formData"} { t.Run(paramType, func(t *testing.T) { // unknown object returns error assert.Error(t, NewOperation(nil).ParseComment(`@Param some_object `+paramType+` main.Object true "Some Object"`, nil)) // verify objects are supported here o := NewOperation(nil) o.parser.addTestType("main.TestObject") err := o.ParseComment(`@Param some_object `+paramType+` main.TestObject true "Some Object"`, nil) assert.NoError(t, err) }) } }) } // Test ParseParamComment Query Params func TestParseParamCommentBodyArray(t *testing.T) { t.Parallel() comment := `@Param names body []string true "Users List"` o := NewOperation(nil) err := o.ParseComment(comment, nil) assert.NoError(t, err) assert.Equal(t, o.Parameters, []spec.Parameter{{ ParamProps: spec.ParamProps{ Name: "names", Description: "Users List", In: "body", Required: true, Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Type: []string{"string"}, }, }, }, }, }, }, }}) } // Test ParseParamComment Params func TestParseParamCommentArray(t *testing.T) { paramTypes := []string{"header", "path", "query"} for _, paramType := range paramTypes { t.Run(paramType, func(t *testing.T) { operation := NewOperation(nil) err := operation.ParseComment(`@Param names `+paramType+` []string true "Users List"`, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "array", "items": { "type": "string" }, "description": "Users List", "name": "names", "in": "` + paramType + `", "required": true } ]` assert.Equal(t, expected, string(b)) err = operation.ParseComment(`@Param names `+paramType+` []model.User true "Users List"`, nil) assert.Error(t, err) }) } } // Test TestParseParamCommentDefaultValue Query Params func TestParseParamCommentDefaultValue(t *testing.T) { t.Parallel() operation := NewOperation(nil) err := operation.ParseComment(`@Param names query string true "Users List" default(test)`, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "string", "default": "test", "description": "Users List", "name": "names", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) } // Test ParseParamComment Query Params func TestParseParamCommentQueryArrayFormat(t *testing.T) { t.Parallel() comment := `@Param names query []string true "Users List" collectionFormat(multi)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "array", "items": { "type": "string" }, "collectionFormat": "multi", "description": "Users List", "name": "names", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByID(t *testing.T) { t.Parallel() comment := `@Param unsafe_id[lte] query int true "Unsafe query param"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "integer", "description": "Unsafe query param", "name": "unsafe_id[lte]", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentWithMultilineDescriptions(t *testing.T) { t.Parallel() comment := `@Param some_id query int true "First line\nSecond line\nThird line"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "integer", "description": "First line\nSecond line\nThird line", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByQueryType(t *testing.T) { t.Parallel() comment := `@Param some_id query int true "Some ID"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "integer", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByBodyType(t *testing.T) { t.Parallel() comment := `@Param some_id body model.OrderRow true "Some ID"` operation := NewOperation(nil) operation.parser.addTestType("model.OrderRow") err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "description": "Some ID", "name": "some_id", "in": "body", "required": true, "schema": { "$ref": "#/definitions/model.OrderRow" } } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByBodyTextPlain(t *testing.T) { t.Parallel() comment := `@Param text body string true "Text to process"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "description": "Text to process", "name": "text", "in": "body", "required": true, "schema": { "type": "string" } } ]` assert.Equal(t, expected, string(b)) } // TODO: fix this func TestParseParamCommentByBodyEnumsText(t *testing.T) { t.Parallel() comment := `@Param text body string true "description" Enums(ENUM1, ENUM2, ENUM3)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "description": "description", "name": "text", "in": "body", "required": true, "schema": { "type": "string", "enum": [ "ENUM1", "ENUM2", "ENUM3" ] } } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByBodyTypeWithDeepNestedFields(t *testing.T) { t.Parallel() comment := `@Param body body model.CommonHeader{data=string,data2=int} true "test deep"` operation := NewOperation(nil) operation.parser.addTestType("model.CommonHeader") err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Len(t, operation.Parameters, 1) assert.Equal(t, "test deep", operation.Parameters[0].Description) assert.True(t, operation.Parameters[0].Required) b, err := json.MarshalIndent(operation.Parameters, "", " ") assert.NoError(t, err) expected := `[ { "description": "test deep", "name": "body", "in": "body", "required": true, "schema": { "allOf": [ { "$ref": "#/definitions/model.CommonHeader" }, { "type": "object", "properties": { "data": { "type": "string" }, "data2": { "type": "integer" } } } ] } } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByBodyTypeArrayOfPrimitiveGo(t *testing.T) { t.Parallel() comment := `@Param some_id body []int true "Some ID"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "description": "Some ID", "name": "some_id", "in": "body", "required": true, "schema": { "type": "array", "items": { "type": "integer" } } } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByBodyTypeArrayOfPrimitiveGoWithDeepNestedFields(t *testing.T) { t.Parallel() comment := `@Param body body []model.CommonHeader{data=string,data2=int} true "test deep"` operation := NewOperation(nil) operation.parser.addTestType("model.CommonHeader") err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Len(t, operation.Parameters, 1) assert.Equal(t, "test deep", operation.Parameters[0].Description) assert.True(t, operation.Parameters[0].Required) b, err := json.MarshalIndent(operation.Parameters, "", " ") assert.NoError(t, err) expected := `[ { "description": "test deep", "name": "body", "in": "body", "required": true, "schema": { "type": "array", "items": { "allOf": [ { "$ref": "#/definitions/model.CommonHeader" }, { "type": "object", "properties": { "data": { "type": "string" }, "data2": { "type": "integer" } } } ] } } } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByBodyTypeErr(t *testing.T) { t.Parallel() comment := `@Param some_id body model.OrderRow true "Some ID"` operation := NewOperation(nil) operation.parser.addTestType("model.notexist") err := operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseParamCommentByFormDataType(t *testing.T) { t.Parallel() comment := `@Param file formData file true "this is a test file"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "file", "description": "this is a test file", "name": "file", "in": "formData", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByFormDataTypeUint64(t *testing.T) { t.Parallel() comment := `@Param file formData uint64 true "this is a test file"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "integer", "format": "int64", "description": "this is a test file", "name": "file", "in": "formData", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByNotSupportedType(t *testing.T) { t.Parallel() comment := `@Param some_id not_supported int true "Some ID"` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseParamCommentNotMatch(t *testing.T) { t.Parallel() comment := `@Param some_id body mock true` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseParamCommentByEnums(t *testing.T) { t.Parallel() comment := `@Param some_id query string true "Some ID" Enums(A, B, C)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "enum": [ "A", "B", "C" ], "type": "string", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) comment = `@Param some_id query int true "Some ID" Enums(1, 2, 3)` operation = NewOperation(nil) err = operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ = json.MarshalIndent(operation.Parameters, "", " ") expected = `[ { "enum": [ 1, 2, 3 ], "type": "integer", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) comment = `@Param some_id query number true "Some ID" Enums(1.1, 2.2, 3.3)` operation = NewOperation(nil) err = operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ = json.MarshalIndent(operation.Parameters, "", " ") expected = `[ { "enum": [ 1.1, 2.2, 3.3 ], "type": "number", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) comment = `@Param some_id query bool true "Some ID" Enums(true, false)` operation = NewOperation(nil) err = operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ = json.MarshalIndent(operation.Parameters, "", " ") expected = `[ { "enum": [ true, false ], "type": "boolean", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) operation = NewOperation(nil) comment = `@Param some_id query int true "Some ID" Enums(A, B, C)` assert.Error(t, operation.ParseComment(comment, nil)) comment = `@Param some_id query number true "Some ID" Enums(A, B, C)` assert.Error(t, operation.ParseComment(comment, nil)) comment = `@Param some_id query boolean true "Some ID" Enums(A, B, C)` assert.Error(t, operation.ParseComment(comment, nil)) comment = `@Param some_id query Document true "Some ID" Enums(A, B, C)` assert.Error(t, operation.ParseComment(comment, nil)) } func TestParseParamCommentByMaxLength(t *testing.T) { t.Parallel() comment := `@Param some_id query string true "Some ID" MaxLength(10)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "maxLength": 10, "type": "string", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) comment = `@Param some_id query int true "Some ID" MaxLength(10)` assert.Error(t, operation.ParseComment(comment, nil)) comment = `@Param some_id query string true "Some ID" MaxLength(Goopher)` assert.Error(t, operation.ParseComment(comment, nil)) } func TestParseParamCommentByMinLength(t *testing.T) { t.Parallel() comment := `@Param some_id query string true "Some ID" MinLength(10)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "minLength": 10, "type": "string", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) comment = `@Param some_id query int true "Some ID" MinLength(10)` assert.Error(t, operation.ParseComment(comment, nil)) comment = `@Param some_id query string true "Some ID" MinLength(Goopher)` assert.Error(t, operation.ParseComment(comment, nil)) } func TestParseParamCommentByMinimum(t *testing.T) { t.Parallel() comment := `@Param some_id query int true "Some ID" Minimum(10)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "minimum": 10, "type": "integer", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) comment = `@Param some_id query int true "Some ID" Mininum(10)` assert.NoError(t, operation.ParseComment(comment, nil)) comment = `@Param some_id query string true "Some ID" Minimum(10)` assert.Error(t, operation.ParseComment(comment, nil)) comment = `@Param some_id query integer true "Some ID" Minimum(Goopher)` assert.Error(t, operation.ParseComment(comment, nil)) } func TestParseParamCommentByMaximum(t *testing.T) { t.Parallel() comment := `@Param some_id query int true "Some ID" Maximum(10)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "maximum": 10, "type": "integer", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) comment = `@Param some_id query int true "Some ID" Maxinum(10)` assert.NoError(t, operation.ParseComment(comment, nil)) comment = `@Param some_id query string true "Some ID" Maximum(10)` assert.Error(t, operation.ParseComment(comment, nil)) comment = `@Param some_id query integer true "Some ID" Maximum(Goopher)` assert.Error(t, operation.ParseComment(comment, nil)) } func TestParseParamCommentByDefault(t *testing.T) { t.Parallel() comment := `@Param some_id query int true "Some ID" Default(10)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "integer", "default": 10, "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByExampleInt(t *testing.T) { t.Parallel() comment := `@Param some_id query int true "Some ID" Example(10)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "integer", "example": 10, "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByExampleString(t *testing.T) { t.Parallel() comment := `@Param some_id query string true "Some ID" Example(True feelings)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "string", "example": "True feelings", "description": "Some ID", "name": "some_id", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentByExampleUnsupportedType(t *testing.T) { t.Parallel() var param spec.Parameter setExample(¶m, "something", "random value") assert.Equal(t, param.Example, nil) setExample(¶m, STRING, "string value") assert.Equal(t, param.Example, "string value") setExample(¶m, INTEGER, "10") assert.Equal(t, param.Example, 10) setExample(¶m, NUMBER, "10") assert.Equal(t, param.Example, float64(10)) } func TestParseParamCommentBySchemaExampleString(t *testing.T) { t.Parallel() comment := `@Param some_id body string true "Some ID" SchemaExample(True feelings)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "description": "Some ID", "name": "some_id", "in": "body", "required": true, "schema": { "type": "string", "example": "True feelings" } } ]` assert.Equal(t, expected, string(b)) } func TestParseParamCommentBySchemaExampleUnsupportedType(t *testing.T) { t.Parallel() var param spec.Parameter setSchemaExample(¶m, "something", "random value") assert.Nil(t, param.Schema) setSchemaExample(¶m, STRING, "string value") assert.Nil(t, param.Schema) param.Schema = &spec.Schema{} setSchemaExample(¶m, STRING, "string value") assert.Equal(t, "string value", param.Schema.Example) setSchemaExample(¶m, INTEGER, "10") assert.Equal(t, 10, param.Schema.Example) setSchemaExample(¶m, NUMBER, "10") assert.Equal(t, float64(10), param.Schema.Example) setSchemaExample(¶m, STRING, "string \\r\\nvalue") assert.Equal(t, "string \r\nvalue", param.Schema.Example) } func TestParseParamArrayWithEnums(t *testing.T) { t.Parallel() comment := `@Param field query []string true "An enum collection" collectionFormat(csv) enums(also,valid)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "array", "items": { "enum": [ "also", "valid" ], "type": "string" }, "collectionFormat": "csv", "description": "An enum collection", "name": "field", "in": "query", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseAndExtractionParamAttribute(t *testing.T) { t.Parallel() op := NewOperation(nil) numberParam := spec.Parameter{} err := op.parseParamAttribute( " default(1) maximum(100) minimum(0) format(csv)", "", NUMBER, "", &numberParam, ) assert.NoError(t, err) assert.Equal(t, float64(0), *numberParam.Minimum) assert.Equal(t, float64(100), *numberParam.Maximum) assert.Equal(t, "csv", numberParam.SimpleSchema.Format) assert.Equal(t, float64(1), numberParam.Default) err = op.parseParamAttribute(" minlength(1)", "", NUMBER, "", nil) assert.Error(t, err) err = op.parseParamAttribute(" maxlength(1)", "", NUMBER, "", nil) assert.Error(t, err) stringParam := spec.Parameter{} err = op.parseParamAttribute( " default(test) maxlength(100) minlength(0) format(csv)", "", STRING, "", &stringParam, ) assert.NoError(t, err) assert.Equal(t, int64(0), *stringParam.MinLength) assert.Equal(t, int64(100), *stringParam.MaxLength) assert.Equal(t, "csv", stringParam.SimpleSchema.Format) err = op.parseParamAttribute(" minimum(0)", "", STRING, "", nil) assert.Error(t, err) err = op.parseParamAttribute(" maximum(0)", "", STRING, "", nil) assert.Error(t, err) arrayParram := spec.Parameter{} err = op.parseParamAttribute(" collectionFormat(tsv)", ARRAY, STRING, "", &arrayParram) assert.Equal(t, "tsv", arrayParram.CollectionFormat) assert.NoError(t, err) err = op.parseParamAttribute(" collectionFormat(tsv)", STRING, STRING, "", nil) assert.Error(t, err) err = op.parseParamAttribute(" default(0)", "", ARRAY, "", nil) assert.NoError(t, err) } func TestParseParamCommentByExtensions(t *testing.T) { comment := `@Param some_id path int true "Some ID" extensions(x-example=test,x-custom=Goopher,x-custom2)` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation.Parameters, "", " ") expected := `[ { "type": "integer", "x-custom": "Goopher", "x-custom2": true, "x-example": "test", "description": "Some ID", "name": "some_id", "in": "path", "required": true } ]` assert.Equal(t, expected, string(b)) } func TestParseParamStructCodeExample(t *testing.T) { t.Parallel() fset := token.NewFileSet() ast, err := goparser.ParseFile(fset, "operation_test.go", `package swag import structs "git.ipao.vip/rogeecn/atomctl/pkg/swag/testdata/param_structs" `, goparser.ParseComments) assert.NoError(t, err) parser := New() err = parser.parseFile("git.ipao.vip/rogeecn/atomctl/pkg/swag/testdata/param_structs", "testdata/param_structs/structs.go", nil, ParseModels) assert.NoError(t, err) _, err = parser.packages.ParseTypes() assert.NoError(t, err) validateParameters := func(operation *Operation, params ...spec.Parameter) { assert.Equal(t, len(params), len(operation.Parameters)) for _, param := range params { found := false for _, p := range operation.Parameters { if p.Name == param.Name { assert.Equal(t, param.ParamProps, p.ParamProps) assert.Equal(t, param.CommonValidations, p.CommonValidations) assert.Equal(t, param.SimpleSchema, p.SimpleSchema) found = true break } } assert.True(t, found, "found parameter %s", param.Name) } } // values used in validation checks max := float64(10) maxLen := int64(10) min := float64(0) // query and form behave the same for _, param := range []string{"query", "formData"} { t.Run(param+" struct", func(t *testing.T) { operation := NewOperation(parser) comment := fmt.Sprintf(`@Param model %s structs.FormModel true "query params"`, param) err = operation.ParseComment(comment, ast) assert.NoError(t, err) validateParameters(operation, spec.Parameter{ ParamProps: spec.ParamProps{ Name: "f", Description: "", In: param, Required: true, }, CommonValidations: spec.CommonValidations{ MaxLength: &maxLen, }, SimpleSchema: spec.SimpleSchema{ Type: "string", }, }, spec.Parameter{ ParamProps: spec.ParamProps{ Name: "b", Description: "B is another field", In: param, }, SimpleSchema: spec.SimpleSchema{ Type: "boolean", }, }) }) } t.Run("header struct", func(t *testing.T) { operation := NewOperation(parser) comment := `@Param auth header structs.AuthHeader true "auth header"` err = operation.ParseComment(comment, ast) assert.NoError(t, err) validateParameters(operation, spec.Parameter{ ParamProps: spec.ParamProps{ Name: "X-Auth-Token", Description: "Token is the auth token", In: "header", Required: true, }, SimpleSchema: spec.SimpleSchema{ Type: "string", }, }, spec.Parameter{ ParamProps: spec.ParamProps{ Name: "anotherHeader", Description: "AnotherHeader is another header", In: "header", }, CommonValidations: spec.CommonValidations{ Maximum: &max, Minimum: &min, }, SimpleSchema: spec.SimpleSchema{ Type: "integer", }, }) }) t.Run("path struct", func(t *testing.T) { operation := NewOperation(parser) comment := `@Param path path structs.PathModel true "path params"` err = operation.ParseComment(comment, ast) assert.NoError(t, err) validateParameters(operation, spec.Parameter{ ParamProps: spec.ParamProps{ Name: "id", Description: "ID is the id", In: "path", Required: true, }, SimpleSchema: spec.SimpleSchema{ Type: "integer", }, }, spec.Parameter{ ParamProps: spec.ParamProps{ Name: "name", Description: "", In: "path", }, CommonValidations: spec.CommonValidations{ MaxLength: &maxLen, }, SimpleSchema: spec.SimpleSchema{ Type: "string", }, }) }) } func TestParseIdComment(t *testing.T) { t.Parallel() comment := `@Id myOperationId` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Equal(t, "myOperationId", operation.ID) } func TestFindTypeDefCoreLib(t *testing.T) { t.Parallel() s, err := findTypeDef("net/http", "Request") assert.NoError(t, err) assert.NotNil(t, s) } func TestFindTypeDefExternalPkg(t *testing.T) { t.Parallel() s, err := findTypeDef("github.com/KyleBanks/depth", "Tree") assert.NoError(t, err) assert.NotNil(t, s) } func TestFindTypeDefInvalidPkg(t *testing.T) { t.Parallel() s, err := findTypeDef("does-not-exist", "foo") assert.Error(t, err) assert.Nil(t, s) } func TestParseSecurityComment(t *testing.T) { t.Parallel() comment := `@Security OAuth2Implicit[read, write]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Equal(t, operation.Security, []map[string][]string{ { "OAuth2Implicit": {"read", "write"}, }, }) } func TestParseSecurityCommentSimple(t *testing.T) { t.Parallel() comment := `@Security ApiKeyAuth` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Equal(t, operation.Security, []map[string][]string{ { "ApiKeyAuth": {}, }, }) } func TestParseSecurityCommentAnd(t *testing.T) { t.Parallel() comment := `@Security OAuth2Implicit[read, write] && Firebase[]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) expect := []map[string][]string{ { "OAuth2Implicit": {"read", "write"}, "Firebase": {""}, }, } assert.Equal(t, operation.Security, expect) oldVersionComment := `@Security OAuth2Implicit[read, write] || Firebase[]` operation = NewOperation(nil) err = operation.ParseComment(oldVersionComment, nil) assert.NoError(t, err) assert.Equal(t, operation.Security, expect) } func TestParseMultiDescription(t *testing.T) { t.Parallel() comment := `@Description line one` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) comment = `@Tags multi` err = operation.ParseComment(comment, nil) assert.NoError(t, err) comment = `@Description line two x` err = operation.ParseComment(comment, nil) assert.NoError(t, err) b, _ := json.MarshalIndent(operation, "", " ") expected := `"description": "line one\nline two x"` assert.Contains(t, string(b), expected) } func TestParseDescriptionMarkdown(t *testing.T) { t.Parallel() operation := NewOperation(nil) operation.parser.markdownFileDir = "example/markdown" comment := `@description.markdown admin.md` err := operation.ParseComment(comment, nil) assert.NoError(t, err) comment = `@description.markdown missing.md` err = operation.ParseComment(comment, nil) assert.Error(t, err) } func TestParseSummary(t *testing.T) { t.Parallel() comment := `@summary line one` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) comment = `@Summary line one` err = operation.ParseComment(comment, nil) assert.NoError(t, err) } func TestParseDeprecationDescription(t *testing.T) { t.Parallel() comment := `@Deprecated` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) if !operation.Deprecated { t.Error("Failed to parse @deprecated comment") } } func TestParseExtentions(t *testing.T) { t.Parallel() // Fail if there are no args for attributes. { comment := `@x-amazon-apigateway-integration` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.EqualError(t, err, "annotation @x-amazon-apigateway-integration need a value") } // Fail if args of attributes are broken. { comment := `@x-amazon-apigateway-integration ["broken"}]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.EqualError(t, err, "annotation @x-amazon-apigateway-integration need a valid json value") } // OK { comment := `@x-amazon-apigateway-integration {"uri": "${some_arn}", "passthroughBehavior": "when_no_match", "httpMethod": "POST", "type": "aws_proxy"}` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Equal(t, operation.Extensions["x-amazon-apigateway-integration"], map[string]interface{}{ "httpMethod": "POST", "passthroughBehavior": "when_no_match", "type": "aws_proxy", "uri": "${some_arn}", }) } // Test x-tagGroups { comment := `@x-tagGroups [{"name":"Natural Persons","tags":["Person","PersonRisk","PersonDocuments"]}]` operation := NewOperation(nil) err := operation.ParseComment(comment, nil) assert.NoError(t, err) assert.Equal(t, operation.Extensions["x-tagGroups"], []interface{}{map[string]interface{}{ "name": "Natural Persons", "tags": []interface{}{ "Person", "PersonRisk", "PersonDocuments", }, }}) } } func TestFindInSlice(t *testing.T) { t.Parallel() assert.True(t, findInSlice([]string{"one", "two", "tree"}, "one")) assert.True(t, findInSlice([]string{"tree", "two", "one"}, "one")) assert.True(t, findInSlice([]string{"two", "one", "tree"}, "one")) assert.False(t, findInSlice([]string{"one", "two", "tree"}, "four")) } func TestParseResponseHeaderComment(t *testing.T) { t.Parallel() operation := NewOperation(nil) operation.Responses = &spec.Responses{} err := operation.ParseResponseComment(`default {string} string "other error"`, nil) assert.NoError(t, err) err = operation.ParseResponseHeaderComment(`all {string} Token "qwerty"`, nil) assert.NoError(t, err) } func TestParseObjectSchema(t *testing.T) { t.Parallel() operation := NewOperation(nil) schema, err := operation.parseObjectSchema("interface{}", nil) assert.NoError(t, err) assert.Equal(t, schema, &spec.Schema{}) schema, err = operation.parseObjectSchema("any", nil) assert.NoError(t, err) assert.Equal(t, schema, &spec.Schema{}) schema, err = operation.parseObjectSchema("any{data=string}", nil) assert.NoError(t, err) assert.Equal(t, schema, (&spec.Schema{}).WithAllOf(spec.Schema{}, *PrimitiveSchema(OBJECT).SetProperty("data", *PrimitiveSchema("string")))) schema, err = operation.parseObjectSchema("int", nil) assert.NoError(t, err) assert.Equal(t, schema, PrimitiveSchema(INTEGER)) schema, err = operation.parseObjectSchema("[]string", nil) assert.NoError(t, err) assert.Equal(t, schema, spec.ArrayProperty(PrimitiveSchema(STRING))) schema, err = operation.parseObjectSchema("[]int", nil) assert.NoError(t, err) assert.Equal(t, schema, spec.ArrayProperty(PrimitiveSchema(INTEGER))) _, err = operation.parseObjectSchema("[]bleah", nil) assert.Error(t, err) schema, err = operation.parseObjectSchema("map[]string", nil) assert.NoError(t, err) assert.Equal(t, schema, spec.MapProperty(PrimitiveSchema(STRING))) schema, err = operation.parseObjectSchema("map[]int", nil) assert.NoError(t, err) assert.Equal(t, schema, spec.MapProperty(PrimitiveSchema(INTEGER))) schema, err = operation.parseObjectSchema("map[]interface{}", nil) assert.NoError(t, err) assert.Equal(t, schema, spec.MapProperty(nil)) _, err = operation.parseObjectSchema("map[string", nil) assert.Error(t, err) _, err = operation.parseObjectSchema("map[]bleah", nil) assert.Error(t, err) operation.parser = New() operation.parser.packages = &PackagesDefinitions{ uniqueDefinitions: map[string]*TypeSpecDef{ "model.User": { File: &ast.File{ Name: &ast.Ident{ Name: "user.go", }, }, TypeSpec: &ast.TypeSpec{ Name: &ast.Ident{ Name: "User", }, }, }, }, } _, err = operation.parseObjectSchema("model.User", nil) assert.NoError(t, err) operation.parser = nil schema, err = operation.parseObjectSchema("user.Model", nil) assert.NoError(t, err) assert.Equal(t, schema, RefSchema("user.Model")) } func TestParseCodeSamples(t *testing.T) { t.Parallel() const comment = `@x-codeSamples file` t.Run("Find sample by file", func(t *testing.T) { operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples")) operation.Summary = "example" err := operation.ParseComment(comment, nil) assert.NoError(t, err, "no error should be thrown") assert.Equal(t, operation.Summary, "example") assert.Equal(t, operation.Extensions["x-codeSamples"], map[string]interface{}{"lang": "JavaScript", "source": "console.log('Hello World');"}) }) t.Run("With broken file sample", func(t *testing.T) { operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples")) operation.Summary = "broken" err := operation.ParseComment(comment, nil) assert.Error(t, err, "no error should be thrown") }) t.Run("Example file not found", func(t *testing.T) { operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples")) operation.Summary = "badExample" err := operation.ParseComment(comment, nil) assert.Error(t, err, "error was expected, as file does not exist") }) t.Run("Without line reminder", func(t *testing.T) { comment := `@x-codeSamples` operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/code_examples")) operation.Summary = "example" err := operation.ParseComment(comment, nil) assert.Error(t, err, "no error should be thrown") }) t.Run(" broken dir", func(t *testing.T) { operation := NewOperation(nil, SetCodeExampleFilesDirectory("testdata/fake_examples")) operation.Summary = "code" err := operation.ParseComment(comment, nil) assert.Error(t, err, "no error should be thrown") }) } func TestParseDeprecatedRouter(t *testing.T) { p := New() searchDir := "./testdata/deprecated_router" if err := p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth); err != nil { t.Error("Failed to parse api: " + err.Error()) } b, _ := json.MarshalIndent(p.swagger, "", " ") expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json")) assert.NoError(t, err) assert.Equal(t, expected, b) }