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 15package loads 16 17import ( 18 "encoding/json" 19 "regexp" 20 "strconv" 21 "strings" 22 "testing" 23 24 "github.com/stretchr/testify/assert" 25) 26 27func TestUnknownSpecVersion(t *testing.T) { 28 _, err := Analyzed([]byte{}, "0.9") 29 assert.Error(t, err) 30} 31 32func TestDefaultsTo20(t *testing.T) { 33 d, err := Analyzed(PetStoreJSONMessage, "") 34 35 assert.NoError(t, err) 36 assert.NotNil(t, d) 37 assert.Equal(t, "2.0", d.Version()) 38 // assert.Equal(t, "2.0", d.data["swagger"].(string)) 39 assert.Equal(t, "/api", d.BasePath()) 40} 41 42func TestLoadsYAMLContent(t *testing.T) { 43 d, err := Analyzed(json.RawMessage([]byte(YAMLSpec)), "") 44 if assert.NoError(t, err) { 45 if assert.NotNil(t, d) { 46 sw := d.Spec() 47 assert.Equal(t, "1.0.0", sw.Info.Version) 48 } 49 } 50} 51 52// for issue 11 53func TestRegressionExpand(t *testing.T) { 54 swaggerFile := "fixtures/yaml/swagger/1/2/3/4/swagger.yaml" 55 document, err := Spec(swaggerFile) 56 assert.NoError(t, err) 57 assert.NotNil(t, document) 58 d, err := document.Expanded() 59 assert.NoError(t, err) 60 assert.NotNil(t, d) 61 b, _ := d.Spec().MarshalJSON() 62 assert.JSONEq(t, expectedExpanded, string(b)) 63} 64 65func TestCascadingRefExpand(t *testing.T) { 66 swaggerFile := "fixtures/yaml/swagger/spec.yml" 67 document, err := Spec(swaggerFile) 68 assert.NoError(t, err) 69 assert.NotNil(t, document) 70 d, err := document.Expanded() 71 assert.NoError(t, err) 72 assert.NotNil(t, d) 73 b, _ := d.Spec().MarshalJSON() 74 assert.JSONEq(t, cascadeRefExpanded, string(b)) 75} 76 77func TestFailsInvalidJSON(t *testing.T) { 78 _, err := Analyzed(json.RawMessage([]byte("{]")), "") 79 80 assert.Error(t, err) 81} 82 83// issue go-swagger/go-swagger#1816 (regression when cloning original spec) 84func TestIssue1846(t *testing.T) { 85 swaggerFile := "fixtures/bugs/1816/fixture-1816.yaml" 86 document, err := Spec(swaggerFile) 87 assert.NoError(t, err) 88 assert.NotNil(t, document) 89 90 sp, err := cloneSpec(document.Spec()) 91 assert.NoError(t, err) 92 jazon, _ := json.MarshalIndent(sp, "", " ") 93 //t.Logf("%s", string(jazon)) 94 rex := regexp.MustCompile(`"\$ref":\s*"(.+)"`) 95 m := rex.FindAllStringSubmatch(string(jazon), -1) 96 if assert.NotNil(t, m) { 97 for _, matched := range m { 98 subMatch := matched[1] 99 if !assert.True(t, strings.HasPrefix(subMatch, "#/definitions") || strings.HasPrefix(subMatch, "#/responses"), 100 "expected $ref to point either to definitions or responses section, got: %s", matched[0]) { 101 t.FailNow() 102 } 103 } 104 } 105} 106 107func BenchmarkAnalyzed(b *testing.B) { 108 d := []byte(`{ 109 "swagger": "2.0", 110 "info": { 111 "version": "1.0.0", 112 "title": "Swagger Petstore", 113 "contact": { 114 "name": "Wordnik API Team", 115 "url": "http://developer.wordnik.com" 116 }, 117 "license": { 118 "name": "Creative Commons 4.0 International", 119 "url": "http://creativecommons.org/licenses/by/4.0/" 120 } 121 }, 122 "host": "petstore.swagger.wordnik.com", 123 "basePath": "/api", 124 "schemes": [ 125 "http" 126 ], 127 "paths": { 128 "/pets": { 129 "get": { 130 "security": [ 131 { 132 "basic": [] 133 } 134 ], 135 "tags": [ "Pet Operations" ], 136 "operationId": "getAllPets", 137 "parameters": [ 138 { 139 "name": "status", 140 "in": "query", 141 "description": "The status to filter by", 142 "type": "string" 143 }, 144 { 145 "name": "limit", 146 "in": "query", 147 "description": "The maximum number of results to return", 148 "type": "integer", 149 "format": "int64" 150 } 151 ], 152 "summary": "Finds all pets in the system", 153 "responses": { 154 "200": { 155 "description": "Pet response", 156 "schema": { 157 "type": "array", 158 "items": { 159 "$ref": "#/definitions/Pet" 160 } 161 } 162 }, 163 "default": { 164 "description": "Unexpected error", 165 "schema": { 166 "$ref": "#/definitions/Error" 167 } 168 } 169 } 170 }, 171 "post": { 172 "security": [ 173 { 174 "basic": [] 175 } 176 ], 177 "tags": [ "Pet Operations" ], 178 "operationId": "createPet", 179 "summary": "Creates a new pet", 180 "consumes": ["application/x-yaml"], 181 "produces": ["application/x-yaml"], 182 "parameters": [ 183 { 184 "name": "pet", 185 "in": "body", 186 "description": "The Pet to create", 187 "required": true, 188 "schema": { 189 "$ref": "#/definitions/newPet" 190 } 191 } 192 ], 193 "responses": { 194 "200": { 195 "description": "Created Pet response", 196 "schema": { 197 "$ref": "#/definitions/Pet" 198 } 199 }, 200 "default": { 201 "description": "Unexpected error", 202 "schema": { 203 "$ref": "#/definitions/Error" 204 } 205 } 206 } 207 } 208 }`) 209 210 for i := 0; i < 1000; i++ { 211 d = append(d, []byte(`, 212 "/pets/`)...) 213 d = strconv.AppendInt(d, int64(i), 10) 214 d = append(d, []byte(`": { 215 "delete": { 216 "security": [ 217 { 218 "apiKey": [] 219 } 220 ], 221 "description": "Deletes the Pet by id", 222 "operationId": "deletePet", 223 "parameters": [ 224 { 225 "name": "id", 226 "in": "path", 227 "description": "ID of pet to delete", 228 "required": true, 229 "type": "integer", 230 "format": "int64" 231 } 232 ], 233 "responses": { 234 "204": { 235 "description": "pet deleted" 236 }, 237 "default": { 238 "description": "unexpected error", 239 "schema": { 240 "$ref": "#/definitions/Error" 241 } 242 } 243 } 244 }, 245 "get": { 246 "tags": [ "Pet Operations" ], 247 "operationId": "getPetById", 248 "summary": "Finds the pet by id", 249 "responses": { 250 "200": { 251 "description": "Pet response", 252 "schema": { 253 "$ref": "#/definitions/Pet" 254 } 255 }, 256 "default": { 257 "description": "Unexpected error", 258 "schema": { 259 "$ref": "#/definitions/Error" 260 } 261 } 262 } 263 }, 264 "parameters": [ 265 { 266 "name": "id", 267 "in": "path", 268 "description": "ID of pet", 269 "required": true, 270 "type": "integer", 271 "format": "int64" 272 } 273 ] 274 }`)...) 275 } 276 277 d = append(d, []byte(` 278 }, 279 "definitions": { 280 "Category": { 281 "id": "Category", 282 "properties": { 283 "id": { 284 "format": "int64", 285 "type": "integer" 286 }, 287 "name": { 288 "type": "string" 289 } 290 } 291 }, 292 "Pet": { 293 "id": "Pet", 294 "properties": { 295 "category": { 296 "$ref": "#/definitions/Category" 297 }, 298 "id": { 299 "description": "unique identifier for the pet", 300 "format": "int64", 301 "maximum": 100.0, 302 "minimum": 0.0, 303 "type": "integer" 304 }, 305 "name": { 306 "type": "string" 307 }, 308 "photoUrls": { 309 "items": { 310 "type": "string" 311 }, 312 "type": "array" 313 }, 314 "status": { 315 "description": "pet status in the store", 316 "enum": [ 317 "available", 318 "pending", 319 "sold" 320 ], 321 "type": "string" 322 }, 323 "tags": { 324 "items": { 325 "$ref": "#/definitions/Tag" 326 }, 327 "type": "array" 328 } 329 }, 330 "required": [ 331 "id", 332 "name" 333 ] 334 }, 335 "newPet": { 336 "anyOf": [ 337 { 338 "$ref": "#/definitions/Pet" 339 }, 340 { 341 "required": [ 342 "name" 343 ] 344 } 345 ] 346 }, 347 "Tag": { 348 "id": "Tag", 349 "properties": { 350 "id": { 351 "format": "int64", 352 "type": "integer" 353 }, 354 "name": { 355 "type": "string" 356 } 357 } 358 }, 359 "Error": { 360 "required": [ 361 "code", 362 "message" 363 ], 364 "properties": { 365 "code": { 366 "type": "integer", 367 "format": "int32" 368 }, 369 "message": { 370 "type": "string" 371 } 372 } 373 } 374 }, 375 "consumes": [ 376 "application/json", 377 "application/xml" 378 ], 379 "produces": [ 380 "application/json", 381 "application/xml", 382 "text/plain", 383 "text/html" 384 ], 385 "securityDefinitions": { 386 "basic": { 387 "type": "basic" 388 }, 389 "apiKey": { 390 "type": "apiKey", 391 "in": "header", 392 "name": "X-API-KEY" 393 } 394 } 395} 396`)...) 397 rm := json.RawMessage(d) 398 b.ResetTimer() 399 for i := 0; i < b.N; i++ { 400 _, err := Analyzed(rm, "") 401 if err != nil { 402 b.Fatal(err) 403 } 404 } 405} 406 407const YAMLSpec = `swagger: '2.0' 408 409info: 410 version: "1.0.0" 411 title: Simple Search API 412 description: | 413 A very simple api description that makes a x-www-form-urlencoded only API to submit searches. 414 415produces: 416 - application/json 417 418consumes: 419 - application/json 420 421paths: 422 /search: 423 post: 424 operationId: search 425 summary: searches tasks 426 description: searches the task titles and descriptions for a match 427 consumes: 428 - application/x-www-form-urlencoded 429 parameters: 430 - name: q 431 in: formData 432 type: string 433 description: the search string 434 required: true 435 /tasks: 436 get: 437 operationId: getTasks 438 summary: Gets Task objects. 439 description: | 440 Optional query param of **size** determines 441 size of returned array 442 tags: 443 - tasks 444 parameters: 445 - name: size 446 in: query 447 description: Size of task list 448 type: integer 449 format: int32 450 default: 20 451 - name: completed 452 in: query 453 description: when true shows completed tasks 454 type: boolean 455 456 responses: 457 default: 458 description: Generic Error 459 200: 460 description: Successful response 461 headers: 462 X-Rate-Limit: 463 type: integer 464 format: int32 465 X-Rate-Limit-Remaining: 466 type: integer 467 format: int32 468 default: 42 469 X-Rate-Limit-Reset: 470 type: integer 471 format: int32 472 default: "1449875311" 473 X-Rate-Limit-Reset-Human: 474 type: string 475 default: 3 days 476 X-Rate-Limit-Reset-Human-Number: 477 type: string 478 default: 3 479 Access-Control-Allow-Origin: 480 type: string 481 default: "*" 482 schema: 483 type: array 484 items: 485 $ref: "#/definitions/Task" 486 post: 487 operationId: createTask 488 summary: Creates a 'Task' object. 489 description: | 490 Validates the content property for length etc. 491 parameters: 492 - name: body 493 in: body 494 schema: 495 $ref: "#/definitions/Task" 496 tags: 497 - tasks 498 responses: 499 default: 500 description: Generic Error 501 201: 502 description: Task Created 503 504 /tasks/{id}: 505 parameters: 506 - name: id 507 in: path 508 type: integer 509 format: int32 510 description: The id of the task 511 required: true 512 minimum: 1 513 put: 514 operationId: updateTask 515 summary: updates a task. 516 description: | 517 Validates the content property for length etc. 518 tags: 519 - tasks 520 parameters: 521 - name: body 522 in: body 523 description: the updated task 524 schema: 525 $ref: "#/definitions/Task" 526 responses: 527 default: 528 description: Generic Error 529 200: 530 description: Task updated 531 schema: 532 $ref: "#/definitions/Task" 533 delete: 534 operationId: deleteTask 535 summary: deletes a task 536 description: | 537 Deleting a task is irrevocable. 538 tags: 539 - tasks 540 responses: 541 default: 542 description: Generic Error 543 204: 544 description: Task Deleted 545 546 547definitions: 548 Task: 549 title: A Task object 550 description: | 551 This describes a task. Tasks require a content property to be set. 552 required: 553 - content 554 type: object 555 properties: 556 id: 557 title: the unique id of the task 558 description: | 559 This id property is autogenerated when a task is created. 560 type: integer 561 format: int64 562 readOnly: true 563 content: 564 title: The content of the task 565 description: | 566 Task content can contain [GFM](https://help.github.com/articles/github-flavored-markdown/). 567 type: string 568 minLength: 5 569 completed: 570 title: when true this task is completed 571 type: boolean 572 creditcard: 573 title: the credit card format usage 574 type: string 575 format: creditcard 576 createdAt: 577 title: task creation time 578 type: string 579 format: date-time 580 readOnly: true 581` 582 583// PetStoreJSONMessage json raw message for Petstore20 584var PetStoreJSONMessage = json.RawMessage([]byte(PetStore20)) 585 586// PetStore20 json doc for swagger 2.0 pet store 587const PetStore20 = `{ 588 "swagger": "2.0", 589 "info": { 590 "version": "1.0.0", 591 "title": "Swagger Petstore", 592 "contact": { 593 "name": "Wordnik API Team", 594 "url": "http://developer.wordnik.com" 595 }, 596 "license": { 597 "name": "Creative Commons 4.0 International", 598 "url": "http://creativecommons.org/licenses/by/4.0/" 599 } 600 }, 601 "host": "petstore.swagger.wordnik.com", 602 "basePath": "/api", 603 "schemes": [ 604 "http" 605 ], 606 "paths": { 607 "/pets": { 608 "get": { 609 "security": [ 610 { 611 "basic": [] 612 } 613 ], 614 "tags": [ "Pet Operations" ], 615 "operationId": "getAllPets", 616 "parameters": [ 617 { 618 "name": "status", 619 "in": "query", 620 "description": "The status to filter by", 621 "type": "string" 622 }, 623 { 624 "name": "limit", 625 "in": "query", 626 "description": "The maximum number of results to return", 627 "type": "integer", 628 "format": "int64" 629 } 630 ], 631 "summary": "Finds all pets in the system", 632 "responses": { 633 "200": { 634 "description": "Pet response", 635 "schema": { 636 "type": "array", 637 "items": { 638 "$ref": "#/definitions/Pet" 639 } 640 } 641 }, 642 "default": { 643 "description": "Unexpected error", 644 "schema": { 645 "$ref": "#/definitions/Error" 646 } 647 } 648 } 649 }, 650 "post": { 651 "security": [ 652 { 653 "basic": [] 654 } 655 ], 656 "tags": [ "Pet Operations" ], 657 "operationId": "createPet", 658 "summary": "Creates a new pet", 659 "consumes": ["application/x-yaml"], 660 "produces": ["application/x-yaml"], 661 "parameters": [ 662 { 663 "name": "pet", 664 "in": "body", 665 "description": "The Pet to create", 666 "required": true, 667 "schema": { 668 "$ref": "#/definitions/newPet" 669 } 670 } 671 ], 672 "responses": { 673 "200": { 674 "description": "Created Pet response", 675 "schema": { 676 "$ref": "#/definitions/Pet" 677 } 678 }, 679 "default": { 680 "description": "Unexpected error", 681 "schema": { 682 "$ref": "#/definitions/Error" 683 } 684 } 685 } 686 } 687 }, 688 "/pets/{id}": { 689 "delete": { 690 "security": [ 691 { 692 "apiKey": [] 693 } 694 ], 695 "description": "Deletes the Pet by id", 696 "operationId": "deletePet", 697 "parameters": [ 698 { 699 "name": "id", 700 "in": "path", 701 "description": "ID of pet to delete", 702 "required": true, 703 "type": "integer", 704 "format": "int64" 705 } 706 ], 707 "responses": { 708 "204": { 709 "description": "pet deleted" 710 }, 711 "default": { 712 "description": "unexpected error", 713 "schema": { 714 "$ref": "#/definitions/Error" 715 } 716 } 717 } 718 }, 719 "get": { 720 "tags": [ "Pet Operations" ], 721 "operationId": "getPetById", 722 "summary": "Finds the pet by id", 723 "responses": { 724 "200": { 725 "description": "Pet response", 726 "schema": { 727 "$ref": "#/definitions/Pet" 728 } 729 }, 730 "default": { 731 "description": "Unexpected error", 732 "schema": { 733 "$ref": "#/definitions/Error" 734 } 735 } 736 } 737 }, 738 "parameters": [ 739 { 740 "name": "id", 741 "in": "path", 742 "description": "ID of pet", 743 "required": true, 744 "type": "integer", 745 "format": "int64" 746 } 747 ] 748 } 749 }, 750 "definitions": { 751 "Category": { 752 "id": "Category", 753 "properties": { 754 "id": { 755 "format": "int64", 756 "type": "integer" 757 }, 758 "name": { 759 "type": "string" 760 } 761 } 762 }, 763 "Pet": { 764 "id": "Pet", 765 "properties": { 766 "category": { 767 "$ref": "#/definitions/Category" 768 }, 769 "id": { 770 "description": "unique identifier for the pet", 771 "format": "int64", 772 "maximum": 100.0, 773 "minimum": 0.0, 774 "type": "integer" 775 }, 776 "name": { 777 "type": "string" 778 }, 779 "photoUrls": { 780 "items": { 781 "type": "string" 782 }, 783 "type": "array" 784 }, 785 "status": { 786 "description": "pet status in the store", 787 "enum": [ 788 "available", 789 "pending", 790 "sold" 791 ], 792 "type": "string" 793 }, 794 "tags": { 795 "items": { 796 "$ref": "#/definitions/Tag" 797 }, 798 "type": "array" 799 } 800 }, 801 "required": [ 802 "id", 803 "name" 804 ] 805 }, 806 "newPet": { 807 "anyOf": [ 808 { 809 "$ref": "#/definitions/Pet" 810 }, 811 { 812 "required": [ 813 "name" 814 ] 815 } 816 ] 817 }, 818 "Tag": { 819 "id": "Tag", 820 "properties": { 821 "id": { 822 "format": "int64", 823 "type": "integer" 824 }, 825 "name": { 826 "type": "string" 827 } 828 } 829 }, 830 "Error": { 831 "required": [ 832 "code", 833 "message" 834 ], 835 "properties": { 836 "code": { 837 "type": "integer", 838 "format": "int32" 839 }, 840 "message": { 841 "type": "string" 842 } 843 } 844 } 845 }, 846 "consumes": [ 847 "application/json", 848 "application/xml" 849 ], 850 "produces": [ 851 "application/json", 852 "application/xml", 853 "text/plain", 854 "text/html" 855 ], 856 "securityDefinitions": { 857 "basic": { 858 "type": "basic" 859 }, 860 "apiKey": { 861 "type": "apiKey", 862 "in": "header", 863 "name": "X-API-KEY" 864 } 865 } 866} 867` 868 869const expectedExpanded = ` 870{ 871 "produces":[ 872 "application/json", 873 "plain/text" 874 ], 875 "schemes":[ 876 "https", 877 "http" 878 ], 879 "swagger":"2.0", 880 "info":{ 881 "description":"Something", 882 "title":"Something", 883 "contact":{ 884 "name":"Somebody", 885 "url":"https://url.com", 886 "email":"email@url.com" 887 }, 888 "version":"v1" 889 }, 890 "host":"security.sonusnet.com", 891 "basePath":"/api", 892 "paths":{ 893 "/whatnot":{ 894 "get":{ 895 "description":"Get something", 896 "responses":{ 897 "200":{ 898 "description":"The something", 899 "schema":{ 900 "description":"A collection of service events", 901 "type":"object", 902 "properties":{ 903 "page":{ 904 "description":"A description of a paged result", 905 "type":"object", 906 "properties":{ 907 "page":{ 908 "description":"the page that was requested", 909 "type":"integer" 910 }, 911 "page_items":{ 912 "description":"the number of items per page requested", 913 "type":"integer" 914 }, 915 "pages":{ 916 "description":"the total number of pages available", 917 "type":"integer" 918 }, 919 "total_items":{ 920 "description":"the total number of items available", 921 "type":"integer", 922 "format":"int64" 923 } 924 } 925 }, 926 "something":{ 927 "description":"Something", 928 "type":"object", 929 "properties":{ 930 "p1":{ 931 "description":"A string", 932 "type":"string" 933 }, 934 "p2":{ 935 "description":"An integer", 936 "type":"integer" 937 } 938 } 939 } 940 } 941 } 942 }, 943 "500":{ 944 "description":"Oops" 945 } 946 } 947 } 948 } 949 }, 950 "definitions":{ 951 "Something":{ 952 "description":"A collection of service events", 953 "type":"object", 954 "properties":{ 955 "page":{ 956 "description":"A description of a paged result", 957 "type":"object", 958 "properties":{ 959 "page":{ 960 "description":"the page that was requested", 961 "type":"integer" 962 }, 963 "page_items":{ 964 "description":"the number of items per page requested", 965 "type":"integer" 966 }, 967 "pages":{ 968 "description":"the total number of pages available", 969 "type":"integer" 970 }, 971 "total_items":{ 972 "description":"the total number of items available", 973 "type":"integer", 974 "format":"int64" 975 } 976 } 977 }, 978 "something":{ 979 "description":"Something", 980 "type":"object", 981 "properties":{ 982 "p1":{ 983 "description":"A string", 984 "type":"string" 985 }, 986 "p2":{ 987 "description":"An integer", 988 "type":"integer" 989 } 990 } 991 } 992 } 993 } 994 } 995} 996` 997 998const cascadeRefExpanded = ` 999{ 1000 "swagger": "2.0", 1001 "consumes":[ 1002 "application/json" 1003 ], 1004 "produces":[ 1005 "application/json" 1006 ], 1007 "schemes":[ 1008 "http" 1009 ], 1010 "info":{ 1011 "description":"recursively following JSON references", 1012 "title":"test 1", 1013 "contact":{ 1014 "name":"Fred" 1015 }, 1016 "version":"0.1.1" 1017 }, 1018 "paths":{ 1019 "/getAll":{ 1020 "get":{ 1021 "operationId":"getAll", 1022 "parameters":[ 1023 { 1024 "description":"max number of results", 1025 "name":"a", 1026 "in":"body", 1027 "schema":{ 1028 "type":"string" 1029 } 1030 } 1031 ], 1032 "responses":{ 1033 "200":{ 1034 "description":"Success", 1035 "schema":{ 1036 "type":"array", 1037 "items":{ 1038 "type":"string" 1039 } 1040 } 1041 } 1042 } 1043 } 1044 } 1045 }, 1046 "definitions":{ 1047 "a":{ 1048 "type":"string" 1049 }, 1050 "b":{ 1051 "type":"array", 1052 "items":{ 1053 "type":"string" 1054 } 1055 } 1056 } 1057} 1058` 1059