...

Source file src/github.com/go-openapi/loads/fmts/yaml_test.go

Documentation: github.com/go-openapi/loads/fmts

     1  // Copyright 2015 go-swagger maintainers
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fmts
    16  
    17  import (
    18  	"encoding/json"
    19  	"errors"
    20  	"net/http"
    21  	"net/http/httptest"
    22  	"testing"
    23  
    24  	yaml "gopkg.in/yaml.v3"
    25  
    26  	"github.com/go-openapi/swag"
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  type failJSONMarshal struct {
    32  }
    33  
    34  func (f failJSONMarshal) MarshalJSON() ([]byte, error) {
    35  	return nil, errors.New("expected")
    36  }
    37  
    38  func TestLoadHTTPBytes(t *testing.T) {
    39  	_, err := swag.LoadFromFileOrHTTP("httx://12394:abd")
    40  	require.Error(t, err)
    41  
    42  	serv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
    43  		rw.WriteHeader(http.StatusNotFound)
    44  	}))
    45  	defer serv.Close()
    46  
    47  	_, err = swag.LoadFromFileOrHTTP(serv.URL)
    48  	require.Error(t, err)
    49  
    50  	ts2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
    51  		rw.WriteHeader(http.StatusOK)
    52  		_, _ = rw.Write([]byte("the content"))
    53  	}))
    54  	defer ts2.Close()
    55  
    56  	d, err := swag.LoadFromFileOrHTTP(ts2.URL)
    57  	require.NoError(t, err)
    58  	assert.Equal(t, []byte("the content"), d)
    59  }
    60  
    61  func TestYAMLToJSON(t *testing.T) {
    62  	const sd = `---
    63  1: the int key value
    64  name: a string value
    65  'y': some value
    66  `
    67  	t.Run("YAML object as JSON", func(t *testing.T) {
    68  		var data interface{}
    69  		require.NoError(t, yaml.Unmarshal([]byte(sd), &data))
    70  
    71  		d, err := YAMLToJSON(data)
    72  		require.NoError(t, err)
    73  		assert.JSONEq(t,
    74  			`{"1":"the int key value","name":"a string value","y":"some value"}`,
    75  			string(d),
    76  		)
    77  	})
    78  
    79  	t.Run("YAML nodes as JSON", func(t *testing.T) {
    80  		var data yaml.Node
    81  		require.NoError(t, yaml.Unmarshal([]byte(sd), &data))
    82  
    83  		data.Content[0].Content = append(data.Content[0].Content,
    84  			&yaml.Node{Kind: yaml.ScalarNode, Value: "tag", Tag: "!!str"},
    85  			&yaml.Node{
    86  				Kind: yaml.MappingNode,
    87  				Content: []*yaml.Node{
    88  					{Kind: yaml.ScalarNode, Value: "name", Tag: "!!str"},
    89  					{Kind: yaml.ScalarNode, Value: "tag name", Tag: "!!str"},
    90  				},
    91  			},
    92  		)
    93  
    94  		d, err := YAMLToJSON(data)
    95  		require.NoError(t, err)
    96  		assert.JSONEq(t,
    97  			`{"1":"the int key value","name":"a string value","y":"some value","tag":{"name":"tag name"}}`,
    98  			string(d),
    99  		)
   100  	})
   101  
   102  	t.Run("YAML slice as JSON", func(t *testing.T) {
   103  		lst := []interface{}{"hello"}
   104  		d, err := YAMLToJSON(&lst)
   105  		require.NoError(t, err)
   106  		assert.JSONEq(t, `["hello"]`, string(d))
   107  	})
   108  
   109  	t.Run("fail to convert to JSON", func(t *testing.T) {
   110  		t.Run("with invalid receiver", func(t *testing.T) {
   111  			_, err := YAMLToJSON(failJSONMarshal{})
   112  			require.Error(t, err)
   113  		})
   114  
   115  		t.Run("with invalid document", func(t *testing.T) {
   116  			_, err := BytesToYAMLDoc([]byte("- name: hello\n"))
   117  			require.Error(t, err)
   118  		})
   119  	})
   120  
   121  	t.Run("with BytesToYamlDoc", func(t *testing.T) {
   122  		dd, err := BytesToYAMLDoc([]byte("description: 'object created'\n"))
   123  		require.NoError(t, err)
   124  
   125  		d, err := YAMLToJSON(dd)
   126  		require.NoError(t, err)
   127  		assert.Equal(t, json.RawMessage(`{"description":"object created"}`), d)
   128  	})
   129  }
   130  
   131  func TestLoadStrategy(t *testing.T) {
   132  	loader := func(_ string) ([]byte, error) {
   133  		return []byte(yamlPetStore), nil
   134  	}
   135  	remLoader := func(_ string) ([]byte, error) {
   136  		return []byte("not it"), nil
   137  	}
   138  
   139  	ld := swag.LoadStrategy("blah", loader, remLoader)
   140  	b, _ := ld("")
   141  	assert.Equal(t, []byte(yamlPetStore), b)
   142  
   143  	serv := httptest.NewServer(http.HandlerFunc(yamlPestoreServer))
   144  	defer serv.Close()
   145  
   146  	s, err := YAMLDoc(serv.URL)
   147  	require.NoError(t, err)
   148  	assert.NotNil(t, s)
   149  
   150  	ts2 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
   151  		rw.WriteHeader(http.StatusNotFound)
   152  		_, _ = rw.Write([]byte("\n"))
   153  	}))
   154  	defer ts2.Close()
   155  	_, err = YAMLDoc(ts2.URL)
   156  	require.Error(t, err)
   157  }
   158  
   159  var yamlPestoreServer = func(rw http.ResponseWriter, _ *http.Request) {
   160  	rw.WriteHeader(http.StatusOK)
   161  	_, _ = rw.Write([]byte(yamlPetStore))
   162  }
   163  
   164  func TestWithYKey(t *testing.T) {
   165  	t.Run("with YAMLv3, unquoted y as key is parsed correctly", func(t *testing.T) {
   166  		doc, err := BytesToYAMLDoc([]byte(withYKey))
   167  		require.NoError(t, err)
   168  
   169  		_, err = YAMLToJSON(doc)
   170  		require.NoError(t, err)
   171  	})
   172  
   173  	t.Run("quoted y as key is parsed correctly", func(t *testing.T) {
   174  		doc, err := BytesToYAMLDoc([]byte(withQuotedYKey))
   175  		require.NoError(t, err)
   176  
   177  		jsond, err := YAMLToJSON(doc)
   178  		require.NoError(t, err)
   179  
   180  		var yt struct {
   181  			Definitions struct {
   182  				Viewbox struct {
   183  					Properties struct {
   184  						Y struct {
   185  							Type string `json:"type"`
   186  						} `json:"y"`
   187  					} `json:"properties"`
   188  				} `json:"viewbox"`
   189  			} `json:"definitions"`
   190  		}
   191  		require.NoError(t, json.Unmarshal(jsond, &yt))
   192  
   193  		assert.Equal(t, "integer", yt.Definitions.Viewbox.Properties.Y.Type)
   194  	})
   195  }
   196  
   197  const withQuotedYKey = `consumes:
   198  - application/json
   199  definitions:
   200    viewBox:
   201      type: object
   202      properties:
   203        x:
   204          type: integer
   205          format: int16
   206        # y -> types don't match: expect map key string or int get: bool
   207        "y":
   208          type: integer
   209          format: int16
   210        width:
   211          type: integer
   212          format: int16
   213        height:
   214          type: integer
   215          format: int16
   216  info:
   217    description: Test RESTful APIs
   218    title: Test Server
   219    version: 1.0.0
   220  basePath: /api
   221  paths:
   222    /test:
   223      get:
   224        operationId: findAll
   225        parameters:
   226          - name: since
   227            in: query
   228            type: integer
   229            format: int64
   230          - name: limit
   231            in: query
   232            type: integer
   233            format: int32
   234            default: 20
   235        responses:
   236          200:
   237            description: Array[Trigger]
   238            schema:
   239              type: array
   240              items:
   241                $ref: "#/definitions/viewBox"
   242  produces:
   243  - application/json
   244  schemes:
   245  - https
   246  swagger: "2.0"
   247  `
   248  
   249  const withYKey = `consumes:
   250  - application/json
   251  definitions:
   252    viewBox:
   253      type: object
   254      properties:
   255        x:
   256          type: integer
   257          format: int16
   258        # y -> types don't match: expect map key string or int get: bool
   259        y:
   260          type: integer
   261          format: int16
   262        width:
   263          type: integer
   264          format: int16
   265        height:
   266          type: integer
   267          format: int16
   268  info:
   269    description: Test RESTful APIs
   270    title: Test Server
   271    version: 1.0.0
   272  basePath: /api
   273  paths:
   274    /test:
   275      get:
   276        operationId: findAll
   277        parameters:
   278          - name: since
   279            in: query
   280            type: integer
   281            format: int64
   282          - name: limit
   283            in: query
   284            type: integer
   285            format: int32
   286            default: 20
   287        responses:
   288          200:
   289            description: Array[Trigger]
   290            schema:
   291              type: array
   292              items:
   293                $ref: "#/definitions/viewBox"
   294  produces:
   295  - application/json
   296  schemes:
   297  - https
   298  swagger: "2.0"
   299  `
   300  
   301  const yamlPetStore = `swagger: '2.0'
   302  info:
   303    version: '1.0.0'
   304    title: Swagger Petstore
   305    description: A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification
   306    termsOfService: http://helloreverb.com/terms/
   307    contact:
   308      name: Swagger API team
   309      email: foo@example.com
   310      url: http://swagger.io
   311    license:
   312      name: MIT
   313      url: http://opensource.org/licenses/MIT
   314  host: petstore.swagger.wordnik.com
   315  basePath: /api
   316  schemes:
   317    - http
   318  consumes:
   319    - application/json
   320  produces:
   321    - application/json
   322  paths:
   323    /pets:
   324      get:
   325        description: Returns all pets from the system that the user has access to
   326        operationId: findPets
   327        produces:
   328          - application/json
   329          - application/xml
   330          - text/xml
   331          - text/html
   332        parameters:
   333          - name: tags
   334            in: query
   335            description: tags to filter by
   336            required: false
   337            type: array
   338            items:
   339              type: string
   340            collectionFormat: csv
   341          - name: limit
   342            in: query
   343            description: maximum number of results to return
   344            required: false
   345            type: integer
   346            format: int32
   347        responses:
   348          '200':
   349            description: pet response
   350            schema:
   351              type: array
   352              items:
   353                $ref: '#/definitions/pet'
   354          default:
   355            description: unexpected error
   356            schema:
   357              $ref: '#/definitions/errorModel'
   358      post:
   359        description: Creates a new pet in the store.  Duplicates are allowed
   360        operationId: addPet
   361        produces:
   362          - application/json
   363        parameters:
   364          - name: pet
   365            in: body
   366            description: Pet to add to the store
   367            required: true
   368            schema:
   369              $ref: '#/definitions/newPet'
   370        responses:
   371          '200':
   372            description: pet response
   373            schema:
   374              $ref: '#/definitions/pet'
   375          default:
   376            description: unexpected error
   377            schema:
   378              $ref: '#/definitions/errorModel'
   379    /pets/{id}:
   380      get:
   381        description: Returns a user based on a single ID, if the user does not have access to the pet
   382        operationId: findPetById
   383        produces:
   384          - application/json
   385          - application/xml
   386          - text/xml
   387          - text/html
   388        parameters:
   389          - name: id
   390            in: path
   391            description: ID of pet to fetch
   392            required: true
   393            type: integer
   394            format: int64
   395        responses:
   396          '200':
   397            description: pet response
   398            schema:
   399              $ref: '#/definitions/pet'
   400          default:
   401            description: unexpected error
   402            schema:
   403              $ref: '#/definitions/errorModel'
   404      delete:
   405        description: deletes a single pet based on the ID supplied
   406        operationId: deletePet
   407        parameters:
   408          - name: id
   409            in: path
   410            description: ID of pet to delete
   411            required: true
   412            type: integer
   413            format: int64
   414        responses:
   415          '204':
   416            description: pet deleted
   417          default:
   418            description: unexpected error
   419            schema:
   420              $ref: '#/definitions/errorModel'
   421  definitions:
   422    pet:
   423      required:
   424        - id
   425        - name
   426      properties:
   427        id:
   428          type: integer
   429          format: int64
   430        name:
   431          type: string
   432        tag:
   433          type: string
   434    newPet:
   435      allOf:
   436        - $ref: '#/definitions/pet'
   437        - required:
   438            - name
   439          properties:
   440            id:
   441              type: integer
   442              format: int64
   443            name:
   444              type: string
   445    errorModel:
   446      required:
   447        - code
   448        - message
   449      properties:
   450        code:
   451          type: integer
   452          format: int32
   453        message:
   454          type: string
   455  `
   456  

View as plain text