github.com/AngusLu/go-swagger@v0.28.0/scan/scanner_test.go (about)

     1  // +build !go1.11
     2  
     3  // Copyright 2015 go-swagger maintainers
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //    http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package scan
    18  
    19  import (
    20  	"fmt"
    21  	"go/ast"
    22  	goparser "go/parser"
    23  	"io/ioutil"
    24  	"log"
    25  	"os"
    26  	"regexp"
    27  	"sort"
    28  	"strings"
    29  	"testing"
    30  
    31  	"github.com/go-openapi/loads"
    32  	"github.com/go-openapi/spec"
    33  	"github.com/go-openapi/strfmt"
    34  	"github.com/go-openapi/validate"
    35  	"github.com/stretchr/testify/assert"
    36  	"golang.org/x/tools/go/loader"
    37  
    38  	_ "github.com/go-swagger/scan-repo-boundary/makeplans"
    39  )
    40  
    41  var classificationProg *loader.Program
    42  var noModelDefs map[string]spec.Schema
    43  
    44  type namedParam struct {
    45  	Index int
    46  	Name  string
    47  }
    48  
    49  type namedParams []namedParam
    50  
    51  func (g namedParams) Len() int           { return len(g) }
    52  func (g namedParams) Swap(i, j int)      { g[i], g[j] = g[j], g[i] }
    53  func (g namedParams) Less(i, j int) bool { return g[i].Name < g[j].Name }
    54  
    55  func init() {
    56  	classificationProg = classifierProgram()
    57  	docFile := "../fixtures/goparsing/classification/models/nomodel.go"
    58  	fileTree, err := goparser.ParseFile(classificationProg.Fset, docFile, nil, goparser.ParseComments)
    59  	if err != nil {
    60  		log.Fatal(err)
    61  	}
    62  	sp := newSchemaParser(classificationProg)
    63  	noModelDefs = make(map[string]spec.Schema)
    64  	err = sp.Parse(fileTree, noModelDefs)
    65  	if err != nil {
    66  		log.Fatal(err)
    67  	}
    68  }
    69  
    70  // only used within this group of tests but never used within actual code base.
    71  func newSchemaAnnotationParser(goName string) *schemaAnnotationParser {
    72  	return &schemaAnnotationParser{GoName: goName, rx: rxModelOverride}
    73  }
    74  
    75  type schemaAnnotationParser struct {
    76  	GoName string
    77  	Name   string
    78  	rx     *regexp.Regexp
    79  }
    80  
    81  func (sap *schemaAnnotationParser) Matches(line string) bool {
    82  	return sap.rx.MatchString(line)
    83  }
    84  
    85  func (sap *schemaAnnotationParser) Parse(lines []string) error {
    86  	if sap.Name != "" {
    87  		return nil
    88  	}
    89  
    90  	if len(lines) > 0 {
    91  		for _, line := range lines {
    92  			matches := sap.rx.FindStringSubmatch(line)
    93  			if len(matches) > 1 && len(matches[1]) > 0 {
    94  				sap.Name = matches[1]
    95  				return nil
    96  			}
    97  		}
    98  	}
    99  	return nil
   100  }
   101  
   102  func extraModelsClassifier(t testing.TB) (*loader.Program, map[string]spec.Schema) {
   103  	prog := classifierProgram()
   104  	docFile := "../fixtures/goparsing/classification/models/extranomodel.go"
   105  	fileTree, err := goparser.ParseFile(prog.Fset, docFile, nil, goparser.ParseComments)
   106  	if err != nil {
   107  		t.Fatal(err)
   108  	}
   109  	sp := newSchemaParser(prog)
   110  	defs := make(map[string]spec.Schema)
   111  	err = sp.Parse(fileTree, defs)
   112  	if err != nil {
   113  		t.Fatal(err)
   114  	}
   115  	return prog, defs
   116  }
   117  
   118  func TestAppScanner_NewSpec(t *testing.T) {
   119  	scanner, err := newAppScanner(&Opts{BasePath: "../fixtures/goparsing/petstore/petstore-fixture"})
   120  	assert.NoError(t, err)
   121  	assert.NotNil(t, scanner)
   122  	doc, err := scanner.Parse()
   123  	assert.NoError(t, err)
   124  	if assert.NotNil(t, doc) {
   125  		verifyParsedPetStore(t, doc)
   126  	}
   127  }
   128  
   129  func TestAppScanner_Definitions(t *testing.T) {
   130  	scanner, err := newAppScanner(&Opts{BasePath: "../fixtures/goparsing/bookings"})
   131  	assert.NoError(t, err)
   132  	assert.NotNil(t, scanner)
   133  	doc, err := scanner.Parse()
   134  	assert.NoError(t, err)
   135  	if assert.NotNil(t, doc) {
   136  		_, ok := doc.Definitions["Booking"]
   137  		assert.True(t, ok, "Should include cross repo structs")
   138  		_, ok = doc.Definitions["Customer"]
   139  		assert.True(t, ok, "Should include package structs with swagger:model")
   140  		_, ok = doc.Definitions["DateRange"]
   141  		assert.True(t, ok, "Should include package structs that are used in responses")
   142  		_, ok = doc.Definitions["BookingResponse"]
   143  		assert.False(t, ok, "Should not include responses")
   144  		_, ok = doc.Definitions["IgnoreMe"]
   145  		assert.False(t, ok, "Should not include un-annotated/un-referenced structs")
   146  	}
   147  }
   148  
   149  func verifyParsedPetStore(t testing.TB, doc *spec.Swagger) {
   150  	assert.EqualValues(t, []string{"application/json"}, doc.Consumes)
   151  	assert.EqualValues(t, []string{"application/json"}, doc.Produces)
   152  	assert.EqualValues(t, []string{"http", "https"}, doc.Schemes)
   153  	assert.Equal(t, "localhost", doc.Host)
   154  	assert.Equal(t, "/v2", doc.BasePath)
   155  
   156  	verifyInfo(t, doc.Info)
   157  
   158  	if assert.NotNil(t, doc.Paths) {
   159  		assert.Len(t, doc.Paths.Paths, 4)
   160  	}
   161  	var keys []string
   162  	for k := range doc.Definitions {
   163  		keys = append(keys, k)
   164  	}
   165  	assert.Len(t, keys, 3)
   166  	assert.Len(t, doc.Responses, 3)
   167  
   168  	definitions := doc.Definitions
   169  	mod, ok := definitions["tag"]
   170  	assert.True(t, ok)
   171  	assert.Equal(t, spec.StringOrArray([]string{"object"}), mod.Type)
   172  	assert.Equal(t, "A Tag is an extra piece of data to provide more information about a pet.", mod.Title)
   173  	assert.Equal(t, "It is used to describe the animals available in the store.", mod.Description)
   174  	assert.Len(t, mod.Required, 2)
   175  
   176  	assertProperty(t, &mod, "integer", "id", "int64", "ID")
   177  	prop, ok := mod.Properties["id"]
   178  	assert.True(t, ok, "should have had an 'id' property")
   179  	assert.Equal(t, "The id of the tag.", prop.Description)
   180  
   181  	assertProperty(t, &mod, "string", "value", "", "Value")
   182  	prop, ok = mod.Properties["value"]
   183  	assert.True(t, ok)
   184  	assert.Equal(t, "The value of the tag.", prop.Description)
   185  
   186  	mod, ok = definitions["pet"]
   187  	assert.True(t, ok)
   188  	assert.Equal(t, spec.StringOrArray([]string{"object"}), mod.Type)
   189  	assert.Equal(t, "A Pet is the main product in the store.", mod.Title)
   190  	assert.Equal(t, "It is used to describe the animals available in the store.", mod.Description)
   191  	assert.Len(t, mod.Required, 2)
   192  
   193  	assertProperty(t, &mod, "integer", "id", "int64", "ID")
   194  	prop, ok = mod.Properties["id"]
   195  	assert.True(t, ok, "should have had an 'id' property")
   196  	assert.Equal(t, "The id of the pet.", prop.Description)
   197  
   198  	assertProperty(t, &mod, "string", "name", "", "Name")
   199  	prop, ok = mod.Properties["name"]
   200  	assert.True(t, ok)
   201  	assert.Equal(t, "The name of the pet.", prop.Description)
   202  	assert.EqualValues(t, 3, *prop.MinLength)
   203  	assert.EqualValues(t, 50, *prop.MaxLength)
   204  	assert.Equal(t, "\\w[\\w-]+", prop.Pattern)
   205  
   206  	assertArrayProperty(t, &mod, "string", "photoUrls", "", "PhotoURLs")
   207  	prop, ok = mod.Properties["photoUrls"]
   208  	assert.True(t, ok)
   209  	assert.Equal(t, "The photo urls for the pet.\nThis only accepts jpeg or png images.", prop.Description)
   210  	if assert.NotNil(t, prop.Items) && assert.NotNil(t, prop.Items.Schema) {
   211  		assert.Equal(t, "\\.(jpe?g|png)$", prop.Items.Schema.Pattern)
   212  	}
   213  
   214  	assertProperty(t, &mod, "string", "status", "", "Status")
   215  	prop, ok = mod.Properties["status"]
   216  	assert.True(t, ok)
   217  	assert.Equal(t, "The current status of the pet in the store.", prop.Description)
   218  
   219  	assertProperty(t, &mod, "string", "birthday", "date", "Birthday")
   220  	prop, ok = mod.Properties["birthday"]
   221  	assert.True(t, ok)
   222  	assert.Equal(t, "The pet's birthday", prop.Description)
   223  
   224  	assertArrayRef(t, &mod, "tags", "Tags", "#/definitions/tag")
   225  	prop, ok = mod.Properties["tags"]
   226  	assert.True(t, ok)
   227  	assert.Equal(t, "Extra bits of information attached to this pet.", prop.Description)
   228  
   229  	mod, ok = definitions["order"]
   230  	assert.True(t, ok)
   231  	assert.Len(t, mod.Properties, 4)
   232  	assert.Len(t, mod.Required, 3)
   233  
   234  	assertProperty(t, &mod, "integer", "id", "int64", "ID")
   235  	prop, ok = mod.Properties["id"]
   236  	assert.True(t, ok, "should have had an 'id' property")
   237  	assert.Equal(t, "the ID of the order", prop.Description)
   238  
   239  	assertProperty(t, &mod, "integer", "userId", "int64", "UserID")
   240  	prop, ok = mod.Properties["userId"]
   241  	assert.True(t, ok, "should have had an 'userId' property")
   242  	assert.Equal(t, "the id of the user who placed the order.", prop.Description)
   243  
   244  	assertProperty(t, &mod, "string", "orderedAt", "date-time", "OrderedAt")
   245  	prop, ok = mod.Properties["orderedAt"]
   246  	assert.Equal(t, "the time at which this order was made.", prop.Description)
   247  	assert.True(t, ok, "should have an 'orderedAt' property")
   248  
   249  	assertArrayProperty(t, &mod, "object", "items", "", "Items")
   250  	prop, ok = mod.Properties["items"]
   251  	assert.True(t, ok, "should have an 'items' slice")
   252  	assert.NotNil(t, prop.Items, "items should have had an items property")
   253  	assert.NotNil(t, prop.Items.Schema, "items.items should have had a schema property")
   254  
   255  	itprop := prop.Items.Schema
   256  	assert.Len(t, itprop.Properties, 2)
   257  	assert.Len(t, itprop.Required, 2)
   258  
   259  	assertProperty(t, itprop, "integer", "petId", "int64", "PetID")
   260  	iprop, ok := itprop.Properties["petId"]
   261  	assert.True(t, ok, "should have had a 'petId' property")
   262  	assert.Equal(t, "the id of the pet to order", iprop.Description)
   263  
   264  	assertProperty(t, itprop, "integer", "qty", "int32", "Quantity")
   265  	iprop, ok = itprop.Properties["qty"]
   266  	assert.True(t, ok, "should have had a 'qty' property")
   267  	assert.Equal(t, "the quantity of this pet to order", iprop.Description)
   268  	assert.EqualValues(t, 1, *iprop.Minimum)
   269  
   270  	// responses
   271  	resp, ok := doc.Responses["genericError"]
   272  	assert.True(t, ok)
   273  	assert.NotNil(t, resp.Schema)
   274  	assert.Len(t, resp.Schema.Properties, 2)
   275  	assertProperty(t, resp.Schema, "integer", "code", "int32", "Code")
   276  	assertProperty(t, resp.Schema, "string", "message", "", "Message")
   277  
   278  	resp, ok = doc.Responses["validationError"]
   279  	assert.True(t, ok)
   280  	assert.NotNil(t, resp.Schema)
   281  	assert.Len(t, resp.Schema.Properties, 3)
   282  	assertProperty(t, resp.Schema, "integer", "code", "int32", "Code")
   283  	assertProperty(t, resp.Schema, "string", "message", "", "Message")
   284  	assertProperty(t, resp.Schema, "string", "field", "", "Field")
   285  
   286  	paths := doc.Paths.Paths
   287  
   288  	// path /pets
   289  	op, ok := paths["/pets"]
   290  	assert.True(t, ok)
   291  	assert.NotNil(t, op)
   292  
   293  	// listPets
   294  	assert.NotNil(t, op.Get)
   295  	assert.Equal(t, "Lists the pets known to the store.", op.Get.Summary)
   296  	assert.Equal(t, "By default it will only lists pets that are available for sale.\nThis can be changed with the status flag.", op.Get.Description)
   297  	assert.Equal(t, "listPets", op.Get.ID)
   298  	assert.EqualValues(t, []string{"pets"}, op.Get.Tags)
   299  	var names namedParams
   300  	for i, v := range op.Get.Parameters {
   301  		names = append(names, namedParam{Index: i, Name: v.Name})
   302  	}
   303  	sort.Sort(names)
   304  	sparam := op.Get.Parameters[names[1].Index]
   305  	assert.Equal(t, "Status", sparam.Description)
   306  	assert.Equal(t, "query", sparam.In)
   307  	assert.Equal(t, "string", sparam.Type)
   308  	assert.Equal(t, "", sparam.Format)
   309  	assert.False(t, sparam.Required)
   310  	assert.Equal(t, "Status", sparam.Extensions["x-go-name"])
   311  	assert.Equal(t, "#/responses/genericError", op.Get.Responses.Default.Ref.String())
   312  	assert.Len(t, op.Get.Parameters, 2)
   313  	sparam1 := op.Get.Parameters[names[0].Index]
   314  	assert.Equal(t, "Birthday", sparam1.Description)
   315  	assert.Equal(t, "query", sparam1.In)
   316  	assert.Equal(t, "string", sparam1.Type)
   317  	assert.Equal(t, "date", sparam1.Format)
   318  	assert.False(t, sparam1.Required)
   319  	assert.Equal(t, "Birthday", sparam1.Extensions["x-go-name"])
   320  	rs, ok := op.Get.Responses.StatusCodeResponses[200]
   321  	assert.True(t, ok)
   322  	assert.NotNil(t, rs.Schema)
   323  	aprop := rs.Schema
   324  	assert.Equal(t, "array", aprop.Type[0])
   325  	assert.NotNil(t, aprop.Items)
   326  	assert.NotNil(t, aprop.Items.Schema)
   327  	assert.Equal(t, "#/definitions/pet", aprop.Items.Schema.Ref.String())
   328  
   329  	// createPet
   330  	assert.NotNil(t, op.Post)
   331  	assert.Equal(t, "Creates a new pet in the store.", op.Post.Summary)
   332  	assert.Equal(t, "", op.Post.Description)
   333  	assert.Equal(t, "createPet", op.Post.ID)
   334  	assert.EqualValues(t, []string{"pets"}, op.Post.Tags)
   335  	verifyRefParam(t, op.Post.Parameters[0], "The pet to submit.", "pet")
   336  	assert.Equal(t, "#/responses/genericError", op.Post.Responses.Default.Ref.String())
   337  	rs, ok = op.Post.Responses.StatusCodeResponses[200]
   338  	assert.True(t, ok)
   339  	assert.NotNil(t, rs.Schema)
   340  	aprop = rs.Schema
   341  	assert.Equal(t, "#/definitions/pet", aprop.Ref.String())
   342  
   343  	// path /pets/{id}
   344  	op, ok = paths["/pets/{id}"]
   345  	assert.True(t, ok)
   346  	assert.NotNil(t, op)
   347  
   348  	// getPetById
   349  	assert.NotNil(t, op.Get)
   350  	assert.Equal(t, "Gets the details for a pet.", op.Get.Summary)
   351  	assert.Equal(t, "", op.Get.Description)
   352  	assert.Equal(t, "getPetById", op.Get.ID)
   353  	assert.EqualValues(t, []string{"pets"}, op.Get.Tags)
   354  	verifyIDParam(t, op.Get.Parameters[0], "The ID of the pet")
   355  	assert.Equal(t, "#/responses/genericError", op.Get.Responses.Default.Ref.String())
   356  	rs, ok = op.Get.Responses.StatusCodeResponses[200]
   357  	assert.True(t, ok)
   358  	assert.NotNil(t, rs.Schema)
   359  	aprop = rs.Schema
   360  	assert.Equal(t, "#/definitions/pet", aprop.Ref.String())
   361  
   362  	// updatePet
   363  	assert.NotNil(t, op.Put)
   364  	assert.Equal(t, "Updates the details for a pet.", op.Put.Summary)
   365  	assert.Equal(t, "", op.Put.Description)
   366  	assert.Equal(t, "updatePet", op.Put.ID)
   367  	assert.EqualValues(t, []string{"pets"}, op.Put.Tags)
   368  	verifyIDParam(t, op.Put.Parameters[0], "The ID of the pet")
   369  	verifyRefParam(t, op.Put.Parameters[1], "The pet to submit.", "pet")
   370  	assert.Equal(t, "#/responses/genericError", op.Put.Responses.Default.Ref.String())
   371  	rs, ok = op.Put.Responses.StatusCodeResponses[200]
   372  	assert.True(t, ok)
   373  	assert.NotNil(t, rs.Schema)
   374  	aprop = rs.Schema
   375  	assert.Equal(t, "#/definitions/pet", aprop.Ref.String())
   376  
   377  	// deletePet
   378  	assert.NotNil(t, op.Delete)
   379  	assert.Equal(t, "Deletes a pet from the store.", op.Delete.Summary)
   380  	assert.Equal(t, "", op.Delete.Description)
   381  	assert.Equal(t, "deletePet", op.Delete.ID)
   382  	assert.EqualValues(t, []string{"pets"}, op.Delete.Tags)
   383  	verifyIDParam(t, op.Delete.Parameters[0], "The ID of the pet")
   384  	assert.Equal(t, "#/responses/genericError", op.Delete.Responses.Default.Ref.String())
   385  	_, ok = op.Delete.Responses.StatusCodeResponses[204]
   386  	assert.True(t, ok)
   387  
   388  	// path /orders/{id}
   389  	op, ok = paths["/orders/{id}"]
   390  	assert.True(t, ok)
   391  	assert.NotNil(t, op)
   392  
   393  	// getOrderDetails
   394  	assert.NotNil(t, op.Get)
   395  	assert.Equal(t, "Gets the details for an order.", op.Get.Summary)
   396  	assert.Equal(t, "", op.Get.Description)
   397  	assert.Equal(t, "getOrderDetails", op.Get.ID)
   398  	assert.EqualValues(t, []string{"orders"}, op.Get.Tags)
   399  	verifyIDParam(t, op.Get.Parameters[0], "The ID of the order")
   400  	assert.Equal(t, "#/responses/genericError", op.Get.Responses.Default.Ref.String())
   401  	rs, ok = op.Get.Responses.StatusCodeResponses[200]
   402  	assert.True(t, ok)
   403  	assert.Equal(t, "#/responses/orderResponse", rs.Ref.String())
   404  	rsm := doc.Responses["orderResponse"]
   405  	assert.NotNil(t, rsm.Schema)
   406  	assert.Equal(t, "#/definitions/order", rsm.Schema.Ref.String())
   407  
   408  	// cancelOrder
   409  	assert.NotNil(t, op.Delete)
   410  	assert.Equal(t, "Deletes an order.", op.Delete.Summary)
   411  	assert.Equal(t, "", op.Delete.Description)
   412  	assert.Equal(t, "cancelOrder", op.Delete.ID)
   413  	assert.EqualValues(t, []string{"orders"}, op.Delete.Tags)
   414  	verifyIDParam(t, op.Delete.Parameters[0], "The ID of the order")
   415  	assert.Equal(t, "#/responses/genericError", op.Delete.Responses.Default.Ref.String())
   416  	_, ok = op.Delete.Responses.StatusCodeResponses[204]
   417  	assert.True(t, ok)
   418  
   419  	// updateOrder
   420  	assert.NotNil(t, op.Put)
   421  	assert.Equal(t, "Updates an order.", op.Put.Summary)
   422  	assert.Equal(t, "", op.Put.Description)
   423  	assert.Equal(t, "updateOrder", op.Put.ID)
   424  	assert.EqualValues(t, []string{"orders"}, op.Put.Tags)
   425  	verifyIDParam(t, op.Put.Parameters[0], "The ID of the order")
   426  	verifyRefParam(t, op.Put.Parameters[1], "The order to submit", "order")
   427  	assert.Equal(t, "#/responses/genericError", op.Put.Responses.Default.Ref.String())
   428  	rs, ok = op.Put.Responses.StatusCodeResponses[200]
   429  	assert.True(t, ok)
   430  	assert.NotNil(t, rs.Schema)
   431  	aprop = rs.Schema
   432  	assert.Equal(t, "#/definitions/order", aprop.Ref.String())
   433  
   434  	// path /orders
   435  	op, ok = paths["/orders"]
   436  	assert.True(t, ok)
   437  	assert.NotNil(t, op)
   438  
   439  	// createOrder
   440  	assert.NotNil(t, op.Post)
   441  	assert.Equal(t, "Creates an order.", op.Post.Summary)
   442  	assert.Equal(t, "", op.Post.Description)
   443  	assert.Equal(t, "createOrder", op.Post.ID)
   444  	assert.EqualValues(t, []string{"orders"}, op.Post.Tags)
   445  	verifyRefParam(t, op.Post.Parameters[0], "The order to submit", "order")
   446  	assert.Equal(t, "#/responses/genericError", op.Post.Responses.Default.Ref.String())
   447  	rs, ok = op.Post.Responses.StatusCodeResponses[200]
   448  	assert.True(t, ok)
   449  	assert.Equal(t, "#/responses/orderResponse", rs.Ref.String())
   450  	rsm = doc.Responses["orderResponse"]
   451  	assert.NotNil(t, rsm.Schema)
   452  	assert.Equal(t, "#/definitions/order", rsm.Schema.Ref.String())
   453  }
   454  
   455  func verifyIDParam(t testing.TB, param spec.Parameter, description string) {
   456  	assert.Equal(t, description, param.Description)
   457  	assert.Equal(t, "path", param.In)
   458  	assert.Equal(t, "integer", param.Type)
   459  	assert.Equal(t, "int64", param.Format)
   460  	assert.True(t, param.Required)
   461  	assert.Equal(t, "ID", param.Extensions["x-go-name"])
   462  }
   463  
   464  func verifyRefParam(t testing.TB, param spec.Parameter, description, refed string) {
   465  	assert.Equal(t, description, param.Description)
   466  	assert.Equal(t, "body", param.In)
   467  	assert.Equal(t, "#/definitions/"+refed, param.Schema.Ref.String())
   468  	assert.True(t, param.Required)
   469  }
   470  
   471  func TestSectionedParser_TitleDescription(t *testing.T) {
   472  	text := `This has a title, separated by a whitespace line
   473  
   474  In this example the punctuation for the title should not matter for swagger.
   475  For go it will still make a difference though.
   476  `
   477  	text2 := `This has a title without whitespace.
   478  The punctuation here does indeed matter. But it won't for go.
   479  `
   480  
   481  	text3 := `This has a title, and markdown in the description
   482  
   483  See how markdown works now, we can have lists:
   484  
   485  + first item
   486  + second item
   487  + third item
   488  
   489  [Links works too](http://localhost)
   490  `
   491  
   492  	text4 := `This has whitespace sensitive markdown in the description
   493  
   494  |+ first item
   495  |    + nested item
   496  |    + also nested item
   497  
   498  Sample code block:
   499  
   500  |    fmt.Println("Hello World!")
   501  
   502  `
   503  
   504  	var err error
   505  
   506  	st := &sectionedParser{}
   507  	st.setTitle = func(lines []string) {}
   508  	err = st.Parse(ascg(text))
   509  	assert.NoError(t, err)
   510  
   511  	assert.EqualValues(t, []string{"This has a title, separated by a whitespace line"}, st.Title())
   512  	assert.EqualValues(t, []string{"In this example the punctuation for the title should not matter for swagger.", "For go it will still make a difference though."}, st.Description())
   513  
   514  	st = &sectionedParser{}
   515  	st.setTitle = func(lines []string) {}
   516  	err = st.Parse(ascg(text2))
   517  	assert.NoError(t, err)
   518  
   519  	assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title())
   520  	assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description())
   521  
   522  	st = &sectionedParser{}
   523  	st.setTitle = func(lines []string) {}
   524  	err = st.Parse(ascg(text3))
   525  	assert.NoError(t, err)
   526  
   527  	assert.EqualValues(t, []string{"This has a title, and markdown in the description"}, st.Title())
   528  	assert.EqualValues(t, []string{"See how markdown works now, we can have lists:", "", "+ first item", "+ second item", "+ third item", "", "[Links works too](http://localhost)"}, st.Description())
   529  
   530  	st = &sectionedParser{}
   531  	st.setTitle = func(lines []string) {}
   532  	err = st.Parse(ascg(text4))
   533  	assert.NoError(t, err)
   534  
   535  	assert.EqualValues(t, []string{"This has whitespace sensitive markdown in the description"}, st.Title())
   536  	assert.EqualValues(t, []string{"+ first item", "    + nested item", "    + also nested item", "", "Sample code block:", "", "    fmt.Println(\"Hello World!\")"}, st.Description())
   537  }
   538  
   539  func dummyBuilder() schemaValidations {
   540  	return schemaValidations{new(spec.Schema)}
   541  }
   542  
   543  func TestSectionedParser_TagsDescription(t *testing.T) {
   544  	block := `This has a title without whitespace.
   545  The punctuation here does indeed matter. But it won't for go.
   546  minimum: 10
   547  maximum: 20
   548  `
   549  	block2 := `This has a title without whitespace.
   550  The punctuation here does indeed matter. But it won't for go.
   551  
   552  minimum: 10
   553  maximum: 20
   554  `
   555  
   556  	var err error
   557  
   558  	st := &sectionedParser{}
   559  	st.setTitle = func(lines []string) {}
   560  	st.taggers = []tagParser{
   561  		{"Maximum", false, false, nil, &setMaximum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMaximumFmt, ""))}},
   562  		{"Minimum", false, false, nil, &setMinimum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMinimumFmt, ""))}},
   563  		{"MultipleOf", false, false, nil, &setMultipleOf{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMultipleOfFmt, ""))}},
   564  	}
   565  
   566  	err = st.Parse(ascg(block))
   567  	assert.NoError(t, err)
   568  	assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title())
   569  	assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description())
   570  	assert.Len(t, st.matched, 2)
   571  	_, ok := st.matched["Maximum"]
   572  	assert.True(t, ok)
   573  	_, ok = st.matched["Minimum"]
   574  	assert.True(t, ok)
   575  
   576  	st = &sectionedParser{}
   577  	st.setTitle = func(lines []string) {}
   578  	st.taggers = []tagParser{
   579  		{"Maximum", false, false, nil, &setMaximum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMaximumFmt, ""))}},
   580  		{"Minimum", false, false, nil, &setMinimum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMinimumFmt, ""))}},
   581  		{"MultipleOf", false, false, nil, &setMultipleOf{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMultipleOfFmt, ""))}},
   582  	}
   583  
   584  	err = st.Parse(ascg(block2))
   585  	assert.NoError(t, err)
   586  	assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title())
   587  	assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description())
   588  	assert.Len(t, st.matched, 2)
   589  	_, ok = st.matched["Maximum"]
   590  	assert.True(t, ok)
   591  	_, ok = st.matched["Minimum"]
   592  	assert.True(t, ok)
   593  }
   594  
   595  func TestSectionedParser_Empty(t *testing.T) {
   596  	block := `swagger:response someResponse`
   597  
   598  	var err error
   599  
   600  	st := &sectionedParser{}
   601  	st.setTitle = func(lines []string) {}
   602  	ap := newSchemaAnnotationParser("SomeResponse")
   603  	ap.rx = rxResponseOverride
   604  	st.annotation = ap
   605  
   606  	err = st.Parse(ascg(block))
   607  	assert.NoError(t, err)
   608  	assert.Empty(t, st.Title())
   609  	assert.Empty(t, st.Description())
   610  	assert.Empty(t, st.taggers)
   611  	assert.Equal(t, "SomeResponse", ap.GoName)
   612  	assert.Equal(t, "someResponse", ap.Name)
   613  }
   614  
   615  func TestSectionedParser_SkipSectionAnnotation(t *testing.T) {
   616  	block := `swagger:model someModel
   617  
   618  This has a title without whitespace.
   619  The punctuation here does indeed matter. But it won't for go.
   620  
   621  minimum: 10
   622  maximum: 20
   623  `
   624  	var err error
   625  
   626  	st := &sectionedParser{}
   627  	st.setTitle = func(lines []string) {}
   628  	ap := newSchemaAnnotationParser("SomeModel")
   629  	st.annotation = ap
   630  	st.taggers = []tagParser{
   631  		{"Maximum", false, false, nil, &setMaximum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMaximumFmt, ""))}},
   632  		{"Minimum", false, false, nil, &setMinimum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMinimumFmt, ""))}},
   633  		{"MultipleOf", false, false, nil, &setMultipleOf{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMultipleOfFmt, ""))}},
   634  	}
   635  
   636  	err = st.Parse(ascg(block))
   637  	assert.NoError(t, err)
   638  	assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title())
   639  	assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description())
   640  	assert.Len(t, st.matched, 2)
   641  	_, ok := st.matched["Maximum"]
   642  	assert.True(t, ok)
   643  	_, ok = st.matched["Minimum"]
   644  	assert.True(t, ok)
   645  	assert.Equal(t, "SomeModel", ap.GoName)
   646  	assert.Equal(t, "someModel", ap.Name)
   647  }
   648  
   649  func TestSectionedParser_TerminateOnNewAnnotation(t *testing.T) {
   650  	block := `swagger:model someModel
   651  
   652  This has a title without whitespace.
   653  The punctuation here does indeed matter. But it won't for go.
   654  
   655  minimum: 10
   656  swagger:meta
   657  maximum: 20
   658  `
   659  	var err error
   660  
   661  	st := &sectionedParser{}
   662  	st.setTitle = func(lines []string) {}
   663  	ap := newSchemaAnnotationParser("SomeModel")
   664  	st.annotation = ap
   665  	st.taggers = []tagParser{
   666  		{"Maximum", false, false, nil, &setMaximum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMaximumFmt, ""))}},
   667  		{"Minimum", false, false, nil, &setMinimum{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMinimumFmt, ""))}},
   668  		{"MultipleOf", false, false, nil, &setMultipleOf{dummyBuilder(), regexp.MustCompile(fmt.Sprintf(rxMultipleOfFmt, ""))}},
   669  	}
   670  
   671  	err = st.Parse(ascg(block))
   672  	assert.NoError(t, err)
   673  	assert.EqualValues(t, []string{"This has a title without whitespace."}, st.Title())
   674  	assert.EqualValues(t, []string{"The punctuation here does indeed matter. But it won't for go."}, st.Description())
   675  	assert.Len(t, st.matched, 1)
   676  	_, ok := st.matched["Maximum"]
   677  	assert.False(t, ok)
   678  	_, ok = st.matched["Minimum"]
   679  	assert.True(t, ok)
   680  	assert.Equal(t, "SomeModel", ap.GoName)
   681  	assert.Equal(t, "someModel", ap.Name)
   682  }
   683  
   684  func ascg(txt string) *ast.CommentGroup {
   685  	var cg ast.CommentGroup
   686  	for _, line := range strings.Split(txt, "\n") {
   687  		var cmt ast.Comment
   688  		cmt.Text = "// " + line
   689  		cg.List = append(cg.List, &cmt)
   690  	}
   691  	return &cg
   692  }
   693  
   694  func TestSchemaValueExtractors(t *testing.T) {
   695  	strfmts := []string{
   696  		"// swagger:strfmt ",
   697  		"* swagger:strfmt ",
   698  		"* swagger:strfmt ",
   699  		" swagger:strfmt ",
   700  		"swagger:strfmt ",
   701  		"// swagger:strfmt    ",
   702  		"* swagger:strfmt     ",
   703  		"* swagger:strfmt    ",
   704  		" swagger:strfmt     ",
   705  		"swagger:strfmt      ",
   706  	}
   707  	models := []string{
   708  		"// swagger:model ",
   709  		"* swagger:model ",
   710  		"* swagger:model ",
   711  		" swagger:model ",
   712  		"swagger:model ",
   713  		"// swagger:model    ",
   714  		"* swagger:model     ",
   715  		"* swagger:model    ",
   716  		" swagger:model     ",
   717  		"swagger:model      ",
   718  	}
   719  
   720  	allOf := []string{
   721  		"// swagger:allOf ",
   722  		"* swagger:allOf ",
   723  		"* swagger:allOf ",
   724  		" swagger:allOf ",
   725  		"swagger:allOf ",
   726  		"// swagger:allOf    ",
   727  		"* swagger:allOf     ",
   728  		"* swagger:allOf    ",
   729  		" swagger:allOf     ",
   730  		"swagger:allOf      ",
   731  	}
   732  
   733  	parameters := []string{
   734  		"// swagger:parameters ",
   735  		"* swagger:parameters ",
   736  		"* swagger:parameters ",
   737  		" swagger:parameters ",
   738  		"swagger:parameters ",
   739  		"// swagger:parameters    ",
   740  		"* swagger:parameters     ",
   741  		"* swagger:parameters    ",
   742  		" swagger:parameters     ",
   743  		"swagger:parameters      ",
   744  	}
   745  
   746  	validParams := []string{
   747  		"yada123",
   748  		"date",
   749  		"date-time",
   750  		"long-combo-1-with-combo-2-and-a-3rd-one-too",
   751  	}
   752  	invalidParams := []string{
   753  		"1-yada-3",
   754  		"1-2-3",
   755  		"-yada-3",
   756  		"-2-3",
   757  		"*blah",
   758  		"blah*",
   759  	}
   760  
   761  	verifySwaggerOneArgSwaggerTag(t, rxStrFmt, strfmts, validParams, append(invalidParams, "", "  ", " "))
   762  	verifySwaggerOneArgSwaggerTag(t, rxModelOverride, models, append(validParams, "", "  ", " "), invalidParams)
   763  
   764  	verifySwaggerOneArgSwaggerTag(t, rxAllOf, allOf, append(validParams, "", "  ", " "), invalidParams)
   765  
   766  	verifySwaggerMultiArgSwaggerTag(t, rxParametersOverride, parameters, validParams, invalidParams)
   767  
   768  	verifyMinMax(t, rxf(rxMinimumFmt, ""), "min", []string{"", ">", "="})
   769  	verifyMinMax(t, rxf(rxMinimumFmt, fmt.Sprintf(rxItemsPrefixFmt, 1)), "items.min", []string{"", ">", "="})
   770  	verifyMinMax(t, rxf(rxMaximumFmt, ""), "max", []string{"", "<", "="})
   771  	verifyMinMax(t, rxf(rxMaximumFmt, fmt.Sprintf(rxItemsPrefixFmt, 1)), "items.max", []string{"", "<", "="})
   772  	verifyNumeric2Words(t, rxf(rxMultipleOfFmt, ""), "multiple", "of")
   773  	verifyNumeric2Words(t, rxf(rxMultipleOfFmt, fmt.Sprintf(rxItemsPrefixFmt, 1)), "items.multiple", "of")
   774  
   775  	verifyIntegerMinMaxManyWords(t, rxf(rxMinLengthFmt, ""), "min", []string{"len", "length"})
   776  	// pattern
   777  	extraSpaces := []string{"", " ", "  ", "     "}
   778  	prefixes := []string{"//", "*", ""}
   779  	patArgs := []string{"^\\w+$", "[A-Za-z0-9-.]*"}
   780  	patNames := []string{"pattern", "Pattern"}
   781  	for _, pref := range prefixes {
   782  		for _, es1 := range extraSpaces {
   783  			for _, nm := range patNames {
   784  				for _, es2 := range extraSpaces {
   785  					for _, es3 := range extraSpaces {
   786  						for _, arg := range patArgs {
   787  							line := strings.Join([]string{pref, es1, nm, es2, ":", es3, arg}, "")
   788  							matches := rxf(rxPatternFmt, "").FindStringSubmatch(line)
   789  							assert.Len(t, matches, 2)
   790  							assert.Equal(t, arg, matches[1])
   791  						}
   792  					}
   793  				}
   794  			}
   795  		}
   796  	}
   797  
   798  	verifyIntegerMinMaxManyWords(t, rxf(rxMinItemsFmt, ""), "min", []string{"items"})
   799  	verifyBoolean(t, rxf(rxUniqueFmt, ""), []string{"unique"}, nil)
   800  
   801  	verifyBoolean(t, rxReadOnly, []string{"read"}, []string{"only"})
   802  	verifyBoolean(t, rxRequired, []string{"required"}, nil)
   803  }
   804  
   805  func makeMinMax(lower string) (res []string) {
   806  	for _, a := range []string{"", "imum"} {
   807  		res = append(res, lower+a, strings.Title(lower)+a)
   808  	}
   809  	return
   810  }
   811  
   812  func verifyBoolean(t *testing.T, matcher *regexp.Regexp, names, names2 []string) {
   813  	extraSpaces := []string{"", " ", "  ", "     "}
   814  	prefixes := []string{"//", "*", ""}
   815  	validArgs := []string{"true", "false"}
   816  	invalidArgs := []string{"TRUE", "FALSE", "t", "f", "1", "0", "True", "False", "true*", "false*"}
   817  	var nms []string
   818  	for _, nm := range names {
   819  		nms = append(nms, nm, strings.Title(nm))
   820  	}
   821  
   822  	var nms2 []string
   823  	for _, nm := range names2 {
   824  		nms2 = append(nms2, nm, strings.Title(nm))
   825  	}
   826  
   827  	var rnms []string
   828  	if len(nms2) > 0 {
   829  		for _, nm := range nms {
   830  			for _, es := range append(extraSpaces, "-") {
   831  				for _, nm2 := range nms2 {
   832  					rnms = append(rnms, strings.Join([]string{nm, es, nm2}, ""))
   833  				}
   834  			}
   835  		}
   836  	} else {
   837  		rnms = nms
   838  	}
   839  
   840  	var cnt int
   841  	for _, pref := range prefixes {
   842  		for _, es1 := range extraSpaces {
   843  			for _, nm := range rnms {
   844  				for _, es2 := range extraSpaces {
   845  					for _, es3 := range extraSpaces {
   846  						for _, vv := range validArgs {
   847  							line := strings.Join([]string{pref, es1, nm, es2, ":", es3, vv}, "")
   848  							matches := matcher.FindStringSubmatch(line)
   849  							assert.Len(t, matches, 2)
   850  							assert.Equal(t, vv, matches[1])
   851  							cnt++
   852  						}
   853  						for _, iv := range invalidArgs {
   854  							line := strings.Join([]string{pref, es1, nm, es2, ":", es3, iv}, "")
   855  							matches := matcher.FindStringSubmatch(line)
   856  							assert.Empty(t, matches)
   857  							cnt++
   858  						}
   859  					}
   860  				}
   861  			}
   862  		}
   863  	}
   864  	var nm2 string
   865  	if len(names2) > 0 {
   866  		nm2 = " " + names2[0]
   867  	}
   868  	var Debug = os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != ""
   869  	if Debug {
   870  		fmt.Printf("tested %d %s%s combinations\n", cnt, names[0], nm2)
   871  	}
   872  }
   873  
   874  func verifyIntegerMinMaxManyWords(t *testing.T, matcher *regexp.Regexp, name1 string, words []string) {
   875  	extraSpaces := []string{"", " ", "  ", "     "}
   876  	prefixes := []string{"//", "*", ""}
   877  	validNumericArgs := []string{"0", "1234"}
   878  	invalidNumericArgs := []string{"1A3F", "2e10", "*12", "12*", "-1235", "0.0", "1234.0394", "-2948.484"}
   879  
   880  	var names []string
   881  	for _, w := range words {
   882  		names = append(names, w, strings.Title(w))
   883  	}
   884  
   885  	var cnt int
   886  	for _, pref := range prefixes {
   887  		for _, es1 := range extraSpaces {
   888  			for _, nm1 := range makeMinMax(name1) {
   889  				for _, es2 := range append(extraSpaces, "-") {
   890  					for _, nm2 := range names {
   891  						for _, es3 := range extraSpaces {
   892  							for _, es4 := range extraSpaces {
   893  								for _, vv := range validNumericArgs {
   894  									line := strings.Join([]string{pref, es1, nm1, es2, nm2, es3, ":", es4, vv}, "")
   895  									matches := matcher.FindStringSubmatch(line)
   896  									//fmt.Printf("matching %q, matches (%d): %v\n", line, len(matches), matches)
   897  									assert.Len(t, matches, 2)
   898  									assert.Equal(t, vv, matches[1])
   899  									cnt++
   900  								}
   901  								for _, iv := range invalidNumericArgs {
   902  									line := strings.Join([]string{pref, es1, nm1, es2, nm2, es3, ":", es4, iv}, "")
   903  									matches := matcher.FindStringSubmatch(line)
   904  									assert.Empty(t, matches)
   905  									cnt++
   906  								}
   907  							}
   908  						}
   909  					}
   910  				}
   911  			}
   912  		}
   913  	}
   914  	var nm2 string
   915  	if len(words) > 0 {
   916  		nm2 = " " + words[0]
   917  	}
   918  	var Debug = os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != ""
   919  	if Debug {
   920  		fmt.Printf("tested %d %s%s combinations\n", cnt, name1, nm2)
   921  
   922  	}
   923  }
   924  
   925  func verifyNumeric2Words(t *testing.T, matcher *regexp.Regexp, name1, name2 string) {
   926  	extraSpaces := []string{"", " ", "  ", "     "}
   927  	prefixes := []string{"//", "*", ""}
   928  	validNumericArgs := []string{"0", "1234", "-1235", "0.0", "1234.0394", "-2948.484"}
   929  	invalidNumericArgs := []string{"1A3F", "2e10", "*12", "12*"}
   930  
   931  	var cnt int
   932  	for _, pref := range prefixes {
   933  		for _, es1 := range extraSpaces {
   934  			for _, es2 := range extraSpaces {
   935  				for _, es3 := range extraSpaces {
   936  					for _, es4 := range extraSpaces {
   937  						for _, vv := range validNumericArgs {
   938  							lines := []string{
   939  								strings.Join([]string{pref, es1, name1, es2, name2, es3, ":", es4, vv}, ""),
   940  								strings.Join([]string{pref, es1, strings.Title(name1), es2, strings.Title(name2), es3, ":", es4, vv}, ""),
   941  								strings.Join([]string{pref, es1, strings.Title(name1), es2, name2, es3, ":", es4, vv}, ""),
   942  								strings.Join([]string{pref, es1, name1, es2, strings.Title(name2), es3, ":", es4, vv}, ""),
   943  							}
   944  							for _, line := range lines {
   945  								matches := matcher.FindStringSubmatch(line)
   946  								//fmt.Printf("matching %q, matches (%d): %v\n", line, len(matches), matches)
   947  								assert.Len(t, matches, 2)
   948  								assert.Equal(t, vv, matches[1])
   949  								cnt++
   950  							}
   951  						}
   952  						for _, iv := range invalidNumericArgs {
   953  							lines := []string{
   954  								strings.Join([]string{pref, es1, name1, es2, name2, es3, ":", es4, iv}, ""),
   955  								strings.Join([]string{pref, es1, strings.Title(name1), es2, strings.Title(name2), es3, ":", es4, iv}, ""),
   956  								strings.Join([]string{pref, es1, strings.Title(name1), es2, name2, es3, ":", es4, iv}, ""),
   957  								strings.Join([]string{pref, es1, name1, es2, strings.Title(name2), es3, ":", es4, iv}, ""),
   958  							}
   959  							for _, line := range lines {
   960  								matches := matcher.FindStringSubmatch(line)
   961  								//fmt.Printf("matching %q, matches (%d): %v\n", line, len(matches), matches)
   962  								assert.Empty(t, matches)
   963  								cnt++
   964  							}
   965  						}
   966  					}
   967  				}
   968  			}
   969  		}
   970  	}
   971  	var Debug = os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != ""
   972  	if Debug {
   973  		fmt.Printf("tested %d %s %s combinations\n", cnt, name1, name2)
   974  	}
   975  }
   976  
   977  func verifyMinMax(t *testing.T, matcher *regexp.Regexp, name string, operators []string) {
   978  	extraSpaces := []string{"", " ", "  ", "     "}
   979  	prefixes := []string{"//", "*", ""}
   980  	validNumericArgs := []string{"0", "1234", "-1235", "0.0", "1234.0394", "-2948.484"}
   981  	invalidNumericArgs := []string{"1A3F", "2e10", "*12", "12*"}
   982  
   983  	var cnt int
   984  	for _, pref := range prefixes {
   985  		for _, es1 := range extraSpaces {
   986  			for _, wrd := range makeMinMax(name) {
   987  				for _, es2 := range extraSpaces {
   988  					for _, es3 := range extraSpaces {
   989  						for _, op := range operators {
   990  							for _, es4 := range extraSpaces {
   991  								for _, vv := range validNumericArgs {
   992  									line := strings.Join([]string{pref, es1, wrd, es2, ":", es3, op, es4, vv}, "")
   993  									matches := matcher.FindStringSubmatch(line)
   994  									// fmt.Printf("matching %q with %q, matches (%d): %v\n", line, matcher, len(matches), matches)
   995  									assert.Len(t, matches, 3)
   996  									assert.Equal(t, vv, matches[2])
   997  									cnt++
   998  								}
   999  								for _, iv := range invalidNumericArgs {
  1000  									line := strings.Join([]string{pref, es1, wrd, es2, ":", es3, op, es4, iv}, "")
  1001  									matches := matcher.FindStringSubmatch(line)
  1002  									assert.Empty(t, matches)
  1003  									cnt++
  1004  								}
  1005  							}
  1006  						}
  1007  					}
  1008  				}
  1009  			}
  1010  		}
  1011  	}
  1012  	var Debug = os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != ""
  1013  	if Debug {
  1014  		fmt.Printf("tested %d %s combinations\n", cnt, name)
  1015  	}
  1016  }
  1017  
  1018  func verifySwaggerOneArgSwaggerTag(t *testing.T, matcher *regexp.Regexp, prefixes, validParams, invalidParams []string) {
  1019  	for _, pref := range prefixes {
  1020  		for _, param := range validParams {
  1021  			line := pref + param
  1022  			matches := matcher.FindStringSubmatch(line)
  1023  			if assert.Len(t, matches, 2) {
  1024  				assert.Equal(t, strings.TrimSpace(param), matches[1])
  1025  			}
  1026  		}
  1027  	}
  1028  
  1029  	for _, pref := range prefixes {
  1030  		for _, param := range invalidParams {
  1031  			line := pref + param
  1032  			matches := matcher.FindStringSubmatch(line)
  1033  			assert.Empty(t, matches)
  1034  		}
  1035  	}
  1036  }
  1037  
  1038  func verifySwaggerMultiArgSwaggerTag(t *testing.T, matcher *regexp.Regexp, prefixes, validParams, invalidParams []string) {
  1039  	var actualParams []string
  1040  	for i := 0; i < len(validParams); i++ {
  1041  		var vp []string
  1042  		for j := 0; j < (i + 1); j++ {
  1043  			vp = append(vp, validParams[j])
  1044  		}
  1045  		actualParams = append(actualParams, strings.Join(vp, " "))
  1046  	}
  1047  	for _, pref := range prefixes {
  1048  		for _, param := range actualParams {
  1049  			line := pref + param
  1050  			matches := matcher.FindStringSubmatch(line)
  1051  			// fmt.Printf("matching %q with %q, matches (%d): %v\n", line, matcher, len(matches), matches)
  1052  			assert.Len(t, matches, 2)
  1053  			assert.Equal(t, strings.TrimSpace(param), matches[1])
  1054  		}
  1055  	}
  1056  
  1057  	for _, pref := range prefixes {
  1058  		for _, param := range invalidParams {
  1059  			line := pref + param
  1060  			matches := matcher.FindStringSubmatch(line)
  1061  			assert.Empty(t, matches)
  1062  		}
  1063  	}
  1064  }
  1065  
  1066  func TestEnhancement793(t *testing.T) {
  1067  	var err error
  1068  	scanner, err := newAppScanner(&Opts{
  1069  		BasePath:   "../fixtures/enhancements/793",
  1070  		ScanModels: true,
  1071  	})
  1072  	assert.NoError(t, err)
  1073  	assert.NotNil(t, scanner)
  1074  	doc, err := scanner.Parse()
  1075  	assert.NoError(t, err)
  1076  	if assert.NotNil(t, doc) {
  1077  		bytes, err := doc.MarshalJSON()
  1078  		assert.NoError(t, err)
  1079  		assert.NotEmpty(t, bytes)
  1080  
  1081  		file, _ := ioutil.TempFile(os.TempDir(), "scanner")
  1082  		file.Write(bytes)
  1083  		file.Close()
  1084  
  1085  		doc, err := loads.Spec(file.Name())
  1086  		if assert.NoError(t, err) {
  1087  			validator := validate.NewSpecValidator(doc.Schema(), strfmt.Default)
  1088  			res, _ := validator.Validate(doc)
  1089  			assert.Empty(t, res.Errors)
  1090  			assert.True(t, res.IsValid())
  1091  		}
  1092  	}
  1093  }