├── .codeclimate.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── lib ├── generators │ └── rspec │ │ ├── swagger │ │ ├── USAGE │ │ ├── swagger_generator.rb │ │ └── templates │ │ │ └── spec.rb │ │ └── swagger_install │ │ ├── swagger_install_generator.rb │ │ └── templates │ │ └── spec │ │ └── swagger_helper.rb └── rspec │ └── rails │ ├── swagger.rb │ └── swagger │ ├── configuration.rb │ ├── document.rb │ ├── formatter.rb │ ├── helpers.rb │ ├── request_builder.rb │ ├── response_formatters.rb │ ├── route_parser.rb │ ├── tasks │ └── swagger.rake │ └── version.rb ├── rspec-rails-swagger.gemspec ├── scripts ├── make_site.sh └── run_tests.sh └── spec ├── fixtures └── files │ ├── instagram.yml │ └── minimal.yml ├── rails_helper.rb ├── requests └── request_spec.rb ├── rspec └── rails │ └── swagger │ ├── document_spec.rb │ ├── formatter_spec.rb │ ├── helpers_paths_spec.rb │ ├── helpers_spec.rb │ ├── request_builder_spec.rb │ └── route_parser_spec.rb ├── spec_helper.rb └── swagger_helper.rb /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | exclude_paths: 6 | - spec/ 7 | config: 8 | languages: 9 | - ruby 10 | fixme: 11 | enabled: true 12 | rubocop: 13 | enabled: true 14 | ratings: 15 | paths: 16 | - "**.rb" 17 | exclude_paths: 18 | - lib/generators/rspec/swagger/templates/ 19 | - spec/testapp/ 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # We want to test multiple versions on Travis 2 | Gemfile.lock 3 | 4 | .bundle/ 5 | rspec-rails-swagger-*.gem 6 | spec/testapp/ 7 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | DisabledByDefault: true 3 | 4 | #################### Lint ################################ 5 | 6 | Lint/AmbiguousOperator: 7 | Description: >- 8 | Checks for ambiguous operators in the first argument of a 9 | method invocation without parentheses. 10 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' 11 | Enabled: true 12 | 13 | Lint/AmbiguousRegexpLiteral: 14 | Description: >- 15 | Checks for ambiguous regexp literals in the first argument of 16 | a method invocation without parenthesis. 17 | Enabled: true 18 | 19 | Lint/AssignmentInCondition: 20 | Description: "Don't use assignment in conditions." 21 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' 22 | Enabled: true 23 | 24 | Lint/BlockAlignment: 25 | Description: 'Align block ends correctly.' 26 | Enabled: true 27 | 28 | Lint/CircularArgumentReference: 29 | Description: "Don't refer to the keyword argument in the default value." 30 | Enabled: true 31 | 32 | Lint/ConditionPosition: 33 | Description: >- 34 | Checks for condition placed in a confusing position relative to 35 | the keyword. 36 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' 37 | Enabled: true 38 | 39 | Lint/Debugger: 40 | Description: 'Check for debugger calls.' 41 | Enabled: true 42 | 43 | Lint/DefEndAlignment: 44 | Description: 'Align ends corresponding to defs correctly.' 45 | Enabled: true 46 | 47 | Lint/DeprecatedClassMethods: 48 | Description: 'Check for deprecated class method calls.' 49 | Enabled: true 50 | 51 | Lint/DuplicateMethods: 52 | Description: 'Check for duplicate methods calls.' 53 | Enabled: true 54 | 55 | Lint/EachWithObjectArgument: 56 | Description: 'Check for immutable argument given to each_with_object.' 57 | Enabled: true 58 | 59 | Lint/ElseLayout: 60 | Description: 'Check for odd code arrangement in an else block.' 61 | Enabled: true 62 | 63 | Lint/EmptyEnsure: 64 | Description: 'Checks for empty ensure block.' 65 | Enabled: true 66 | 67 | Lint/EmptyInterpolation: 68 | Description: 'Checks for empty string interpolation.' 69 | Enabled: true 70 | 71 | Lint/EndAlignment: 72 | Description: 'Align ends correctly.' 73 | Enabled: true 74 | 75 | Lint/EndInMethod: 76 | Description: 'END blocks should not be placed inside method definitions.' 77 | Enabled: true 78 | 79 | Lint/EnsureReturn: 80 | Description: 'Do not use return in an ensure block.' 81 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure' 82 | Enabled: true 83 | 84 | Lint/Eval: 85 | Description: 'The use of eval represents a serious security risk.' 86 | Enabled: true 87 | 88 | Lint/FormatParameterMismatch: 89 | Description: 'The number of parameters to format/sprint must match the fields.' 90 | Enabled: true 91 | 92 | Lint/HandleExceptions: 93 | Description: "Don't suppress exception." 94 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' 95 | Enabled: true 96 | 97 | Lint/InvalidCharacterLiteral: 98 | Description: >- 99 | Checks for invalid character literals with a non-escaped 100 | whitespace character. 101 | Enabled: true 102 | 103 | Lint/LiteralInCondition: 104 | Description: 'Checks of literals used in conditions.' 105 | Enabled: true 106 | 107 | Lint/LiteralInInterpolation: 108 | Description: 'Checks for literals used in interpolation.' 109 | Enabled: true 110 | 111 | Lint/Loop: 112 | Description: >- 113 | Use Kernel#loop with break rather than begin/end/until or 114 | begin/end/while for post-loop tests. 115 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' 116 | Enabled: true 117 | 118 | Lint/NestedMethodDefinition: 119 | Description: 'Do not use nested method definitions.' 120 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' 121 | Enabled: true 122 | 123 | Lint/NonLocalExitFromIterator: 124 | Description: 'Do not use return in iterator to cause non-local exit.' 125 | Enabled: true 126 | 127 | Lint/ParenthesesAsGroupedExpression: 128 | Description: >- 129 | Checks for method calls with a space before the opening 130 | parenthesis. 131 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 132 | Enabled: true 133 | 134 | Lint/RequireParentheses: 135 | Description: >- 136 | Use parentheses in the method call to avoid confusion 137 | about precedence. 138 | Enabled: true 139 | 140 | Lint/RescueException: 141 | Description: 'Avoid rescuing the Exception class.' 142 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' 143 | Enabled: true 144 | 145 | Lint/ShadowingOuterLocalVariable: 146 | Description: >- 147 | Do not use the same name as outer local variable 148 | for block arguments or block local variables. 149 | Enabled: true 150 | 151 | Lint/StringConversionInInterpolation: 152 | Description: 'Checks for Object#to_s usage in string interpolation.' 153 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s' 154 | Enabled: true 155 | 156 | Lint/UnderscorePrefixedVariableName: 157 | Description: 'Do not use prefix `_` for a variable that is used.' 158 | Enabled: true 159 | 160 | Lint/UnneededDisable: 161 | Description: >- 162 | Checks for rubocop:disable comments that can be removed. 163 | Note: this cop is not disabled when disabling all cops. 164 | It must be explicitly disabled. 165 | Enabled: true 166 | 167 | Lint/UnusedBlockArgument: 168 | Description: 'Checks for unused block arguments.' 169 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 170 | Enabled: true 171 | 172 | Lint/UnusedMethodArgument: 173 | Description: 'Checks for unused method arguments.' 174 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 175 | Enabled: true 176 | 177 | Lint/UnreachableCode: 178 | Description: 'Unreachable code.' 179 | Enabled: true 180 | 181 | Lint/UselessAccessModifier: 182 | Description: 'Checks for useless access modifiers.' 183 | Enabled: true 184 | 185 | Lint/UselessAssignment: 186 | Description: 'Checks for useless assignment to a local variable.' 187 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' 188 | Enabled: true 189 | 190 | Lint/UselessComparison: 191 | Description: 'Checks for comparison of something with itself.' 192 | Enabled: true 193 | 194 | Lint/UselessElseWithoutRescue: 195 | Description: 'Checks for useless `else` in `begin..end` without `rescue`.' 196 | Enabled: true 197 | 198 | Lint/UselessSetterCall: 199 | Description: 'Checks for useless setter call to a local variable.' 200 | Enabled: true 201 | 202 | Lint/Void: 203 | Description: 'Possible use of operator/literal/variable in void context.' 204 | Enabled: true 205 | 206 | ###################### Metrics #################################### 207 | 208 | Metrics/AbcSize: 209 | Description: >- 210 | A calculated magnitude based on number of assignments, 211 | branches, and conditions. 212 | Reference: 'http://c2.com/cgi/wiki?AbcMetric' 213 | Enabled: false 214 | Max: 20 215 | 216 | Metrics/BlockNesting: 217 | Description: 'Avoid excessive block nesting' 218 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' 219 | Enabled: true 220 | Max: 4 221 | 222 | Metrics/ClassLength: 223 | Description: 'Avoid classes longer than 250 lines of code.' 224 | Enabled: true 225 | Max: 250 226 | 227 | Metrics/CyclomaticComplexity: 228 | Description: >- 229 | A complexity metric that is strongly correlated to the number 230 | of test cases needed to validate a method. 231 | Enabled: true 232 | 233 | Metrics/LineLength: 234 | Description: 'Limit lines to 80 characters.' 235 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' 236 | Enabled: false 237 | 238 | Metrics/MethodLength: 239 | Description: 'Avoid methods longer than 30 lines of code.' 240 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' 241 | Enabled: true 242 | Max: 30 243 | 244 | Metrics/ModuleLength: 245 | Description: 'Avoid modules longer than 250 lines of code.' 246 | Enabled: true 247 | Max: 250 248 | 249 | Metrics/ParameterLists: 250 | Description: 'Avoid parameter lists longer than three or four parameters.' 251 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' 252 | Enabled: true 253 | 254 | Metrics/PerceivedComplexity: 255 | Description: >- 256 | A complexity metric geared towards measuring complexity for a 257 | human reader. 258 | Enabled: false 259 | 260 | ##################### Performance ############################# 261 | 262 | Performance/Count: 263 | Description: >- 264 | Use `count` instead of `select...size`, `reject...size`, 265 | `select...count`, `reject...count`, `select...length`, 266 | and `reject...length`. 267 | Enabled: true 268 | 269 | Performance/Detect: 270 | Description: >- 271 | Use `detect` instead of `select.first`, `find_all.first`, 272 | `select.last`, and `find_all.last`. 273 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' 274 | Enabled: true 275 | 276 | Performance/FlatMap: 277 | Description: >- 278 | Use `Enumerable#flat_map` 279 | instead of `Enumerable#map...Array#flatten(1)` 280 | or `Enumberable#collect..Array#flatten(1)` 281 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code' 282 | Enabled: true 283 | EnabledForFlattenWithoutParams: false 284 | # If enabled, this cop will warn about usages of 285 | # `flatten` being called without any parameters. 286 | # This can be dangerous since `flat_map` will only flatten 1 level, and 287 | # `flatten` without any parameters can flatten multiple levels. 288 | 289 | Performance/ReverseEach: 290 | Description: 'Use `reverse_each` instead of `reverse.each`.' 291 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' 292 | Enabled: true 293 | 294 | Performance/Sample: 295 | Description: >- 296 | Use `sample` instead of `shuffle.first`, 297 | `shuffle.last`, and `shuffle[Fixnum]`. 298 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' 299 | Enabled: true 300 | 301 | Performance/Size: 302 | Description: >- 303 | Use `size` instead of `count` for counting 304 | the number of elements in `Array` and `Hash`. 305 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code' 306 | Enabled: true 307 | 308 | Performance/StringReplacement: 309 | Description: >- 310 | Use `tr` instead of `gsub` when you are replacing the same 311 | number of characters. Use `delete` instead of `gsub` when 312 | you are deleting characters. 313 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code' 314 | Enabled: true 315 | 316 | ##################### Rails ################################## 317 | 318 | Rails/ActionFilter: 319 | Description: 'Enforces consistent use of action filter methods.' 320 | Enabled: false 321 | 322 | Rails/Date: 323 | Description: >- 324 | Checks the correct usage of date aware methods, 325 | such as Date.today, Date.current etc. 326 | Enabled: false 327 | 328 | Rails/Delegate: 329 | Description: 'Prefer delegate method for delegations.' 330 | Enabled: false 331 | 332 | Rails/FindBy: 333 | Description: 'Prefer find_by over where.first.' 334 | Enabled: false 335 | 336 | Rails/FindEach: 337 | Description: 'Prefer all.find_each over all.find.' 338 | Enabled: false 339 | 340 | Rails/HasAndBelongsToMany: 341 | Description: 'Prefer has_many :through to has_and_belongs_to_many.' 342 | Enabled: false 343 | 344 | Rails/Output: 345 | Description: 'Checks for calls to puts, print, etc.' 346 | Enabled: false 347 | 348 | Rails/ReadWriteAttribute: 349 | Description: >- 350 | Checks for read_attribute(:attr) and 351 | write_attribute(:attr, val). 352 | Enabled: false 353 | 354 | Rails/ScopeArgs: 355 | Description: 'Checks the arguments of ActiveRecord scopes.' 356 | Enabled: false 357 | 358 | Rails/TimeZone: 359 | Description: 'Checks the correct usage of time zone aware methods.' 360 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time' 361 | Reference: 'http://danilenko.org/2012/7/6/rails_timezones' 362 | Enabled: false 363 | 364 | Rails/Validation: 365 | Description: 'Use validates :attribute, hash of validations.' 366 | Enabled: false 367 | 368 | ################## Style ################################# 369 | 370 | Style/AccessModifierIndentation: 371 | Description: Check indentation of private/protected visibility modifiers. 372 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected' 373 | Enabled: false 374 | 375 | Style/AccessorMethodName: 376 | Description: Check the naming of accessor methods for get_/set_. 377 | Enabled: false 378 | 379 | Style/Alias: 380 | Description: 'Use alias_method instead of alias.' 381 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' 382 | Enabled: false 383 | 384 | Style/AlignArray: 385 | Description: >- 386 | Align the elements of an array literal if they span more than 387 | one line. 388 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays' 389 | Enabled: false 390 | 391 | Style/AlignHash: 392 | Description: >- 393 | Align the elements of a hash literal if they span more than 394 | one line. 395 | Enabled: false 396 | 397 | Style/AlignParameters: 398 | Description: >- 399 | Align the parameters of a method call if they span more 400 | than one line. 401 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' 402 | Enabled: false 403 | 404 | Style/AndOr: 405 | Description: 'Use &&/|| instead of and/or.' 406 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or' 407 | Enabled: false 408 | 409 | Style/ArrayJoin: 410 | Description: 'Use Array#join instead of Array#*.' 411 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' 412 | Enabled: false 413 | 414 | Style/AsciiComments: 415 | Description: 'Use only ascii symbols in comments.' 416 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' 417 | Enabled: false 418 | 419 | Style/AsciiIdentifiers: 420 | Description: 'Use only ascii symbols in identifiers.' 421 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' 422 | Enabled: false 423 | 424 | Style/Attr: 425 | Description: 'Checks for uses of Module#attr.' 426 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' 427 | Enabled: false 428 | 429 | Style/BeginBlock: 430 | Description: 'Avoid the use of BEGIN blocks.' 431 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks' 432 | Enabled: false 433 | 434 | Style/BarePercentLiterals: 435 | Description: 'Checks if usage of %() or %Q() matches configuration.' 436 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand' 437 | Enabled: false 438 | 439 | Style/BlockComments: 440 | Description: 'Do not use block comments.' 441 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments' 442 | Enabled: false 443 | 444 | Style/BlockEndNewline: 445 | Description: 'Put end statement of multiline block on its own line.' 446 | Enabled: false 447 | 448 | Style/BlockDelimiters: 449 | Description: >- 450 | Avoid using {...} for multi-line blocks (multiline chaining is 451 | always ugly). 452 | Prefer {...} over do...end for single-line blocks. 453 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 454 | Enabled: false 455 | 456 | Style/BracesAroundHashParameters: 457 | Description: 'Enforce braces style around hash parameters.' 458 | Enabled: false 459 | 460 | Style/CaseEquality: 461 | Description: 'Avoid explicit use of the case equality operator(===).' 462 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' 463 | Enabled: false 464 | 465 | Style/CaseIndentation: 466 | Description: 'Indentation of when in a case/when/[else/]end.' 467 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case' 468 | Enabled: false 469 | 470 | Style/CharacterLiteral: 471 | Description: 'Checks for uses of character literals.' 472 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' 473 | Enabled: false 474 | 475 | Style/ClassAndModuleCamelCase: 476 | Description: 'Use CamelCase for classes and modules.' 477 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes' 478 | Enabled: false 479 | 480 | Style/ClassAndModuleChildren: 481 | Description: 'Checks style of children classes and modules.' 482 | Enabled: false 483 | 484 | Style/ClassCheck: 485 | Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.' 486 | Enabled: false 487 | 488 | Style/ClassMethods: 489 | Description: 'Use self when defining module/class methods.' 490 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-class-methods' 491 | Enabled: false 492 | 493 | Style/ClassVars: 494 | Description: 'Avoid the use of class variables.' 495 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' 496 | Enabled: false 497 | 498 | Style/ClosingParenthesisIndentation: 499 | Description: 'Checks the indentation of hanging closing parentheses.' 500 | Enabled: false 501 | 502 | Style/ColonMethodCall: 503 | Description: 'Do not use :: for method call.' 504 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' 505 | Enabled: false 506 | 507 | Style/CommandLiteral: 508 | Description: 'Use `` or %x around command literals.' 509 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x' 510 | Enabled: false 511 | 512 | Style/CommentAnnotation: 513 | Description: 'Checks formatting of annotation comments.' 514 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' 515 | Enabled: false 516 | 517 | Style/CommentIndentation: 518 | Description: 'Indentation of comments.' 519 | Enabled: false 520 | 521 | Style/ConstantName: 522 | Description: 'Constants should use SCREAMING_SNAKE_CASE.' 523 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case' 524 | Enabled: false 525 | 526 | Style/DefWithParentheses: 527 | Description: 'Use def with parentheses when there are arguments.' 528 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' 529 | Enabled: false 530 | 531 | Style/PreferredHashMethods: 532 | Description: 'Checks for use of deprecated Hash methods.' 533 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key' 534 | Enabled: false 535 | 536 | Style/Documentation: 537 | Description: 'Document classes and non-namespace modules.' 538 | Enabled: false 539 | 540 | Style/DotPosition: 541 | Description: 'Checks the position of the dot in multi-line method calls.' 542 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' 543 | Enabled: false 544 | 545 | Style/DoubleNegation: 546 | Description: 'Checks for uses of double negation (!!).' 547 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' 548 | Enabled: false 549 | 550 | Style/EachWithObject: 551 | Description: 'Prefer `each_with_object` over `inject` or `reduce`.' 552 | Enabled: false 553 | 554 | Style/ElseAlignment: 555 | Description: 'Align elses and elsifs correctly.' 556 | Enabled: false 557 | 558 | Style/EmptyElse: 559 | Description: 'Avoid empty else-clauses.' 560 | Enabled: false 561 | 562 | Style/EmptyLineBetweenDefs: 563 | Description: 'Use empty lines between defs.' 564 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods' 565 | Enabled: false 566 | 567 | Style/EmptyLines: 568 | Description: "Don't use several empty lines in a row." 569 | Enabled: false 570 | 571 | Style/EmptyLinesAroundAccessModifier: 572 | Description: "Keep blank lines around access modifiers." 573 | Enabled: false 574 | 575 | Style/EmptyLinesAroundBlockBody: 576 | Description: "Keeps track of empty lines around block bodies." 577 | Enabled: false 578 | 579 | Style/EmptyLinesAroundClassBody: 580 | Description: "Keeps track of empty lines around class bodies." 581 | Enabled: false 582 | 583 | Style/EmptyLinesAroundModuleBody: 584 | Description: "Keeps track of empty lines around module bodies." 585 | Enabled: false 586 | 587 | Style/EmptyLinesAroundMethodBody: 588 | Description: "Keeps track of empty lines around method bodies." 589 | Enabled: false 590 | 591 | Style/EmptyLiteral: 592 | Description: 'Prefer literals to Array.new/Hash.new/String.new.' 593 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' 594 | Enabled: false 595 | 596 | Style/EndBlock: 597 | Description: 'Avoid the use of END blocks.' 598 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks' 599 | Enabled: false 600 | 601 | Style/EndOfLine: 602 | Description: 'Use Unix-style line endings.' 603 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf' 604 | Enabled: false 605 | 606 | Style/EvenOdd: 607 | Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' 608 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 609 | Enabled: false 610 | 611 | Style/ExtraSpacing: 612 | Description: 'Do not use unnecessary spacing.' 613 | Enabled: false 614 | 615 | Style/FileName: 616 | Description: 'Use snake_case for source file names.' 617 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' 618 | Enabled: false 619 | 620 | Style/InitialIndentation: 621 | Description: >- 622 | Checks the indentation of the first non-blank non-comment line in a file. 623 | Enabled: false 624 | 625 | Style/FirstParameterIndentation: 626 | Description: 'Checks the indentation of the first parameter in a method call.' 627 | Enabled: false 628 | 629 | Style/FlipFlop: 630 | Description: 'Checks for flip flops' 631 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' 632 | Enabled: false 633 | 634 | Style/For: 635 | Description: 'Checks use of for or each in multiline loops.' 636 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops' 637 | Enabled: false 638 | 639 | Style/FormatString: 640 | Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' 641 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' 642 | Enabled: false 643 | 644 | Style/GlobalVars: 645 | Description: 'Do not introduce global variables.' 646 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' 647 | Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html' 648 | Enabled: false 649 | 650 | Style/GuardClause: 651 | Description: 'Check for conditionals that can be replaced with guard clauses' 652 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 653 | Enabled: false 654 | 655 | Style/HashSyntax: 656 | Description: >- 657 | Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax 658 | { :a => 1, :b => 2 }. 659 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals' 660 | Enabled: false 661 | 662 | Style/IfUnlessModifier: 663 | Description: >- 664 | Favor modifier if/unless usage when you have a 665 | single-line body. 666 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' 667 | Enabled: false 668 | 669 | Style/IfWithSemicolon: 670 | Description: 'Do not use if x; .... Use the ternary operator instead.' 671 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' 672 | Enabled: false 673 | 674 | Style/IndentationConsistency: 675 | Description: 'Keep indentation straight.' 676 | Enabled: false 677 | 678 | Style/IndentationWidth: 679 | Description: 'Use 2 spaces for indentation.' 680 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' 681 | Enabled: true 682 | 683 | Style/IndentArray: 684 | Description: >- 685 | Checks the indentation of the first element in an array 686 | literal. 687 | Enabled: false 688 | 689 | Style/IndentHash: 690 | Description: 'Checks the indentation of the first key in a hash literal.' 691 | Enabled: false 692 | 693 | Style/InfiniteLoop: 694 | Description: 'Use Kernel#loop for infinite loops.' 695 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop' 696 | Enabled: false 697 | 698 | Style/Lambda: 699 | Description: 'Use the new lambda literal syntax for single-line blocks.' 700 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' 701 | Enabled: false 702 | 703 | Style/LambdaCall: 704 | Description: 'Use lambda.call(...) instead of lambda.(...).' 705 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' 706 | Enabled: false 707 | 708 | Style/LeadingCommentSpace: 709 | Description: 'Comments should start with a space.' 710 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space' 711 | Enabled: true 712 | 713 | Style/LineEndConcatenation: 714 | Description: >- 715 | Use \ instead of + or << to concatenate two string literals at 716 | line end. 717 | Enabled: false 718 | 719 | Style/MethodDefParentheses: 720 | Description: >- 721 | Checks if the method definitions have or don't have 722 | parentheses. 723 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' 724 | Enabled: false 725 | 726 | Style/MethodName: 727 | Description: 'Use the configured style when naming methods.' 728 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' 729 | Enabled: false 730 | 731 | Style/ModuleFunction: 732 | Description: 'Checks for usage of `extend self` in modules.' 733 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' 734 | Enabled: false 735 | 736 | Style/MultilineBlockChain: 737 | Description: 'Avoid multi-line chains of blocks.' 738 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 739 | Enabled: false 740 | 741 | Style/MultilineBlockLayout: 742 | Description: 'Ensures newlines after multiline block do statements.' 743 | Enabled: false 744 | 745 | Style/MultilineIfThen: 746 | Description: 'Do not use then for multi-line if/unless.' 747 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then' 748 | Enabled: false 749 | 750 | Style/MultilineOperationIndentation: 751 | Description: >- 752 | Checks indentation of binary operations that span more than 753 | one line. 754 | Enabled: false 755 | 756 | Style/MultilineTernaryOperator: 757 | Description: >- 758 | Avoid multi-line ?: (the ternary operator); 759 | use if/unless instead. 760 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary' 761 | Enabled: false 762 | 763 | Style/NegatedIf: 764 | Description: >- 765 | Favor unless over if for negative conditions 766 | (or control flow or). 767 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' 768 | Enabled: false 769 | 770 | Style/NegatedWhile: 771 | Description: 'Favor until over while for negative conditions.' 772 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' 773 | Enabled: false 774 | 775 | Style/NestedTernaryOperator: 776 | Description: 'Use one expression per branch in a ternary operator.' 777 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary' 778 | Enabled: false 779 | 780 | Style/Next: 781 | Description: 'Use `next` to skip iteration instead of a condition at the end.' 782 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 783 | Enabled: false 784 | 785 | Style/NilComparison: 786 | Description: 'Prefer x.nil? to x == nil.' 787 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 788 | Enabled: false 789 | 790 | Style/NonNilCheck: 791 | Description: 'Checks for redundant nil checks.' 792 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks' 793 | Enabled: false 794 | 795 | Style/Not: 796 | Description: 'Use ! instead of not.' 797 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' 798 | Enabled: false 799 | 800 | Style/NumericLiterals: 801 | Description: >- 802 | Add underscores to large numeric literals to improve their 803 | readability. 804 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' 805 | Enabled: false 806 | 807 | Style/OneLineConditional: 808 | Description: >- 809 | Favor the ternary operator(?:) over 810 | if/then/else/end constructs. 811 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' 812 | Enabled: false 813 | 814 | Style/OpMethod: 815 | Description: 'When defining binary operators, name the argument other.' 816 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' 817 | Enabled: false 818 | 819 | Style/OptionalArguments: 820 | Description: >- 821 | Checks for optional arguments that do not appear at the end 822 | of the argument list 823 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#optional-arguments' 824 | Enabled: false 825 | 826 | Style/ParallelAssignment: 827 | Description: >- 828 | Check for simple usages of parallel assignment. 829 | It will only warn when the number of variables 830 | matches on both sides of the assignment. 831 | This also provides performance benefits 832 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment' 833 | Enabled: false 834 | 835 | Style/ParenthesesAroundCondition: 836 | Description: >- 837 | Don't use parentheses around the condition of an 838 | if/unless/while. 839 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if' 840 | Enabled: false 841 | 842 | Style/PercentLiteralDelimiters: 843 | Description: 'Use `%`-literal delimiters consistently' 844 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' 845 | Enabled: false 846 | 847 | Style/PercentQLiterals: 848 | Description: 'Checks if uses of %Q/%q match the configured preference.' 849 | Enabled: false 850 | 851 | Style/PerlBackrefs: 852 | Description: 'Avoid Perl-style regex back references.' 853 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' 854 | Enabled: false 855 | 856 | Style/PredicateName: 857 | Description: 'Check the names of predicate methods.' 858 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' 859 | Enabled: false 860 | 861 | Style/Proc: 862 | Description: 'Use proc instead of Proc.new.' 863 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' 864 | Enabled: false 865 | 866 | Style/RaiseArgs: 867 | Description: 'Checks the arguments passed to raise/fail.' 868 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' 869 | Enabled: false 870 | 871 | Style/RedundantBegin: 872 | Description: "Don't use begin blocks when they are not needed." 873 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit' 874 | Enabled: false 875 | 876 | Style/RedundantException: 877 | Description: "Checks for an obsolete RuntimeException argument in raise/fail." 878 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror' 879 | Enabled: false 880 | 881 | Style/RedundantReturn: 882 | Description: "Don't use return where it's not required." 883 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return' 884 | Enabled: false 885 | 886 | Style/RedundantSelf: 887 | Description: "Don't use self where it's not needed." 888 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required' 889 | Enabled: false 890 | 891 | Style/RegexpLiteral: 892 | Description: 'Use / or %r around regular expressions.' 893 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' 894 | Enabled: false 895 | 896 | Style/RescueEnsureAlignment: 897 | Description: 'Align rescues and ensures correctly.' 898 | Enabled: false 899 | 900 | Style/RescueModifier: 901 | Description: 'Avoid using rescue in its modifier form.' 902 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers' 903 | Enabled: false 904 | 905 | Style/SelfAssignment: 906 | Description: >- 907 | Checks for places where self-assignment shorthand should have 908 | been used. 909 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' 910 | Enabled: false 911 | 912 | Style/Semicolon: 913 | Description: "Don't use semicolons to terminate expressions." 914 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon' 915 | Enabled: false 916 | 917 | Style/SignalException: 918 | Description: 'Checks for proper usage of fail and raise.' 919 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' 920 | Enabled: false 921 | 922 | Style/SingleLineBlockParams: 923 | Description: 'Enforces the names of some block params.' 924 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' 925 | Enabled: false 926 | 927 | Style/SingleLineMethods: 928 | Description: 'Avoid single-line methods.' 929 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' 930 | Enabled: false 931 | 932 | Style/SpaceBeforeFirstArg: 933 | Description: >- 934 | Checks that exactly one space is used between a method name 935 | and the first argument for method calls without parentheses. 936 | Enabled: true 937 | 938 | Style/SpaceAfterColon: 939 | Description: 'Use spaces after colons.' 940 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 941 | Enabled: false 942 | 943 | Style/SpaceAfterComma: 944 | Description: 'Use spaces after commas.' 945 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 946 | Enabled: false 947 | 948 | Style/SpaceAroundKeyword: 949 | Description: 'Use spaces around keywords.' 950 | Enabled: false 951 | 952 | Style/SpaceAfterMethodName: 953 | Description: >- 954 | Do not put a space between a method name and the opening 955 | parenthesis in a method definition. 956 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 957 | Enabled: false 958 | 959 | Style/SpaceAfterNot: 960 | Description: Tracks redundant space after the ! operator. 961 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang' 962 | Enabled: false 963 | 964 | Style/SpaceAfterSemicolon: 965 | Description: 'Use spaces after semicolons.' 966 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 967 | Enabled: false 968 | 969 | Style/SpaceBeforeBlockBraces: 970 | Description: >- 971 | Checks that the left block brace has or doesn't have space 972 | before it. 973 | Enabled: false 974 | 975 | Style/SpaceBeforeComma: 976 | Description: 'No spaces before commas.' 977 | Enabled: false 978 | 979 | Style/SpaceBeforeComment: 980 | Description: >- 981 | Checks for missing space between code and a comment on the 982 | same line. 983 | Enabled: false 984 | 985 | Style/SpaceBeforeSemicolon: 986 | Description: 'No spaces before semicolons.' 987 | Enabled: false 988 | 989 | Style/SpaceInsideBlockBraces: 990 | Description: >- 991 | Checks that block braces have or don't have surrounding space. 992 | For blocks taking parameters, checks that the left brace has 993 | or doesn't have trailing space. 994 | Enabled: false 995 | 996 | Style/SpaceAroundBlockParameters: 997 | Description: 'Checks the spacing inside and after block parameters pipes.' 998 | Enabled: false 999 | 1000 | Style/SpaceAroundEqualsInParameterDefault: 1001 | Description: >- 1002 | Checks that the equals signs in parameter default assignments 1003 | have or don't have surrounding space depending on 1004 | configuration. 1005 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals' 1006 | Enabled: false 1007 | 1008 | Style/SpaceAroundOperators: 1009 | Description: 'Use a single space around operators.' 1010 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 1011 | Enabled: false 1012 | 1013 | Style/SpaceInsideBrackets: 1014 | Description: 'No spaces after [ or before ].' 1015 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' 1016 | Enabled: false 1017 | 1018 | Style/SpaceInsideHashLiteralBraces: 1019 | Description: "Use spaces inside hash literal braces - or don't." 1020 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' 1021 | Enabled: false 1022 | 1023 | Style/SpaceInsideParens: 1024 | Description: 'No spaces after ( or before ).' 1025 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' 1026 | Enabled: false 1027 | 1028 | Style/SpaceInsideRangeLiteral: 1029 | Description: 'No spaces inside range literals.' 1030 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals' 1031 | Enabled: false 1032 | 1033 | Style/SpaceInsideStringInterpolation: 1034 | Description: 'Checks for padding/surrounding spaces inside string interpolation.' 1035 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#string-interpolation' 1036 | Enabled: false 1037 | 1038 | Style/SpecialGlobalVars: 1039 | Description: 'Avoid Perl-style global variables.' 1040 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' 1041 | Enabled: false 1042 | 1043 | Style/StringLiterals: 1044 | Description: 'Checks if uses of quotes match the configured preference.' 1045 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' 1046 | Enabled: false 1047 | 1048 | Style/StringLiteralsInInterpolation: 1049 | Description: >- 1050 | Checks if uses of quotes inside expressions in interpolated 1051 | strings match the configured preference. 1052 | Enabled: false 1053 | 1054 | Style/StructInheritance: 1055 | Description: 'Checks for inheritance from Struct.new.' 1056 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-extend-struct-new' 1057 | Enabled: false 1058 | 1059 | Style/SymbolLiteral: 1060 | Description: 'Use plain symbols instead of string symbols when possible.' 1061 | Enabled: false 1062 | 1063 | Style/SymbolProc: 1064 | Description: 'Use symbols as procs instead of blocks when possible.' 1065 | Enabled: false 1066 | 1067 | Style/Tab: 1068 | Description: 'No hard tabs.' 1069 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' 1070 | Enabled: true 1071 | 1072 | Style/TrailingBlankLines: 1073 | Description: 'Checks trailing blank lines and final newline.' 1074 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof' 1075 | Enabled: false 1076 | 1077 | Style/TrailingCommaInArguments: 1078 | Description: 'Checks for trailing comma in parameter lists.' 1079 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-params-comma' 1080 | Enabled: false 1081 | 1082 | Style/TrailingCommaInLiteral: 1083 | Description: 'Checks for trailing comma in literals.' 1084 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' 1085 | Enabled: false 1086 | 1087 | Style/TrailingWhitespace: 1088 | Description: 'Avoid trailing whitespace.' 1089 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace' 1090 | Enabled: true 1091 | 1092 | Style/TrivialAccessors: 1093 | Description: 'Prefer attr_* methods to trivial readers/writers.' 1094 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' 1095 | Enabled: false 1096 | 1097 | Style/UnlessElse: 1098 | Description: >- 1099 | Do not use unless with else. Rewrite these with the positive 1100 | case first. 1101 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless' 1102 | Enabled: false 1103 | 1104 | Style/UnneededCapitalW: 1105 | Description: 'Checks for %W when interpolation is not needed.' 1106 | Enabled: false 1107 | 1108 | Style/UnneededPercentQ: 1109 | Description: 'Checks for %q/%Q when single quotes or double quotes would do.' 1110 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q' 1111 | Enabled: false 1112 | 1113 | Style/TrailingUnderscoreVariable: 1114 | Description: >- 1115 | Checks for the usage of unneeded trailing underscores at the 1116 | end of parallel variable assignment. 1117 | Enabled: false 1118 | 1119 | Style/VariableInterpolation: 1120 | Description: >- 1121 | Don't interpolate global, instance and class variables 1122 | directly in strings. 1123 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' 1124 | Enabled: false 1125 | 1126 | Style/VariableName: 1127 | Description: 'Use the configured style when naming variables.' 1128 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' 1129 | Enabled: false 1130 | 1131 | Style/WhenThen: 1132 | Description: 'Use when x then ... for one-line cases.' 1133 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' 1134 | Enabled: false 1135 | 1136 | Style/WhileUntilDo: 1137 | Description: 'Checks for redundant do after while or until.' 1138 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do' 1139 | Enabled: false 1140 | 1141 | Style/WhileUntilModifier: 1142 | Description: >- 1143 | Favor modifier while/until usage when you have a 1144 | single-line body. 1145 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' 1146 | Enabled: false 1147 | 1148 | Style/WordArray: 1149 | Description: 'Use %w or %W for arrays of words.' 1150 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' 1151 | Enabled: false 1152 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.6 4 | - 2.7 5 | - 3.0 6 | env: 7 | - RAILS_VERSION=5.2.4.4 8 | - RAILS_VERSION=6.1.0 9 | cache: bundler 10 | install: scripts/make_site.sh 11 | script: scripts/run_tests.sh 12 | matrix: 13 | exclude: 14 | - rvm: 3.0 15 | env: RAILS_VERSION=5.2.4.4 16 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This code of conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at drewish@katherinehouse.com. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 45 | version 1.3.0, available at 46 | [http://contributor-covenant.org/version/1/3/0/][version] 47 | 48 | [homepage]: http://contributor-covenant.org 49 | [version]: http://contributor-covenant.org/version/1/3/0/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Running tests 4 | 5 | The `scripts/make_site.sh` script will create a test site for a specific version of 6 | Rails: 7 | ``` 8 | export RAILS_VERSION=4.2.0 9 | scripts/make_site.sh 10 | ``` 11 | 12 | Once the test site is created you can run the tests: 13 | ``` 14 | scripts/run_tests.sh 15 | ``` 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | # Allow the rails version to come from an ENV setting so Travis can test multiple versions. 6 | rails_version = ENV['RAILS_VERSION'] || '5.2.4.4' 7 | 8 | gem 'bootsnap' 9 | gem 'rails', "~> #{rails_version}" 10 | gem 'pry' 11 | gem 'pry-byebug' 12 | gem 'sqlite3' 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 andrew morton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RSpec Rails Swagger 2 | 3 | [![Build Status](https://travis-ci.org/drewish/rspec-rails-swagger.svg?branch=master)](https://travis-ci.org/drewish/rspec-rails-swagger) 4 | [![Code Climate](https://codeclimate.com/github/drewish/rspec-rails-swagger/badges/gpa.svg)](https://codeclimate.com/github/drewish/rspec-rails-swagger) 5 | 6 | This gem helps you generate Swagger docs by using RSpec to document the paths. 7 | You execute a command to run the tests and generate the `.yaml` or `.json` output. 8 | Running the tests ensures that your API and docs are in agreement, and generates 9 | output that can be saved as response examples. 10 | 11 | The design of this was heavily influenced by the awesome [swagger_rails gem](https://github.com/domaindrivendev/swagger_rails). 12 | 13 | ## Setup 14 | 15 | Add the gem to your Rails app's `Gemfile`: 16 | ```rb 17 | group :development, :test do 18 | gem 'rspec-rails-swagger' 19 | end 20 | ``` 21 | 22 | Update your bundle: 23 | ``` 24 | bundle install 25 | ``` 26 | 27 | If you don't have a `spec/rails_helper.rb` file: 28 | ``` 29 | rails generate rspec:install 30 | ``` 31 | 32 | Create the `spec/swagger_helper.rb` file: 33 | ``` 34 | rails generate rspec:swagger_install 35 | ``` 36 | 37 | ## Documenting Your API 38 | 39 | Now you can edit `spec/swagger_helper.rb` and start filling in the top level 40 | Swagger documentation, e.g. basePath, [definitions](http://swagger.io/specification/#definitionsObject), 41 | [parameters](http://swagger.io/specification/#parametersDefinitionsObject), 42 | [tags](http://swagger.io/specification/#tagObject), etc. 43 | 44 | You can use the generator to create a spec to documentation a controller: 45 | 46 | ``` 47 | rails generate rspec:swagger PostsController 48 | ``` 49 | 50 | That will create a `spec/requests/posts_spec.rb` file with the paths, operations 51 | and some default requests filled in. With the structure in place you should only 52 | need to add `before` calls to create records and then update the `let`s to 53 | return the appropriate values. 54 | 55 | ## Generate the JSON or YAML 56 | 57 | To create the Swagger files use the rake task: 58 | 59 | ``` 60 | bundle exec rake swagger 61 | ``` 62 | 63 | Now you can use Swagger UI or the renderer of your choice to display the 64 | formatted documentation. [swagger_engine](https://github.com/batdevis/swagger_engine) 65 | works pretty well and supports multiple documents. 66 | 67 | ## RSpec DSL 68 | 69 | The DSL follows the hierarchy of the Swagger Schema: 70 | 71 | - [Paths Object](http://swagger.io/specification/#paths-object-29) 72 | - [Path Item Object](http://swagger.io/specification/#path-item-object-32) 73 | - [Parameter Object](http://swagger.io/specification/#parameter-object-44)s (Optional) 74 | - [Operation Object](http://swagger.io/specification/#operation-object-36) 75 | - [Parameter Object](http://swagger.io/specification/#parameter-object-44)s (Optional) 76 | - [Responses Object](http://swagger.io/specification/#responses-object-54) 77 | - [Response Object](http://swagger.io/specification/#response-object-58) 78 | - [Example Object](http://swagger.io/specification/#example-object-65)s (Optional) 79 | 80 | Here's an example of a spec with comments to for the corresponding objects: 81 | 82 | ```rb 83 | require 'swagger_helper' 84 | 85 | # Paths Object 86 | RSpec.describe "Posts Controller", type: :request do 87 | before { Post.new.save } 88 | 89 | # Path Item Object 90 | path '/posts' do 91 | # Operation Object 92 | operation "GET", summary: "fetch list" do 93 | # Response Object 94 | response 200, description: "successful" 95 | end 96 | end 97 | 98 | # Path Object 99 | path '/posts/{post_id}' do 100 | # Parameter Object 101 | parameter "post_id", { in: :path, type: :integer } 102 | let(:post_id) { 1 } 103 | 104 | # Operation Object 105 | get summary: "fetch item" do 106 | # Response Object 107 | response 200, description: "success" 108 | end 109 | end 110 | 111 | # Path Post Object 112 | path '/posts/' do 113 | # Parameter Object for content type could be defined like: 114 | consumes 'application/json' 115 | # or: 116 | parameter 'Content-Type', { in: :header, type: :string } 117 | let(:'Content-Type') { 'application/json' } 118 | # one of them would be considered 119 | 120 | # authorization token in the header: 121 | parameter 'Authorization', { in: :header, type: :string } 122 | let(:'Authorization') { 'Bearer ' } 123 | 124 | # Parameter Object 125 | parameter "post_id", { in: :path, type: :integer } 126 | let(:post_id) { 1 } 127 | 128 | # Parameter Object for Body 129 | parameter "body", { in: :body, required: true, schema: { 130 | type: :object, 131 | properties: { 132 | title: { type: :string }, 133 | author_email: { type: :email } 134 | } 135 | } 136 | let (:body) { 137 | { post: 138 | { title: 'my example', 139 | author_email: 'me@example.com' } 140 | } 141 | } 142 | } 143 | # checkout http://swagger.io/specification/#parameter-object-44 for more information, options and details 144 | 145 | # Operation Object 146 | post summary: "update an item" do 147 | # Response Object 148 | response 200, description: "success" 149 | end 150 | # ... 151 | end 152 | ``` 153 | 154 | 155 | ### Paths Object 156 | These methods are available inside of an RSpec contexts with the `type: :request` tag. 157 | 158 | #### `path(template, attributes = {}, &block)` 159 | Defines a new Path Item. 160 | 161 | The `attributes` parameter accepts: 162 | - `swagger_doc` a key in `RSpec.configuration.swagger_docs` that determines 163 | which file the path belongs in. 164 | - `tags` with an array of tags that will be applied to the child Operations. 165 | 166 | You can also provide a default file and tags by setting them on a parent RSpec 167 | context block: 168 | ```rb 169 | RSpec.describe "Sample Requests", type: :request do 170 | # The swagger_doc will be used as a default for the paths defined within the 171 | # context block. Similarly, the tags will be merged with those set on paths 172 | # defined within them. 173 | context "setting defaults", swagger_doc: 'default_document.json', tags: [:context_tag] do 174 | path '/posts', swagger_doc: 'overridden_document.yaml' tags: ['path_tag'] do 175 | operation "GET", summary: "fetch list" do 176 | produces 'application/json' 177 | tags 'operation_tag' 178 | 179 | response(200, { description: "successful" }) 180 | end 181 | end 182 | end 183 | end 184 | ``` 185 | The `GET /posts` operation in this example will be saved in the `'overridden_document.yaml'` 186 | file and tagged with `["context_tag", "path_tag", "operation_tag"]`. 187 | 188 | 189 | ### Path Item Object 190 | These methods are available inside of blocks passed to the `path` method. 191 | 192 | #### `operation(method, attributes = {}, &block)` 193 | Defines a new Operation Object. The `method` is case insensitive. 194 | 195 | The `attributes` parameter accepts: 196 | - `tags` with an array of tags. These will be merged with tags passed to the 197 | Path Item or `tags` method inside the Operation's block. 198 | 199 | #### `delete(attributes = {}, &block)` 200 | Alias for `operation(:delete, attributes, block)`. 201 | 202 | #### `get(attributes = {}, &block)` 203 | Alias for `operation(:get, attributes, block)`. 204 | 205 | #### `head(attributes = {}, &block)` 206 | Alias for `operation(:head, attributes, block)`. 207 | 208 | #### `options(attributes = {}, &block)` 209 | Alias for `operation(:options, attributes, block)`. 210 | 211 | #### `patch(attributes = {}, &block)` 212 | Alias for `operation(:patch, attributes, block)`. 213 | 214 | #### `post(attributes = {}, &block)` 215 | Alias for `operation(:post, attributes, block)`. 216 | 217 | #### `put(attributes = {}, &block)` 218 | Alias for `operation(:put, attributes, block)`. 219 | 220 | 221 | ### Parameters 222 | These methods are available inside of blocks passed to the `path` or `operation` method. 223 | 224 | #### `parameter(name, attributes = {})` 225 | Defines a new Parameter Object. You can define the parameter inline: 226 | ```rb 227 | parameter :callback_url, in: :query, type: :string, required: true 228 | ``` 229 | 230 | Or, via reference: 231 | ```rb 232 | parameter ref: "#/parameters/site_id" 233 | ``` 234 | 235 | Values for the parameters are set using `let`: 236 | ```rb 237 | post summary: "create" do 238 | parameter "body", in: :body, schema: { foo: :bar } 239 | let(:body) { { post: { title: 'asdf', body: "blah" } } } 240 | # ... 241 | end 242 | ``` 243 | 244 | 245 | ### Operation Object 246 | These methods are available inside of blocks passed to the `operation` method. 247 | 248 | #### `consumes(*mime_types)` 249 | Use this to add MIME types that are specific to the operation. They will be merged 250 | with the Swagger Object's consumes field. 251 | ```rb 252 | consumes 'application/json', 'application/xml' 253 | ``` 254 | 255 | #### `produces(*mime_types)` 256 | Use this to add MIME types that are specific to the operation. They will be merged 257 | with the Swagger Object's consumes field. 258 | ```rb 259 | produces 'application/json', 'application/xml' 260 | ``` 261 | 262 | #### `response(status_code, attributes = {}, &block)` 263 | Defines a new Response Object. `status_code` must be between 1 and 599. `attributes` 264 | must include a `description`. 265 | 266 | #### `tags(*tags)` 267 | Adds operation specific tags. 268 | ```rb 269 | tags :accounts, :pets 270 | ``` 271 | 272 | You can also provide tags through the RSpec context block and/or `path` method: 273 | ```rb 274 | RSpec.describe "Sample Requests", type: :request, tags: [:context_tag] do 275 | path '/posts', tags: ['path_tag'] do 276 | operation "GET", summary: "fetch list" do 277 | produces 'application/json' 278 | tags 'operation_tag' 279 | 280 | response(200, { description: "successful" }) 281 | end 282 | end 283 | end 284 | ``` 285 | These tags will be merged with those of the operation. The `GET /posts` operation 286 | in this example will be tagged with `["context_tag", "path_tag", "operation_tag"]`. 287 | 288 | 289 | ### Response Object 290 | These methods are available inside of blocks passed to the `response` method. 291 | 292 | #### `capture_example()` 293 | This method will capture the response body from the test and create an Example 294 | Object for the Response. 295 | 296 | You could also set this in an RSpec context block if you'd like examples for 297 | multiple operations or paths: 298 | ```rb 299 | describe 'Connections', type: :request, capture_examples: true do 300 | # Any requests in this block will capture example responses 301 | end 302 | ``` 303 | 304 | #### `schema(definition)` 305 | Sets the schema field for the Response Object. You can define it inline: 306 | ```rb 307 | schema( 308 | type: :array, 309 | items: { 310 | type: :object, 311 | properties: { 312 | id: { type: :string }, 313 | name: { type: :string }, 314 | }, 315 | } 316 | ) 317 | ``` 318 | 319 | Or, by reference: 320 | ```rb 321 | schema ref: '#/definitions/Account' 322 | ``` 323 | -------------------------------------------------------------------------------- /lib/generators/rspec/swagger/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | This creates an RSpec request spec to define Swagger documentation for a 3 | controller. It will create a test for each of the controller's methods. 4 | 5 | Example: 6 | rails generate rspec:swagger V3::AccountsController 7 | 8 | This will create: 9 | spec/requests/v3/accounts_spec.rb 10 | -------------------------------------------------------------------------------- /lib/generators/rspec/swagger/swagger_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | 3 | module Rspec 4 | module Generators 5 | class SwaggerGenerator < ::Rails::Generators::NamedBase 6 | source_root File.expand_path('../templates', __FILE__) 7 | 8 | def setup 9 | @routes = RSpec::Rails::Swagger::RouteParser.new(controller_path).routes 10 | end 11 | 12 | def create_spec_file 13 | template 'spec.rb', File.join('spec/requests', "#{controller_path}_spec.rb") 14 | end 15 | 16 | private 17 | 18 | def controller_path 19 | file_path.chomp('_controller') 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/generators/rspec/swagger/templates/spec.rb: -------------------------------------------------------------------------------- 1 | require 'swagger_helper' 2 | 3 | RSpec.describe '<%= controller_path %>', type: :request do 4 | <% @routes.each do | template, path_item | %> 5 | path '<%= template %>' do 6 | <% unless path_item[:params].empty? -%> 7 | # You'll want to customize the parameter types... 8 | <% path_item[:params].each do |param| -%> 9 | parameter '<%= param %>', in: :path, type: :string 10 | <% end -%> 11 | # ...and values used to make the requests. 12 | <% path_item[:params].each do |param| -%> 13 | let(:<%= param %>) { '123' } 14 | <% end -%> 15 | 16 | <% end -%> 17 | <% path_item[:actions].each do | action, details | -%> 18 | <%= action %>(summary: '<%= details[:summary] %>') do 19 | response(200, description: 'successful') do 20 | # You can add before/let blocks here to trigger the response code 21 | end 22 | end 23 | <% end -%> 24 | end 25 | <% end -%> 26 | end 27 | -------------------------------------------------------------------------------- /lib/generators/rspec/swagger_install/swagger_install_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | 3 | module Rspec 4 | module Generators 5 | class SwaggerInstallGenerator < ::Rails::Generators::Base 6 | source_root File.expand_path('../templates', __FILE__) 7 | 8 | def copy_swagger_helper 9 | template 'spec/swagger_helper.rb' 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/generators/rspec/swagger_install/templates/spec/swagger_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/rails/swagger' 2 | require 'rails_helper' 3 | 4 | RSpec.configure do |config| 5 | # Specify a root directory where the generated Swagger files will be saved. 6 | config.swagger_root = Rails.root.to_s + '/swagger' 7 | 8 | # Define one or more Swagger documents and global metadata for each. 9 | config.swagger_docs = { 10 | 'v1/swagger.json' => { 11 | swagger: '2.0', 12 | info: { 13 | title: 'API V1', 14 | version: 'v1' 15 | } 16 | } 17 | } 18 | end 19 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/core' 2 | require 'rspec/rails/swagger/configuration' 3 | require 'rspec/rails/swagger/document' 4 | require 'rspec/rails/swagger/formatter' 5 | require 'rspec/rails/swagger/helpers' 6 | require 'rspec/rails/swagger/response_formatters' 7 | require 'rspec/rails/swagger/request_builder' 8 | require 'rspec/rails/swagger/route_parser' 9 | require 'rspec/rails/swagger/version' 10 | 11 | module RSpec 12 | module Rails 13 | module Swagger 14 | initialize_configuration RSpec.configuration 15 | 16 | if defined?(::Rails) 17 | class Railtie < ::Rails::Railtie 18 | rake_tasks do 19 | load 'rspec/rails/swagger/tasks/swagger.rake' 20 | end 21 | generators do 22 | require "generators/rspec/swagger/swagger_generator.rb" 23 | end 24 | end 25 | end 26 | end 27 | end 28 | end 29 | 30 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger/configuration.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Rails 3 | module Swagger 4 | # Fake class to document RSpec Swagger configuration options. 5 | class Configuration 6 | end 7 | 8 | def self.initialize_configuration(config) 9 | config.add_setting :swagger_root 10 | config.add_setting :swagger_docs, default: {} 11 | 12 | Helpers.add_swagger_type_configurations(config) 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger/document.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Rails 3 | module Swagger 4 | class Document 5 | attr_accessor :data 6 | 7 | def initialize(data) 8 | @data = data.deep_symbolize_keys 9 | end 10 | 11 | def [](value) 12 | data[value] 13 | end 14 | 15 | ## 16 | # Look up parameter or definition references. 17 | def resolve_ref(ref) 18 | unless %r{#/(?parameters|definitions)/(?.+)} =~ ref 19 | raise ArgumentError, "Invalid reference: #{ref}" 20 | end 21 | 22 | result = data.fetch(location.to_sym, {})[name.to_sym] 23 | raise ArgumentError, "Reference value does not exist: #{ref}" unless result 24 | 25 | if location == 'parameters' 26 | result.merge(name: name) 27 | end 28 | 29 | result 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger/formatter.rb: -------------------------------------------------------------------------------- 1 | RSpec::Support.require_rspec_core "formatters/base_text_formatter" 2 | RSpec::Support.require_rspec_core "formatters/console_codes" 3 | 4 | module RSpec 5 | module Rails 6 | module Swagger 7 | class Formatter < RSpec::Core::Formatters::BaseTextFormatter 8 | RSpec::Core::Formatters.register self, :example_group_started, 9 | :example_passed, :example_pending, :example_failed, :example_finished, 10 | :close 11 | 12 | def documents 13 | # We don't try to load the docs in `initalize` because when running 14 | # `rspec -f RSpec::Swagger::Formatter` RSpec initalized this class 15 | # before `swagger_helper` has run. 16 | @documents ||= ::RSpec.configuration.swagger_docs 17 | end 18 | 19 | def example_group_started(notification) 20 | output.print(*group_output(notification)) 21 | end 22 | 23 | def example_passed(notification) 24 | output.print(RSpec::Core::Formatters::ConsoleCodes.wrap(example_output(notification), :success)) 25 | end 26 | 27 | def example_pending(notification) 28 | output.print(RSpec::Core::Formatters::ConsoleCodes.wrap(example_output(notification), :pending)) 29 | end 30 | 31 | def example_failed(notification) 32 | output.print(RSpec::Core::Formatters::ConsoleCodes.wrap(example_output(notification), :failure)) 33 | end 34 | 35 | def example_finished(notification) 36 | metadata = notification.example.metadata 37 | return unless metadata[:swagger_object] == :response 38 | 39 | # Then add everything to the document 40 | document = document_for(metadata[:swagger_doc]) 41 | path_item = path_item_for(document, metadata[:swagger_path_item]) 42 | operation = operation_for(path_item, metadata[:swagger_operation]) 43 | response = response_for(operation, metadata[:swagger_response]) 44 | end 45 | 46 | def close(_notification) 47 | documents.each{|k, v| write_file(k, v)} 48 | 49 | self 50 | end 51 | 52 | private 53 | 54 | def group_output(notification) 55 | metadata = notification.group.metadata 56 | 57 | # This is a little odd because I didn't want to split the logic across 58 | # a start and end method. Instead we just start a new line for each 59 | # path and operation and just let the status codes pile up on the end. 60 | # There's probably a better way that doesn't have the initial newline. 61 | case metadata[:swagger_object] 62 | when :path_item 63 | ["\n", metadata[:swagger_path_item][:path]] 64 | when :operation 65 | ["\n ", "%-8s" % metadata[:swagger_operation][:method]] 66 | end 67 | end 68 | 69 | def example_output(notification) 70 | " #{notification.example.metadata[:swagger_response][:status_code]}" 71 | end 72 | 73 | def write_file(name, document) 74 | output = 75 | if %w(.yaml .yml).include? File.extname(name) 76 | YAML.dump(deep_stringify(document)) 77 | else 78 | JSON.pretty_generate(document) + "\n" 79 | end 80 | 81 | # It would be good to at least warn if the name includes some '../' that 82 | # takes it out of root directory. 83 | target = Pathname(name).expand_path(::RSpec.configuration.swagger_root) 84 | target.dirname.mkpath 85 | target.write(output) 86 | end 87 | 88 | # Converts hash keys and symbolic values into strings. 89 | # 90 | # Based on ActiveSupport's Hash _deep_transform_keys_in_object 91 | def deep_stringify(object) 92 | case object 93 | when Hash 94 | object.each_with_object({}) do |(key, value), result| 95 | result[key.to_s] = deep_stringify(value) 96 | end 97 | when Array 98 | object.map { |e| deep_stringify(e) } 99 | when Symbol 100 | object.to_s 101 | else 102 | object 103 | end 104 | end 105 | 106 | def document_for(doc_name = nil) 107 | if doc_name 108 | documents.fetch(doc_name) 109 | else 110 | documents.values.first 111 | end 112 | end 113 | 114 | def path_item_for(document, swagger_path_item) 115 | name = swagger_path_item[:path] 116 | 117 | document[:paths] ||= {} 118 | document[:paths][name] ||= {} 119 | if swagger_path_item[:parameters] 120 | document[:paths][name][:parameters] = prepare_parameters(swagger_path_item[:parameters]) 121 | end 122 | document[:paths][name] 123 | end 124 | 125 | def operation_for(path, swagger_operation) 126 | method = swagger_operation[:method] 127 | 128 | path[method] ||= {responses: {}} 129 | path[method].tap do |operation| 130 | if swagger_operation[:parameters] 131 | operation[:parameters] = prepare_parameters(swagger_operation[:parameters]) 132 | end 133 | operation.merge!(swagger_operation.slice( 134 | :tags, :summary, :description, :externalDocs, :operationId, 135 | :consumes, :produces, :schemes, :deprecated, :security 136 | )) 137 | end 138 | end 139 | 140 | def response_for(operation, swagger_response) 141 | status = swagger_response[:status_code] 142 | 143 | operation[:responses][status] ||= {} 144 | operation[:responses][status].tap do |response| 145 | if swagger_response[:examples] 146 | response[:examples] = prepare_examples(swagger_response[:examples]) 147 | end 148 | response.merge!(swagger_response.slice(:description, :schema, :headers)) 149 | end 150 | end 151 | 152 | def prepare_parameters(params) 153 | params.values 154 | end 155 | 156 | def prepare_examples(examples) 157 | examples.each_pair do |format, resp| 158 | examples[format] = ResponseFormatters[format].call(resp) 159 | end 160 | 161 | examples 162 | end 163 | end 164 | end 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger/helpers.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Rails 3 | module Swagger 4 | module Helpers 5 | # paths: (Paths) 6 | # /pets: (Path Item) 7 | # post: (Operation) 8 | # tags: 9 | # - pet 10 | # summary: Add a new pet to the store 11 | # description: "" 12 | # operationId: addPet 13 | # consumes: 14 | # - application/json 15 | # produces: 16 | # - application/json 17 | # parameters: (Parameters) 18 | # - in: body 19 | # name: body 20 | # description: Pet object that needs to be added to the store 21 | # required: false 22 | # schema: 23 | # $ref: "#/definitions/Pet" 24 | # responses: (Responses) 25 | # "405": (Response) 26 | # description: Invalid input 27 | 28 | # The helpers serve as a DSL. 29 | def self.add_swagger_type_configurations(config) 30 | # The filters are used to ensure that the methods are nested correctly 31 | # and following the Swagger schema. 32 | config.extend Paths, type: :request 33 | config.extend PathItem, swagger_object: :path_item 34 | config.extend Parameters, swagger_object: :path_item 35 | config.extend Operation, swagger_object: :operation 36 | config.extend Parameters, swagger_object: :operation 37 | config.extend Response, swagger_object: :response 38 | end 39 | 40 | module Paths 41 | def path template, attributes = {}, &block 42 | attributes.symbolize_keys! 43 | 44 | raise ArgumentError, "Path must start with a /" unless template.starts_with?('/') 45 | #TODO template might be a $ref 46 | meta = { 47 | swagger_object: :path_item, 48 | swagger_doc: attributes[:swagger_doc] || default_document, 49 | swagger_path_item: {path: template}, 50 | } 51 | # Merge tags passed into the path with those from parent contexts. 52 | if attributes[:tags] 53 | meta[:tags] = (metadata.try(:[], :tags) || []) + attributes[:tags] 54 | end 55 | describe(template, meta, &block) 56 | end 57 | 58 | private 59 | 60 | def default_document 61 | metadata.try(:[], :swagger_doc) || RSpec.configuration.swagger_docs.keys.first 62 | end 63 | end 64 | 65 | module PathItem 66 | METHODS = %w(get put post delete options head patch).freeze 67 | 68 | def operation method, attributes = {}, &block 69 | attributes.symbolize_keys! 70 | # Include tags from parent contexts so you can tag all the paths 71 | # in a controller at once. 72 | if metadata.try(:[], :tags).present? 73 | attributes[:tags] ||= [] 74 | attributes[:tags] += metadata[:tags] 75 | end 76 | 77 | method = method.to_s.downcase 78 | validate_method! method 79 | 80 | meta = { 81 | swagger_object: :operation, 82 | swagger_operation: attributes.merge(method: method.to_sym).reject{ |v| v.nil? } 83 | } 84 | describe(method.to_s, meta, &block) 85 | end 86 | 87 | METHODS.each do |method| 88 | define_method(method) do |attributes = {}, &block| 89 | operation(method, attributes, &block) 90 | end 91 | end 92 | 93 | private 94 | 95 | def validate_method! method 96 | unless METHODS.include? method.to_s 97 | raise ArgumentError, "Operation has an invalid 'method' value. Try: #{METHODS}." 98 | end 99 | end 100 | end 101 | 102 | module Parameters 103 | def parameter name, attributes = {} 104 | attributes.symbolize_keys! 105 | 106 | # Look for $refs 107 | if name.respond_to?(:has_key?) 108 | ref = name.delete(:ref) || name.delete('ref') 109 | full_param = resolve_document(metadata).resolve_ref(ref) 110 | 111 | validate_parameter! full_param 112 | 113 | param = { '$ref' => ref } 114 | key = parameter_key(full_param) 115 | else 116 | validate_parameter! attributes 117 | 118 | # Path attributes are always required 119 | attributes[:required] = true if attributes[:in] == :path 120 | 121 | param = { name: name.to_s }.merge(attributes) 122 | key = parameter_key(param) 123 | end 124 | 125 | parameters_for_object[key] = param 126 | end 127 | 128 | def resolve_document metadata 129 | # TODO: It's really inefficient to keep recreating this. It'd be nice 130 | # if we could cache them some place. 131 | name = metadata[:swagger_doc] 132 | Document.new(RSpec.configuration.swagger_docs[name]) 133 | end 134 | 135 | private 136 | 137 | # This key ensures uniqueness based on the 'name' and 'in' values. 138 | def parameter_key parameter 139 | "#{parameter[:in]}&#{parameter[:name]}" 140 | end 141 | 142 | def parameters_for_object 143 | object_key = "swagger_#{metadata[:swagger_object]}".to_sym 144 | object_data = metadata[object_key] ||= {} 145 | object_data[:parameters] ||= {} 146 | end 147 | 148 | def validate_parameter! attributes 149 | validate_location! attributes[:in] 150 | 151 | if attributes[:in].to_s == 'body' 152 | unless attributes[:schema].present? 153 | raise ArgumentError, "Parameter is missing required 'schema' value." 154 | end 155 | else 156 | validate_type! attributes[:type] 157 | end 158 | end 159 | 160 | def validate_location! location 161 | unless location.present? 162 | raise ArgumentError, "Parameter is missing required 'in' value." 163 | end 164 | 165 | locations = %w(query header path formData body) 166 | unless locations.include? location.to_s 167 | raise ArgumentError, "Parameter has an invalid 'in' value. Try: #{locations}." 168 | end 169 | end 170 | 171 | def validate_type! type 172 | unless type.present? 173 | raise ArgumentError, "Parameter is missing required 'type' value." 174 | end 175 | 176 | types = %w(string number integer boolean array file) 177 | unless types.include? type.to_s 178 | raise ArgumentError, "Parameter has an invalid 'type' value. Try: #{types}." 179 | end 180 | end 181 | end 182 | 183 | module Operation 184 | def consumes *mime_types 185 | metadata[:swagger_operation][:consumes] = mime_types 186 | end 187 | 188 | def produces *mime_types 189 | metadata[:swagger_operation][:produces] = mime_types 190 | end 191 | 192 | def tags *tags 193 | metadata[:swagger_operation][:tags] ||= [] 194 | metadata[:swagger_operation][:tags] += tags 195 | end 196 | 197 | def response status_code, attributes = {}, &block 198 | attributes.symbolize_keys! 199 | 200 | validate_status_code! status_code 201 | validate_description! attributes[:description] 202 | 203 | meta = { 204 | swagger_object: :response, 205 | swagger_response: attributes.merge(status_code: status_code) 206 | } 207 | describe(status_code, meta) do 208 | self.module_exec(&block) if block_given? 209 | 210 | # To make a request we need: 211 | # - the details we've collected in the metadata 212 | # - parameter values defined using let() 213 | # RSpec tries to limit access to metadata inside of it() / before() 214 | # / after() blocks but that scope is the only place you can access 215 | # the let() values. The solution the swagger_rails dev came up with 216 | # is to use the example.metadata passed into the block with the 217 | # block's scope which has access to the let() values. 218 | before do |example| 219 | builder = RequestBuilder.new(example.metadata, self) 220 | method = builder.method 221 | path = [builder.path, builder.query].join 222 | headers = builder.headers 223 | env = builder.env 224 | body = builder.body 225 | 226 | # Run the request 227 | if ::Rails::VERSION::MAJOR >= 5 228 | self.send(method, path, params: body, headers: headers, env: env) 229 | else 230 | self.send(method, path, body, headers.merge(env)) 231 | end 232 | 233 | if example.metadata[:capture_examples] 234 | examples = example.metadata[:swagger_response][:examples] ||= {} 235 | examples[response.content_type.to_s] = response.body 236 | end 237 | end 238 | 239 | # TODO: see if we can get the caller to show up in the error 240 | # backtrace for this test. 241 | it("returns the correct status code") do 242 | expect(response).to have_http_status(status_code) 243 | end 244 | end 245 | end 246 | 247 | private 248 | 249 | def validate_status_code! status_code 250 | unless status_code == :default || (100..599).cover?(status_code) 251 | raise ArgumentError, "status_code must be an integer 100 to 599, or :default" 252 | end 253 | end 254 | 255 | def validate_description! description 256 | unless description.present? 257 | raise ArgumentError, "Response is missing required 'description' value." 258 | end 259 | end 260 | end 261 | 262 | module Response 263 | def capture_example 264 | metadata[:capture_examples] = true 265 | end 266 | 267 | def schema definition 268 | definition.symbolize_keys! 269 | 270 | ref = definition.delete(:ref) 271 | schema = ref ? { '$ref' => ref } : definition 272 | 273 | metadata[:swagger_response][:schema] = schema 274 | end 275 | end 276 | end 277 | end 278 | end 279 | end 280 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger/request_builder.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Rails 3 | module Swagger 4 | class RequestBuilder 5 | attr_reader :metadata, :instance 6 | 7 | ## 8 | # Creates a new RequestBuilder from the Example class's +metadata+ hash 9 | # and a test +instance+ that we can use to populate the parameter 10 | # values. 11 | def initialize(metadata, instance) 12 | @metadata, @instance = metadata, instance 13 | end 14 | 15 | ## 16 | # Finds the Document associated with this request so things like schema 17 | # and parameter references can be resolved. 18 | def document 19 | @document ||= begin 20 | Document.new(RSpec.configuration.swagger_docs[metadata[:swagger_doc]]) 21 | end 22 | end 23 | 24 | def method 25 | metadata[:swagger_operation][:method] 26 | end 27 | 28 | def produces 29 | Array(metadata[:swagger_operation][:produces]).presence || Array(document[:produces]) 30 | end 31 | 32 | def consumes 33 | Array(metadata[:swagger_operation][:consumes]).presence || Array(document[:consumes]) 34 | end 35 | 36 | ## 37 | # Returns parameters defined in the operation and path item. Providing 38 | # a +location+ will filter the parameters to those with a matching +in+ 39 | # value. 40 | def parameters location = nil 41 | path_item = metadata[:swagger_path_item] || {} 42 | operation = metadata[:swagger_operation] || {} 43 | params = path_item.fetch(:parameters, {}).merge(operation.fetch(:parameters, {})) 44 | if location.present? 45 | params.select{ |k, _| k.starts_with? "#{location}&" } 46 | else 47 | params 48 | end 49 | end 50 | 51 | def parameter_values location 52 | values = parameters(location). 53 | map{ |_, p| p['$ref'] ? document.resolve_ref(p['$ref']) : p }. 54 | select{ |p| p[:required] || instance.respond_to?(p[:name]) }. 55 | map{ |p| [p[:name], instance.send(p[:name])] } 56 | Hash[values] 57 | end 58 | 59 | def headers 60 | headers = {} 61 | 62 | # Match the names that Rails uses internally 63 | headers['HTTP_ACCEPT'] = produces.first if produces.present? 64 | headers['CONTENT_TYPE'] = consumes.first if consumes.present? 65 | 66 | # TODO: do we need to do some capitalization to match the rack 67 | # conventions? 68 | parameter_values(:header).each { |k, v| headers[k] = v } 69 | 70 | headers 71 | end 72 | 73 | ## 74 | # If +instance+ defines an +env+ method this will return those values 75 | # for inclusion in the Rack env hash. 76 | def env 77 | return {} unless instance.respond_to? :env 78 | 79 | instance.env 80 | end 81 | 82 | def path 83 | base_path = document[:basePath] || '' 84 | # Find params in the path and replace them with values defined in 85 | # in the example group. 86 | base_path + metadata[:swagger_path_item][:path].gsub(/(\{.*?\})/) do |match| 87 | # QUESTION: Should check that the parameter is actually defined in 88 | # `parameters` before fetch a value? 89 | instance.send(match[1...-1]) 90 | end 91 | end 92 | 93 | def query 94 | query_params = parameter_values(:query).to_query 95 | "?#{query_params}" unless query_params.blank? 96 | end 97 | 98 | def body 99 | # And here all we need is the first half of the key to find the body 100 | # parameter and its name to fetch a value. 101 | key = parameters(:body).keys.first 102 | if key 103 | instance.send(key.split('&').last).to_json 104 | end 105 | end 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger/response_formatters.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Rails 3 | module Swagger 4 | class ResponseFormatters 5 | 6 | class JSON 7 | def call(resp) 8 | if resp.kind_of? String 9 | ::JSON.parse(resp) 10 | else 11 | resp 12 | end 13 | rescue ::JSON::ParserError 14 | resp 15 | end 16 | end 17 | 18 | @formatters = { 19 | :default => ->(resp) { resp }, 20 | 'application/json' => JSON.new 21 | } 22 | 23 | class << self 24 | 25 | def register(format, callable) 26 | @formatters[format] = callable 27 | end 28 | 29 | def [](key) 30 | @formatters[key] || @formatters[:default] 31 | end 32 | end 33 | 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger/route_parser.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Rails 3 | module Swagger 4 | class RouteParser 5 | attr_reader :controller 6 | 7 | def initialize(controller) 8 | @controller = controller 9 | end 10 | 11 | def routes 12 | ::Rails.application.routes.routes.select do |route| 13 | route.defaults[:controller] == controller 14 | end.reduce({}) do |tree, route| 15 | path = path_from(route) 16 | verb = verb_from(route) 17 | tree[path] ||= { params: params_from(route), actions: {} } 18 | tree[path][:actions][verb] = { summary: summary_from(route) } 19 | tree 20 | end 21 | end 22 | 23 | private 24 | 25 | def path_from(route) 26 | route.path.spec.to_s 27 | .chomp('(.:format)') # Ignore any format suffix 28 | .gsub(/:([^\/.?]+)/, '{\1}') # Convert :id to {id} 29 | end 30 | 31 | def verb_from(route) 32 | verb = route.verb 33 | if verb.kind_of? String 34 | verb.downcase 35 | else 36 | verb.source.gsub(/[$^]/, '').downcase 37 | end 38 | end 39 | 40 | def summary_from(route) 41 | verb = route.requirements[:action] 42 | noun = route.requirements[:controller].split('/').last.singularize 43 | 44 | # Apply a few customizations to make things more readable 45 | case verb 46 | when 'index' 47 | verb = 'list' 48 | noun = noun.pluralize 49 | when 'destroy' 50 | verb = 'delete' 51 | end 52 | 53 | "#{verb} #{noun}" 54 | end 55 | 56 | def params_from(route) 57 | route.segments - ['format'] 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger/tasks/swagger.rake: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | desc "Regenerate Swagger docs" 4 | RSpec::Core::RakeTask.new(:swagger) do |t| 5 | t.verbose = false 6 | t.rspec_opts = "-f RSpec::Rails::Swagger::Formatter --order defined -t swagger_object" 7 | end 8 | -------------------------------------------------------------------------------- /lib/rspec/rails/swagger/version.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Rails 3 | # Version information for RSpec Swagger. 4 | module Swagger 5 | module Version 6 | STRING = '1.0.0' 7 | end 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /rspec-rails-swagger.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) 2 | require "rspec/rails/swagger/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'rspec-rails-swagger' 6 | s.version = RSpec::Rails::Swagger::Version::STRING 7 | s.licenses = ['MIT'] 8 | s.summary = "Generate Swagger docs from RSpec integration tests" 9 | s.description = "Inspired by swagger_rails" 10 | s.author = "andrew morton" 11 | s.email = 'drewish@katherinehouse.com' 12 | s.files = Dir['*.md', '*.txt', 'lib/**/*'] 13 | s.homepage = 'https://github.com/drewish/rspec-rails-swagger' 14 | 15 | s.required_ruby_version = '>= 2.0' 16 | s.add_runtime_dependency 'rspec-rails', '~> 4.0' 17 | end 18 | -------------------------------------------------------------------------------- /scripts/make_site.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x -e 3 | 4 | major=$(echo $RAILS_VERSION | cut -d '.' -f1) 5 | 6 | rm Gemfile.lock || true 7 | bundle install 8 | 9 | rm -r spec/testapp || true 10 | bundle exec rails new spec/testapp --api -d sqlite3 --skip-gemfile --skip-bundle --skip-test-unit --skip-action-mailer --skip-puma --skip-action-cable --skip-sprockets --skip-javascript --skip-spring --skip-listen 11 | cd spec/testapp 12 | 13 | bundle exec rails generate scaffold Post title:string body:text 14 | rm -r spec || true 15 | 16 | if [ $major -eq 5 ] 17 | then 18 | bundle exec rails db:create 19 | bundle exec rails db:migrate RAILS_ENV=test 20 | else 21 | bundle exec rake db:create 22 | bundle exec rake db:migrate RAILS_ENV=test 23 | fi 24 | 25 | cd - 26 | -------------------------------------------------------------------------------- /scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x -e 3 | 4 | bundle exec rspec 5 | # Duplicating the body of the rake task. Need to figure out how to call it directly. 6 | bundle exec rspec -f RSpec::Rails::Swagger::Formatter --order defined -t swagger_object 7 | -------------------------------------------------------------------------------- /spec/fixtures/files/instagram.yml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: '2.0' 3 | 4 | ################################################################################ 5 | # API Information # 6 | ################################################################################ 7 | info: 8 | version: v1 9 | title: Instagram API 10 | description: | 11 | The first version of the Instagram API is an exciting step forward towards 12 | making it easier for users to have open access to their data. We created it 13 | so that you can surface the amazing content Instagram users share every 14 | second, in fun and innovative ways. 15 | 16 | Build something great! 17 | 18 | Once you've 19 | [registered your client](http://instagram.com/developer/register/) it's easy 20 | to start requesting data from Instagram. 21 | 22 | All endpoints are only accessible via https and are located at 23 | `api.instagram.com`. For instance: you can grab the most popular photos at 24 | the moment by accessing the following URL with your client ID 25 | (replace CLIENT-ID with your own): 26 | ``` 27 | https://api.instagram.com/v1/media/popular?client_id=CLIENT-ID 28 | ``` 29 | You're best off using an access_token for the authenticated user for each 30 | endpoint, though many endpoints don't require it. 31 | In some cases an access_token will give you more access to information, and 32 | in all cases, it means that you are operating under a per-access_token limit 33 | vs. the same limit for your single client_id. 34 | 35 | 36 | ## Limits 37 | Be nice. If you're sending too many requests too quickly, we'll send back a 38 | `503` error code (server unavailable). 39 | You are limited to 5000 requests per hour per `access_token` or `client_id` 40 | overall. Practically, this means you should (when possible) authenticate 41 | users so that limits are well outside the reach of a given user. 42 | 43 | ## Deleting Objects 44 | We do our best to have all our URLs be 45 | [RESTful](http://en.wikipedia.org/wiki/Representational_state_transfer). 46 | Every endpoint (URL) may support one of four different http verbs. GET 47 | requests fetch information about an object, POST requests create objects, 48 | PUT requests update objects, and finally DELETE requests will delete 49 | objects. 50 | 51 | Since many old browsers don't support PUT or DELETE, we've made it easy to 52 | fake PUTs and DELETEs. All you have to do is do a POST with _method=PUT or 53 | _method=DELETE as a parameter and we will treat it as if you used PUT or 54 | DELETE respectively. 55 | 56 | ## Structure 57 | 58 | ### The Envelope 59 | Every response is contained by an envelope. That is, each response has a 60 | predictable set of keys with which you can expect to interact: 61 | ```json 62 | { 63 | "meta": { 64 | "code": 200 65 | }, 66 | "data": { 67 | ... 68 | }, 69 | "pagination": { 70 | "next_url": "...", 71 | "next_max_id": "13872296" 72 | } 73 | } 74 | ``` 75 | 76 | #### META 77 | The meta key is used to communicate extra information about the response to 78 | the developer. If all goes well, you'll only ever see a code key with value 79 | 200. However, sometimes things go wrong, and in that case you might see a 80 | response like: 81 | ```json 82 | { 83 | "meta": { 84 | "error_type": "OAuthException", 85 | "code": 400, 86 | "error_message": "..." 87 | } 88 | } 89 | ``` 90 | 91 | #### DATA 92 | The data key is the meat of the response. It may be a list or dictionary, 93 | but either way this is where you'll find the data you requested. 94 | #### PAGINATION 95 | Sometimes you just can't get enough. For this reason, we've provided a 96 | convenient way to access more data in any request for sequential data. 97 | Simply call the url in the next_url parameter and we'll respond with the 98 | next set of data. 99 | ```json 100 | { 101 | ... 102 | "pagination": { 103 | "next_url": "https://api.instagram.com/v1/tags/puppy/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&max_id=13872296", 104 | "next_max_id": "13872296" 105 | } 106 | } 107 | ``` 108 | On views where pagination is present, we also support the "count" parameter. 109 | Simply set this to the number of items you'd like to receive. Note that the 110 | default values should be fine for most applications - but if you decide to 111 | increase this number there is a maximum value defined on each endpoint. 112 | 113 | ### JSONP 114 | If you're writing an AJAX application, and you'd like to wrap our response 115 | with a callback, all you have to do is specify a callback parameter with 116 | any API call: 117 | ``` 118 | https://api.instagram.com/v1/tags/coffee/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&callback=callbackFunction 119 | ``` 120 | Would respond with: 121 | ```js 122 | callbackFunction({ 123 | ... 124 | }); 125 | ``` 126 | termsOfService: http://instagram.com/about/legal/terms/api 127 | 128 | ################################################################################ 129 | # Host, Base Path, Schemes and Content Types # 130 | ################################################################################ 131 | host: api.instagram.com 132 | basePath: /v1 133 | schemes: 134 | - https 135 | produces: 136 | - application/json 137 | consumes: 138 | - application/json 139 | 140 | ################################################################################ 141 | # Tags # 142 | ################################################################################ 143 | tags: 144 | - name: Users 145 | - name: Relationships 146 | description: | 147 | Relationships are expressed using the following terms: 148 | 149 | **outgoing_status**: Your relationship to the user. Can be "follows", 150 | "requested", "none". 151 | **incoming_status**: A user's relationship to you. Can be "followed_by", 152 | "requested_by", "blocked_by_you", "none". 153 | - name: Media 154 | description: | 155 | At this time, uploading via the API is not possible. We made a conscious 156 | choice not to add this for the following reasons: 157 | 158 | * Instagram is about your life on the go – we hope to encourage photos 159 | from within the app. 160 | * We want to fight spam & low quality photos. Once we allow uploading 161 | from other sources, it's harder to control what comes into the Instagram 162 | ecosystem. All this being said, we're working on ways to ensure users 163 | have a consistent and high-quality experience on our platform. 164 | - name: Commnts 165 | - name: Likes 166 | - name: Tags 167 | - name: Location 168 | - name: Subscribtions 169 | 170 | ################################################################################ 171 | # Security # 172 | ################################################################################ 173 | securityDefinitions: 174 | oauth: 175 | type: oauth2 176 | flow: implicit 177 | authorizationUrl: https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token 178 | scopes: 179 | basic: | 180 | to read any and all data related to a user (e.g. following/followed-by 181 | lists, photos, etc.) (granted by default) 182 | comments: to create or delete comments on a user’s behalf 183 | relationships: to follow and unfollow users on a user’s behalf 184 | likes: to like and unlike items on a user’s behalf 185 | key: 186 | type: apiKey 187 | in: query 188 | name: access_token 189 | security: 190 | - oauth: 191 | - basic 192 | - comments 193 | - relationships 194 | - likes 195 | - key: [] 196 | 197 | ################################################################################ 198 | # Parameters # 199 | ################################################################################ 200 | parameters: 201 | user-id: 202 | name: user-id 203 | in: path 204 | description: The user identifier number 205 | type: number 206 | required: true 207 | tag-name: 208 | name: tag-name 209 | in: path 210 | description: Tag name 211 | type: string 212 | required: true 213 | 214 | ################################################################################ 215 | # Paths # 216 | ################################################################################ 217 | paths: 218 | /users/{user-id}: 219 | parameters: 220 | - $ref: '#/parameters/user-id' 221 | get: 222 | security: 223 | - key: [] 224 | - oauth: 225 | - basic 226 | tags: 227 | - Users 228 | description: Get basic information about a user. 229 | responses: 230 | 200: 231 | description: The user object 232 | schema: 233 | type: object 234 | properties: 235 | data: 236 | $ref: '#/definitions/User' 237 | 238 | /users/self/feed: 239 | get: 240 | tags: 241 | - Users 242 | description: See the authenticated user's feed. 243 | parameters: 244 | - name: count 245 | in: query 246 | description: Count of media to return. 247 | type: integer 248 | - name: max_id 249 | in: query 250 | description: Return media earlier than this max_id.s 251 | type: integer 252 | - name: min_id 253 | in: query 254 | description: Return media later than this min_id. 255 | 256 | type: integer 257 | responses: 258 | 200: 259 | description: OK 260 | schema: 261 | type: object 262 | properties: 263 | data: 264 | type: array 265 | items: 266 | $ref: '#/definitions/Media' 267 | 268 | /users/{user-id}/media/recent: 269 | parameters: 270 | - $ref: '#/parameters/user-id' 271 | get: 272 | tags: 273 | - Users 274 | responses: 275 | 200: 276 | description: | 277 | Get the most recent media published by a user. To get the most recent 278 | media published by the owner of the access token, you can use `self` 279 | instead of the `user-id`. 280 | schema: 281 | type: object 282 | properties: 283 | data: 284 | type: array 285 | items: 286 | $ref: '#/definitions/Media' 287 | parameters: 288 | - name: count 289 | in: query 290 | description: Count of media to return. 291 | type: integer 292 | - name: max_timestamp 293 | in: query 294 | description: Return media before this UNIX timestamp. 295 | type: integer 296 | - name: min_timestamp 297 | in: query 298 | description: Return media after this UNIX timestamp. 299 | type: integer 300 | - name: min_id 301 | in: query 302 | description: Return media later than this min_id. 303 | type: string 304 | - name: max_id 305 | in: query 306 | description: Return media earlier than this max_id. 307 | type: string 308 | 309 | /users/self/media/liked: 310 | get: 311 | tags: 312 | - Users 313 | description: | 314 | See the list of media liked by the authenticated user. 315 | Private media is returned as long as the authenticated user 316 | has permissionto view that media. Liked media lists are only 317 | available for the currently authenticated user. 318 | responses: 319 | 200: 320 | description: OK 321 | schema: 322 | type: object 323 | properties: 324 | data: 325 | type: array 326 | items: 327 | $ref: '#/definitions/Media' 328 | parameters: 329 | - name: count 330 | in: query 331 | description: Count of media to return. 332 | type: integer 333 | - name: max_like_id 334 | in: query 335 | description: Return media liked before this id. 336 | type: integer 337 | 338 | /users/search: 339 | get: 340 | tags: 341 | - Users 342 | description: Search for a user by name. 343 | parameters: 344 | - name: q 345 | in: query 346 | description: A query string 347 | type: string 348 | required: true 349 | - name: count 350 | in: query 351 | description: Number of users to return. 352 | type: string 353 | responses: 354 | 200: 355 | description: OK 356 | schema: 357 | type: object 358 | properties: 359 | data: 360 | type: array 361 | items: 362 | $ref: '#/definitions/MiniProfile' 363 | 364 | /users/{user-id}/follows: 365 | parameters: 366 | - $ref: '#/parameters/user-id' 367 | get: 368 | tags: 369 | - Relationships 370 | description: Get the list of users this user follows. 371 | responses: 372 | 200: 373 | description: OK 374 | schema: 375 | properties: 376 | data: 377 | type: array 378 | items: 379 | $ref: '#/definitions/MiniProfile' 380 | 381 | /users/{user-id}/followed-by: 382 | parameters: 383 | - $ref: '#/parameters/user-id' 384 | get: 385 | tags: 386 | - Relationships 387 | description: Get the list of users this user is followed by. 388 | responses: 389 | 200: 390 | description: OK 391 | schema: 392 | properties: 393 | data: 394 | type: array 395 | items: 396 | $ref: '#/definitions/MiniProfile' 397 | 398 | /users/self/requested-by: 399 | get: 400 | tags: 401 | - Relationships 402 | description: | 403 | List the users who have requested this user's permission to follow. 404 | responses: 405 | 200: 406 | description: OK 407 | schema: 408 | properties: 409 | meta: 410 | properties: 411 | code: 412 | type: integer 413 | data: 414 | type: array 415 | items: 416 | $ref: '#/definitions/MiniProfile' 417 | 418 | /users/{user-id}/relationship: 419 | parameters: 420 | - $ref: '#/parameters/user-id' 421 | post: 422 | tags: 423 | - Relationships 424 | description: | 425 | Modify the relationship between the current user and thetarget user. 426 | security: 427 | - oauth: 428 | - relationships 429 | parameters: 430 | - name: action 431 | in: body 432 | description: One of follow/unfollow/block/unblock/approve/ignore. 433 | schema: 434 | type: string 435 | enum: 436 | - follow 437 | - unfollow 438 | - block 439 | - unblock 440 | - approve 441 | 442 | responses: 443 | 200: 444 | description: OK 445 | schema: 446 | properties: 447 | data: 448 | type: array 449 | items: 450 | $ref: '#/definitions/MiniProfile' 451 | 452 | /media/{media-id}: 453 | parameters: 454 | - name: media-id 455 | in: path 456 | description: The media ID 457 | type: integer 458 | required: true 459 | get: 460 | tags: 461 | - Media 462 | description: | 463 | Get information about a media object. 464 | The returned type key will allow you to differentiate between `image` 465 | and `video` media. 466 | 467 | Note: if you authenticate with an OAuth Token, you will receive the 468 | `user_has_liked` key which quickly tells you whether the current user 469 | has liked this media item. 470 | responses: 471 | 200: 472 | description: OK 473 | schema: 474 | $ref: '#/definitions/Media' 475 | 476 | /media1/{shortcode}: #FIXME: correct path is /media/{shortcode} 477 | parameters: 478 | - name: shortcode 479 | in: path 480 | description: The media shortcode 481 | type: string 482 | required: true 483 | get: 484 | tags: 485 | - Media 486 | description: | 487 | This endpoint returns the same response as **GET** `/media/media-id`. 488 | 489 | A media object's shortcode can be found in its shortlink URL. 490 | An example shortlink is `http://instagram.com/p/D/` 491 | Its corresponding shortcode is D. 492 | 493 | responses: 494 | 200: 495 | description: OK 496 | schema: 497 | $ref: '#/definitions/Media' 498 | 499 | /media/search: 500 | get: 501 | tags: 502 | - Media 503 | description: | 504 | Search for media in a given area. The default time span is set to 5 505 | days. The time span must not exceed 7 days. Defaults time stamps cover 506 | the last 5 days. Can return mix of image and video types. 507 | 508 | parameters: 509 | - name: LAT 510 | description: | 511 | Latitude of the center search coordinate. If used, lng is required. 512 | type: number 513 | in: query 514 | - name: MIN_TIMESTAMP 515 | description: | 516 | A unix timestamp. All media returned will be taken later than 517 | this timestamp. 518 | type: integer 519 | in: query 520 | - name: LNG 521 | description: | 522 | Longitude of the center search coordinate. If used, lat is required. 523 | type: number 524 | in: query 525 | - name: MAX_TIMESTAMP 526 | description: | 527 | A unix timestamp. All media returned will be taken earlier than this 528 | timestamp. 529 | type: integer 530 | in: query 531 | - name: DISTANCE 532 | description: Default is 1km (distance=1000), max distance is 5km. 533 | type: integer 534 | maximum: 5000 535 | default: 1000 536 | in: query 537 | responses: 538 | 200: 539 | description: OK 540 | schema: 541 | type: object 542 | description: List of all media with added `distance` property 543 | properties: 544 | data: 545 | type: array 546 | items: 547 | allOf: 548 | - $ref: '#/definitions/Media' 549 | - 550 | properties: 551 | distance: 552 | type: number 553 | 554 | /media/popular: 555 | get: 556 | tags: 557 | - Media 558 | description: | 559 | Get a list of what media is most popular at the moment. 560 | Can return mix of image and video types. 561 | responses: 562 | 200: 563 | description: OK 564 | schema: 565 | type: object 566 | properties: 567 | data: 568 | type: array 569 | items: 570 | $ref: '#/definitions/Media' 571 | 572 | /media/{media-id}/comments: 573 | parameters: 574 | - name: media-id 575 | in: path 576 | description: Media ID 577 | type: integer 578 | required: true 579 | get: 580 | tags: 581 | - Comments 582 | description: | 583 | Get a list of recent comments on a media object. 584 | responses: 585 | 200: 586 | description: OK 587 | schema: 588 | properties: 589 | meta: 590 | properties: 591 | code: 592 | type: number 593 | data: 594 | type: array 595 | items: 596 | $ref: '#/definitions/Comment' 597 | post: 598 | tags: 599 | - Comments 600 | - Media 601 | description: | 602 | Create a comment on a media object with the following rules: 603 | 604 | * The total length of the comment cannot exceed 300 characters. 605 | * The comment cannot contain more than 4 hashtags. 606 | * The comment cannot contain more than 1 URL. 607 | * The comment cannot consist of all capital letters. 608 | security: 609 | - oauth: 610 | - comments 611 | parameters: 612 | - name: TEXT 613 | description: | 614 | Text to post as a comment on the media object as specified in 615 | media-id. 616 | in: body 617 | schema: 618 | type: number 619 | responses: 620 | 200: 621 | description: OK 622 | schema: 623 | type: object 624 | properties: 625 | meta: 626 | properties: 627 | code: 628 | type: number 629 | data: 630 | type: object 631 | delete: 632 | tags: 633 | - Comments 634 | description: | 635 | Remove a comment either on the authenticated user's media object or 636 | authored by the authenticated user. 637 | responses: 638 | 200: 639 | description: OK 640 | schema: 641 | type: object 642 | properties: 643 | meta: 644 | properties: 645 | code: 646 | type: number 647 | data: 648 | type: object 649 | 650 | /media/{media-id}/likes: 651 | parameters: 652 | - name: media-id 653 | in: path 654 | description: Media ID 655 | type: integer 656 | required: true 657 | get: 658 | tags: 659 | - Likes 660 | - Media 661 | description: | 662 | Get a list of users who have liked this media. 663 | responses: 664 | 200: 665 | description: OK 666 | schema: 667 | properties: 668 | meta: 669 | properties: 670 | code: 671 | type: number 672 | data: 673 | type: array 674 | items: 675 | $ref: '#/definitions/Like' 676 | post: 677 | tags: 678 | - Likes 679 | description: Set a like on this media by the currently authenticated user. 680 | security: 681 | - oauth: 682 | - comments 683 | responses: 684 | 200: 685 | description: OK 686 | schema: 687 | type: object 688 | properties: 689 | meta: 690 | properties: 691 | code: 692 | type: number 693 | data: 694 | type: object 695 | delete: 696 | tags: 697 | - Likes 698 | description: | 699 | Remove a like on this media by the currently authenticated user. 700 | responses: 701 | 200: 702 | description: OK 703 | schema: 704 | type: object 705 | properties: 706 | meta: 707 | properties: 708 | code: 709 | type: number 710 | data: 711 | type: object 712 | 713 | /tags/{tag-name}: 714 | parameters: 715 | - $ref: '#/parameters/tag-name' 716 | get: 717 | tags: 718 | - Tags 719 | description: Get information about a tag object. 720 | responses: 721 | 200: 722 | description: OK 723 | schema: 724 | $ref: '#/definitions/Tag' 725 | 726 | /tags/{tag-name}/media/recent: 727 | parameters: 728 | - $ref: '#/parameters/tag-name' 729 | get: 730 | tags: 731 | - Tags 732 | description: | 733 | Get a list of recently tagged media. Use the `max_tag_id` and 734 | `min_tag_id` parameters in the pagination response to paginate through 735 | these objects. 736 | responses: 737 | 200: 738 | description: OK 739 | schema: 740 | properties: 741 | data: 742 | type: array 743 | items: 744 | $ref: '#/definitions/Tag' 745 | 746 | /tags/search: 747 | get: 748 | tags: 749 | - Tags 750 | parameters: 751 | - name: q 752 | description: | 753 | A valid tag name without a leading #. (eg. snowy, nofilter) 754 | in: query 755 | type: string 756 | responses: 757 | 200: 758 | description: OK 759 | schema: 760 | type: object 761 | properties: 762 | meta: 763 | properties: 764 | code: 765 | type: integer 766 | data: 767 | type: array 768 | items: 769 | $ref: '#/definitions/Tag' 770 | 771 | /locations/{location-id}: 772 | parameters: 773 | - name: location-id 774 | description: Location ID 775 | in: path 776 | type: integer 777 | required: true 778 | get: 779 | tags: 780 | - Location 781 | description: Get information about a location. 782 | responses: 783 | 200: 784 | description: OK 785 | schema: 786 | type: object 787 | properties: 788 | data: 789 | $ref: '#/definitions/Location' 790 | 791 | /locations/{location-id}/media/recent: 792 | parameters: 793 | - name: location-id 794 | description: Location ID 795 | in: path 796 | type: integer 797 | required: true 798 | get: 799 | tags: 800 | - Location 801 | - Media 802 | description: Get a list of recent media objects from a given location. 803 | parameters: 804 | - name: max_timestamp 805 | in: query 806 | description: Return media before this UNIX timestamp. 807 | type: integer 808 | - name: min_timestamp 809 | in: query 810 | description: Return media after this UNIX timestamp. 811 | type: integer 812 | - name: min_id 813 | in: query 814 | description: Return media later than this min_id. 815 | type: string 816 | - name: max_id 817 | in: query 818 | description: Return media earlier than this max_id. 819 | type: string 820 | responses: 821 | 200: 822 | description: OK 823 | schema: 824 | type: object 825 | properties: 826 | data: 827 | type: array 828 | items: 829 | $ref: '#/definitions/Media' 830 | 831 | /locations/search: 832 | get: 833 | tags: 834 | - Location 835 | description: Search for a location by geographic coordinate. 836 | parameters: 837 | - name: distance 838 | in: query 839 | description: Default is 1000m (distance=1000), max distance is 5000. 840 | type: integer 841 | 842 | - name: facebook_places_id 843 | in: query 844 | description: | 845 | Returns a location mapped off of a Facebook places id. If used, a 846 | Foursquare id and lat, lng are not required. 847 | type: integer 848 | 849 | - name: foursquare_id 850 | in: query 851 | description: | 852 | returns a location mapped off of a foursquare v1 api location id. 853 | If used, you are not required to use lat and lng. Note that this 854 | method is deprecated; you should use the new foursquare IDs with V2 855 | of their API. 856 | type: integer 857 | 858 | - name: lat 859 | in: query 860 | description: | 861 | atitude of the center search coordinate. If used, lng is required. 862 | type: number 863 | 864 | - name: lng 865 | in: query 866 | description: | 867 | ongitude of the center search coordinate. If used, lat is required. 868 | type: number 869 | 870 | - name: foursquare_v2_id 871 | in: query 872 | description: | 873 | Returns a location mapped off of a foursquare v2 api location id. If 874 | used, you are not required to use lat and lng. 875 | type: integer 876 | responses: 877 | 200: 878 | description: OK 879 | schema: 880 | type: object 881 | properties: 882 | data: 883 | type: array 884 | items: 885 | $ref: '#/definitions/Location' 886 | 887 | /geographies/{geo-id}/media/recent: 888 | parameters: 889 | - name: geo-id 890 | in: path 891 | description: Geolocation ID 892 | type: integer 893 | required: true 894 | get: 895 | description: | 896 | Get recent media from a geography subscription that you created. 897 | **Note**: You can only access Geographies that were explicitly created 898 | by your OAuth client. Check the Geography Subscriptions section of the 899 | [real-time updates page](https://instagram.com/developer/realtime/). 900 | When you create a subscription to some geography 901 | that you define, you will be returned a unique geo-id that can be used 902 | in this query. To backfill photos from the location covered by this 903 | geography, use the [media search endpoint 904 | ](https://instagram.com/developer/endpoints/media/). 905 | parameters: 906 | - name: count 907 | in: query 908 | description: Max number of media to return. 909 | type: integer 910 | - name: min_id 911 | in: query 912 | description: Return media before this `min_id`. 913 | type: integer 914 | responses: 915 | 200: 916 | description: OK 917 | 918 | ################################################################################ 919 | # Definitions # 920 | ################################################################################ 921 | definitions: 922 | User: 923 | type: object 924 | properties: 925 | id: 926 | type: integer 927 | username: 928 | type: string 929 | full_name: 930 | type: string 931 | profile_picture: 932 | type: string 933 | bio: 934 | type: string 935 | website: 936 | type: string 937 | counts: 938 | type: object 939 | properties: 940 | media: 941 | type: integer 942 | follows: 943 | type: integer 944 | follwed_by: 945 | type: integer 946 | Media: 947 | type: object 948 | properties: 949 | created_time: 950 | description: Epoc time (ms) 951 | type: integer 952 | type: 953 | type: string 954 | filter: 955 | type: string 956 | tags: 957 | type: array 958 | items: 959 | $ref: '#/definitions/Tag' 960 | id: 961 | type: integer 962 | user: 963 | $ref: '#/definitions/MiniProfile' 964 | users_in_photo: 965 | type: array 966 | items: 967 | $ref: '#/definitions/MiniProfile' 968 | location: 969 | $ref: '#/definitions/Location' 970 | comments:: 971 | type: object 972 | properties: 973 | count: 974 | type: integer 975 | data: 976 | type: array 977 | items: 978 | $ref: '#/definitions/Comment' 979 | likes: 980 | type: object 981 | properties: 982 | count: 983 | type: integer 984 | data: 985 | type: array 986 | items: 987 | $ref: '#/definitions/MiniProfile' 988 | images: 989 | properties: 990 | low_resolution: 991 | $ref: '#/definitions/Image' 992 | thumbnail: 993 | $ref: '#/definitions/Image' 994 | standard_resolution: 995 | $ref: '#/definitions/Image' 996 | videos: 997 | properties: 998 | low_resolution: 999 | $ref: '#/definitions/Image' 1000 | standard_resolution: 1001 | $ref: '#/definitions/Image' 1002 | Location: 1003 | type: object 1004 | properties: 1005 | id: 1006 | type: string 1007 | name: 1008 | type: string 1009 | latitude: 1010 | type: number 1011 | longitude: 1012 | type: number 1013 | Comment: 1014 | type: object 1015 | properties: 1016 | id: 1017 | type: string 1018 | created_time: 1019 | type: string 1020 | text: 1021 | type: string 1022 | from: 1023 | $ref: '#/definitions/MiniProfile' 1024 | Like: 1025 | type: object 1026 | properties: 1027 | user_name: 1028 | type: string 1029 | first_name: 1030 | type: string 1031 | last_name: 1032 | type: string 1033 | type: 1034 | type: string 1035 | id: 1036 | type: string 1037 | Tag: 1038 | type: object 1039 | properties: 1040 | media_count: 1041 | type: integer 1042 | name: 1043 | type: string 1044 | Image: 1045 | type: object 1046 | properties: 1047 | width: 1048 | type: integer 1049 | height: 1050 | type: integer 1051 | url: 1052 | type: string 1053 | MiniProfile: 1054 | type: object 1055 | description: A shorter version of User for likes array 1056 | properties: 1057 | user_name: 1058 | type: string 1059 | full_name: 1060 | type: string 1061 | id: 1062 | type: integer 1063 | profile_picture: 1064 | type: string 1065 | -------------------------------------------------------------------------------- /spec/fixtures/files/minimal.yml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: '2.0' 3 | info: 4 | version: 0.0.0 5 | title: Simple API 6 | paths: 7 | /: 8 | get: 9 | responses: 10 | 200: 11 | description: OK 12 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require File.expand_path('../testapp/config/environment', __FILE__) 4 | # Prevent database truncation if the environment is production 5 | abort("The Rails environment is running in production mode!") if Rails.env.production? 6 | require 'spec_helper' 7 | require 'rspec/rails' 8 | 9 | # Requires supporting ruby files with custom matchers and macros, etc, in 10 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 11 | # run as spec files by default. This means that files in spec/support that end 12 | # in _spec.rb will both be required and run as specs, causing the specs to be 13 | # run twice. It is recommended that you do not name files matching this glob to 14 | # end with _spec.rb. You can configure this pattern with the --pattern 15 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 16 | # 17 | # The following line is provided for convenience purposes. It has the downside 18 | # of increasing the boot-up time by auto-requiring all files in the support 19 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 20 | # require only the support files necessary. 21 | # 22 | # Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 23 | 24 | RSpec.configure do |config| 25 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 26 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 27 | 28 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 29 | # examples within a transaction, remove the following line or assign false 30 | # instead of true. 31 | config.use_transactional_fixtures = true 32 | 33 | # RSpec Rails can automatically mix in different behaviours to your tests 34 | # based on their file location, for example enabling you to call `get` and 35 | # `post` in specs under `spec/controllers`. 36 | # 37 | # You can disable this behaviour by removing the line below, and instead 38 | # explicitly tag your specs with their type, e.g.: 39 | # 40 | # RSpec.describe UsersController, :type => :controller do 41 | # # ... 42 | # end 43 | # 44 | # The different available types are documented in the features, such as in 45 | # https://relishapp.com/rspec/rspec-rails/docs 46 | config.infer_spec_type_from_file_location! 47 | 48 | # Filter lines from Rails gems in backtraces. 49 | config.filter_rails_from_backtrace! 50 | # arbitrary gems may also be filtered via: 51 | # config.filter_gems_from_backtrace("gem name") 52 | end 53 | -------------------------------------------------------------------------------- /spec/requests/request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'swagger_helper' 2 | 3 | RSpec.describe "Sample Requests", type: :request, tags: [:context_tag] do 4 | path '/posts', tags: ['path_tag'] do 5 | operation "GET", summary: "fetch list" do 6 | produces 'application/json' 7 | tags 'operation_tag' 8 | 9 | response(200, {description: "successful"}) 10 | end 11 | 12 | post summary: "create" do 13 | produces 'application/json' 14 | consumes 'application/json' 15 | 16 | parameter "body", in: :body, schema: { foo: :bar} 17 | let(:body) { { post: { title: 'asdf', body: "blah" } } } 18 | 19 | response(201, {description: "successfully created"}) do 20 | it "uses the body we passed in" do 21 | post = JSON.parse(response.body) 22 | expect(post["title"]).to eq('asdf') 23 | expect(post["body"]).to eq('blah') 24 | end 25 | capture_example 26 | end 27 | end 28 | end 29 | 30 | path '/posts/{post_id}' do 31 | parameter "post_id", {in: :path, type: :integer} 32 | let(:post_id) { 1 } 33 | 34 | get summary: "fetch item" do 35 | produces 'application/json' 36 | 37 | before { Post.new.save } 38 | response(200, {description: "success"}) do 39 | capture_example 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/rspec/rails/swagger/document_spec.rb: -------------------------------------------------------------------------------- 1 | require 'swagger_helper' 2 | 3 | RSpec.describe RSpec::Rails::Swagger::Document do 4 | subject { described_class.new(data) } 5 | let(:data) { minimial_example } 6 | 7 | it "stores the data" do 8 | expect(subject[:swagger]).to eq('2.0') 9 | end 10 | 11 | describe "#resolve_ref" do 12 | context 'with nothing to reference' do 13 | let(:data) { minimial_example } 14 | 15 | it 'errors' do 16 | expect{ subject.resolve_ref('#/parameters/user-id') }.to raise_exception(ArgumentError) 17 | expect{ subject.resolve_ref('#/definitions/Tag') }.to raise_exception(ArgumentError) 18 | end 19 | end 20 | 21 | context 'with data to reference' do 22 | let(:data) { instagram_example } 23 | 24 | it "errors on invalid references" do 25 | expect{ subject.resolve_ref('parameters/user-id') }.to raise_exception(ArgumentError) 26 | expect{ subject.resolve_ref('definitions/user-id') }.to raise_exception(ArgumentError) 27 | end 28 | 29 | it "finds parameter references" do 30 | expect(subject.resolve_ref('#/parameters/user-id')).to eq({ 31 | name: 'user-id', 32 | in: 'path', 33 | description: 'The user identifier number', 34 | type: 'number', 35 | required: true, 36 | }) 37 | end 38 | 39 | it "finds valid schema references" do 40 | expect(subject.resolve_ref('#/definitions/Tag')).to eq({ 41 | type: 'object', 42 | properties: { 43 | media_count: { 44 | type: 'integer', 45 | }, 46 | name: { 47 | type: 'string', 48 | }, 49 | } 50 | }) 51 | end 52 | end 53 | end 54 | 55 | def minimial_example 56 | YAML.load_file(File.expand_path('../../../../fixtures/files/minimal.yml', __FILE__)) 57 | end 58 | 59 | def instagram_example 60 | YAML.load_file(File.expand_path('../../../../fixtures/files/instagram.yml', __FILE__)) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/rspec/rails/swagger/formatter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'swagger_helper' 2 | 3 | RSpec.describe RSpec::Rails::Swagger::Formatter do 4 | let(:output) { StringIO.new } 5 | let(:formatter) { described_class.new(output) } 6 | let(:documents) { {'minimal.json' => minimal} } 7 | # Make this a method to bypass rspec's memoization. 8 | def minimal 9 | { 10 | swagger: '2.0', 11 | info: { 12 | version: '0.0.0', 13 | title: 'Simple API' 14 | } 15 | } 16 | end 17 | 18 | before do 19 | RSpec.configure {|c| c.swagger_docs = documents } 20 | end 21 | 22 | describe "#example_finished" do 23 | let(:example_notification) { double('Notification', example: double('Example', metadata: metadata)) } 24 | let(:metadata) { {} } 25 | 26 | context "with a single document" do 27 | let(:metadata) do 28 | { 29 | swagger_object: :response, 30 | swagger_path_item: {path: "/ping"}, 31 | swagger_operation: {method: :put}, 32 | swagger_response: {status_code: 200, description: "OK"}, 33 | } 34 | end 35 | 36 | it "copies the requests into the document" do 37 | formatter.example_finished(example_notification) 38 | 39 | expect(formatter.documents.values.first[:paths]).to eq({ 40 | '/ping' => { 41 | put: { 42 | responses: {200 => {description: 'OK'}} 43 | } 44 | } 45 | }) 46 | end 47 | end 48 | 49 | context "with multiple documents" do 50 | let(:documents) { {'doc1.json' => minimal, 'doc2.json' => minimal} } 51 | let(:metadata) do 52 | { 53 | swagger_object: :response, 54 | swagger_doc: 'doc2.json', 55 | swagger_path_item: {path: "/ping"}, 56 | swagger_operation: {method: :put}, 57 | swagger_response: {status_code: 200, description: "OK"}, 58 | } 59 | end 60 | 61 | it "puts the response on the right document" do 62 | formatter.example_finished(example_notification) 63 | 64 | expect(formatter.documents['doc1.json'][:paths]).to be_blank 65 | expect(formatter.documents['doc2.json'][:paths].length).to eq(1) 66 | end 67 | end 68 | 69 | context "with a response examples" do 70 | let(:metadata_examples) { {'application/json' => JSON.dump({foo: :bar})} } 71 | let(:metadata) do 72 | { 73 | swagger_object: :response, 74 | swagger_path_item: {path: "/ping"}, 75 | swagger_operation: {method: :put}, 76 | swagger_response: {status_code: 200, description: "OK", examples: metadata_examples}, 77 | } 78 | end 79 | 80 | shared_examples 'response example formatter' do 81 | it "copies the requests into the document" do 82 | formatter.example_finished(example_notification) 83 | expected_paths = { 84 | '/ping' => { 85 | put: { 86 | responses: {200 => {examples: output_examples, description: 'OK'}} 87 | } 88 | } 89 | } 90 | expect(formatter.documents.values.first[:paths]).to eq(expected_paths) 91 | end 92 | end 93 | 94 | context "with a default formatter" do 95 | before(:example) do 96 | RSpec::Rails::Swagger::ResponseFormatters.register( 97 | 'application/json', 98 | RSpec::Rails::Swagger::ResponseFormatters::JSON.new 99 | ) 100 | end 101 | 102 | let(:output_examples) { {'application/json' => {"foo" => "bar"}} } 103 | include_examples 'response example formatter' 104 | end 105 | 106 | context "custom application/json formatter" do 107 | before(:example) do 108 | RSpec::Rails::Swagger::ResponseFormatters.register('application/json', ->(resp) { resp }) 109 | end 110 | 111 | let(:output_examples) { {'application/json' => JSON.dump({foo: :bar})} } 112 | include_examples 'response example formatter' 113 | end 114 | end 115 | end 116 | 117 | describe "#close" do 118 | let(:blank_notification) { double('Notification') } 119 | 120 | context "no relevant examples" do 121 | it "writes document with no changes" do 122 | expect(formatter).to receive(:write_file).with(documents.keys.first, documents.values.first) 123 | formatter.close(blank_notification) 124 | end 125 | end 126 | 127 | context "with a relevant example" do 128 | let(:example_notification) { double(example: double(metadata: metadata)) } 129 | let(:metadata) do 130 | { 131 | swagger_object: :response, 132 | swagger_path_item: {path: "/ping"}, 133 | swagger_operation: {method: :get, produces: ["application/json"]}, 134 | swagger_response: {status_code: 200, description: 'all good'}, 135 | } 136 | end 137 | 138 | it "writes a document with the request" do 139 | formatter.example_finished(example_notification) 140 | 141 | expect(formatter).to receive(:write_file).with( 142 | documents.keys.first, 143 | documents.values.first.merge({ 144 | paths: { 145 | '/ping' => { 146 | get: { 147 | responses: {200 => {description: 'all good'}}, 148 | produces: ["application/json"] 149 | } 150 | } 151 | } 152 | }) 153 | ) 154 | 155 | formatter.close(blank_notification) 156 | end 157 | 158 | describe 'output formats' do 159 | let(:documents) { {file_name => minimal} } 160 | 161 | subject do 162 | formatter.example_finished(example_notification) 163 | formatter.close(blank_notification) 164 | Pathname(file_name).expand_path(::RSpec.configuration.swagger_root).read 165 | end 166 | 167 | %w(yaml yml).each do |extension| 168 | context "with a name that ends in .#{extension}" do 169 | let(:file_name) { "minimal.#{extension}" } 170 | 171 | it 'outputs YAML' do 172 | expect(subject).to eq < {'$ref' => '#/parameters/Pet'} 148 | }) 149 | end 150 | end 151 | end 152 | end 153 | 154 | 155 | RSpec.describe RSpec::Rails::Swagger::Helpers::Operation do 156 | let(:klass) do 157 | Class.new do 158 | include RSpec::Rails::Swagger::Helpers::Operation 159 | attr_accessor :metadata 160 | def describe *args ; end 161 | end 162 | end 163 | subject { klass.new } 164 | 165 | describe '#consumes' do 166 | before { subject.metadata = {swagger_operation: {}} } 167 | 168 | it 'accepts an array' do 169 | subject.consumes('foo', 'bar') 170 | 171 | expect(subject.metadata[:swagger_operation][:consumes]).to eq ['foo', 'bar'] 172 | end 173 | end 174 | 175 | describe '#produces' do 176 | before { subject.metadata = {swagger_operation: {}} } 177 | 178 | it 'accepts an array' do 179 | subject.produces('foo', 'bar') 180 | 181 | expect(subject.metadata[:swagger_operation][:produces]).to eq ['foo', 'bar'] 182 | end 183 | end 184 | 185 | describe '#tags' do 186 | before { subject.metadata = {swagger_operation: {}} } 187 | 188 | it 'accepts an array' do 189 | subject.tags('foo', 'bar') 190 | 191 | expect(subject.metadata[:swagger_operation][:tags]).to eq ['foo', 'bar'] 192 | end 193 | end 194 | 195 | describe '#response' do 196 | before { subject.metadata = {swagger_object: :operation} } 197 | 198 | it "requires code be an integer 100...600 or :default" do 199 | expect{ subject.response 99, description: "too low" }.to raise_exception(ArgumentError) 200 | expect{ subject.response 600, description: "too high" }.to raise_exception(ArgumentError) 201 | expect{ subject.response '404', description: "string" }.to raise_exception(ArgumentError) 202 | expect{ subject.response 'default', description: "string" }.to raise_exception(ArgumentError) 203 | 204 | expect{ subject.response 100, description: "low" }.not_to raise_exception 205 | expect{ subject.response 599, description: "high" }.not_to raise_exception 206 | expect{ subject.response :default, description: "symbol" }.not_to raise_exception 207 | end 208 | 209 | it "requires a description" do 210 | expect{ subject.response 100 }.to raise_exception(ArgumentError) 211 | expect{ subject.response 100, description: "low" }.not_to raise_exception 212 | end 213 | end 214 | end 215 | 216 | RSpec.describe RSpec::Rails::Swagger::Helpers::Response do 217 | let(:klass) do 218 | Class.new do 219 | include RSpec::Rails::Swagger::Helpers::Response 220 | attr_accessor :metadata 221 | def describe *args ; end 222 | end 223 | end 224 | subject { klass.new } 225 | 226 | before { subject.metadata = { swagger_object: :response, swagger_response: {} } } 227 | 228 | describe '#capture_example' do 229 | it "sets the capture metadata" do 230 | expect{ subject.capture_example } 231 | .to change{ subject.metadata[:capture_examples] }.to(true) 232 | end 233 | end 234 | 235 | describe '#schema' do 236 | it 'stores the schema' do 237 | subject.schema({ 238 | type: :object, properties: { title: { type: 'string' } } 239 | }) 240 | 241 | expect(subject.metadata[:swagger_response]).to include(schema: { 242 | type: :object, properties: { title: { type: 'string' } } 243 | }) 244 | end 245 | 246 | it 'supports refs' do 247 | subject.schema ref: '#/definitions/Pet' 248 | 249 | expect(subject.metadata[:swagger_response]).to include(schema: { '$ref' => '#/definitions/Pet' }) 250 | end 251 | end 252 | end 253 | -------------------------------------------------------------------------------- /spec/rspec/rails/swagger/request_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'swagger_helper' 2 | 3 | RSpec.describe RSpec::Rails::Swagger::RequestBuilder do 4 | describe '#initialize' do 5 | it 'stores metadata and instance' do 6 | metadata = { foo: :bar } 7 | instance = double 8 | subject = described_class.new(metadata, instance) 9 | 10 | expect(subject.metadata).to eq metadata 11 | expect(subject.instance).to eq instance 12 | end 13 | end 14 | 15 | describe '#document' do 16 | subject { described_class.new(metadata, double('instance')) } 17 | let(:metadata) { { swagger_doc: 'example.json' } } 18 | 19 | it 'loads the document' do 20 | allow(RSpec.configuration.swagger_docs).to receive(:[]).with('example.json').and_return({foo: :bar}) 21 | 22 | expect(subject.document[:foo]).to eq :bar 23 | end 24 | end 25 | 26 | describe '#method' do 27 | subject { described_class.new(metadata, double('instance')) } 28 | let(:metadata) { { swagger_operation: {method: 'get' } } } 29 | 30 | it "returns the operation's method" do 31 | expect(subject.method).to eq 'get' 32 | end 33 | end 34 | 35 | describe '#produces' do 36 | subject { described_class.new(metadata, double('instance')) } 37 | let(:document) { double } 38 | before { allow(subject).to receive(:document) { document } } 39 | 40 | context 'with string in operation' do 41 | let(:metadata) { { swagger_operation: {produces: 'something' } } } 42 | 43 | it 'converts it to an array' do 44 | expect(subject.produces).to eq ['something'] 45 | end 46 | end 47 | 48 | context 'with array in operation' do 49 | let(:metadata) { { swagger_operation: {produces: 'something' } } } 50 | 51 | it 'uses that value' do 52 | expect(subject.produces).to eq ['something'] 53 | end 54 | end 55 | 56 | context 'with no value in operation' do 57 | let(:metadata) { { swagger_operation: {} } } 58 | 59 | it 'uses the value from the document' do 60 | expect(document).to receive(:[]).with(:produces) { 'or other' } 61 | 62 | expect(subject.produces).to eq ['or other'] 63 | end 64 | end 65 | end 66 | 67 | describe '#consumes' do 68 | subject { described_class.new(metadata, double('instance')) } 69 | let(:document) { double } 70 | before { allow(subject).to receive(:document) { document } } 71 | 72 | context 'with string in operation' do 73 | let(:metadata) { { swagger_operation: {consumes: 'something' } } } 74 | 75 | it 'converts it to an array' do 76 | expect(subject.consumes).to eq ['something'] 77 | end 78 | end 79 | 80 | context 'with array in operation' do 81 | let(:metadata) { { swagger_operation: {consumes: ['something'] } } } 82 | 83 | it 'uses that value' do 84 | expect(subject.consumes).to eq ['something'] 85 | end 86 | end 87 | 88 | context 'with no value in operation' do 89 | let(:metadata) { { swagger_operation: {} } } 90 | 91 | it 'uses the value from the document' do 92 | expect(document).to receive(:[]).with(:consumes) { 'or other' } 93 | 94 | expect(subject.consumes).to eq ['or other'] 95 | end 96 | end 97 | end 98 | 99 | describe '#parameters' do 100 | subject { described_class.new(metadata, double('instance')) } 101 | let(:metadata) do 102 | { 103 | swagger_path_item: { 104 | parameters: { 105 | 'path&petId' => { name: 'petId', in: :path, description: 'path' }, 106 | 'query&site' => { name: 'site', in: :query } 107 | } 108 | }, 109 | swagger_operation: { 110 | parameters: { 'path&petId' => { name: 'petId', in: :path, description: 'op' } } 111 | } 112 | } 113 | end 114 | 115 | it 'merges values from the path and operation' do 116 | expect(subject.parameters).to eq({ 117 | 'path&petId' => { name: 'petId', in: :path, description: 'op' }, 118 | 'query&site' => { name: 'site', in: :query } 119 | }) 120 | end 121 | end 122 | 123 | describe '#parameter_values' do 124 | subject { described_class.new(metadata, instance) } 125 | let(:metadata) do 126 | { 127 | swagger_operation: { 128 | parameters: { 129 | "query&date" => { "$ref" => "#/parameters/filter_date" }, 130 | "query&subscriber" => { name: "subscriber", type: :string, in: :query, required: required } 131 | } 132 | } 133 | } 134 | end 135 | let(:instance) { double('instance') } 136 | before do 137 | expect(subject).to receive_message_chain(:document, :resolve_ref) do 138 | { name: "date", type: :integer, in: :query, required: required } 139 | end 140 | end 141 | 142 | context 'required parameters' do 143 | let(:required) { true } 144 | 145 | it 'includes defined values' do 146 | allow(instance).to receive(:date) { 10 } 147 | allow(instance).to receive(:subscriber) { false } 148 | 149 | expect(subject.parameter_values(:query)).to eq({ 'date' => 10, 'subscriber' => false }) 150 | end 151 | 152 | it 'undefined cause errors' do 153 | expect{ subject.parameter_values(:query) }.to raise_exception(RSpec::Mocks::MockExpectationError) 154 | end 155 | end 156 | 157 | context 'optional parameters' do 158 | let(:required) { false } 159 | 160 | it 'includes defined values' do 161 | allow(instance).to receive(:date) { 27 } 162 | allow(instance).to receive(:subscriber) { true } 163 | 164 | expect(subject.parameter_values(:query)).to eq({ 'date' => 27, 'subscriber' => true }) 165 | end 166 | 167 | it 'ommits undefined values' do 168 | expect(subject.parameter_values(:query)).to eq({}) 169 | end 170 | end 171 | end 172 | 173 | describe '#env' do 174 | subject { described_class.new(double('metadata'), instance) } 175 | let(:instance) { double('instance') } 176 | 177 | context 'with no env method on the instance' do 178 | it 'returns empty hash' do 179 | expect(subject.env).to eq({}) 180 | end 181 | end 182 | 183 | context 'with env method on the instance' do 184 | it 'returns the results' do 185 | allow(instance).to receive(:env) { { foo: :bar } } 186 | expect(subject.env).to eq({foo: :bar}) 187 | end 188 | end 189 | end 190 | 191 | describe '#headers' do 192 | subject { described_class.new(double('metadata'), instance) } 193 | let(:instance) { double('instance') } 194 | let(:produces) { } 195 | let(:consumes) { } 196 | before do 197 | allow(subject).to receive(:produces) { produces } 198 | allow(subject).to receive(:consumes) { consumes } 199 | allow(subject).to receive(:parameters).with(:header) { {} } 200 | end 201 | 202 | context 'when produces has a single value' do 203 | let(:produces) { ['foo/bar'] } 204 | it 'sets the Accept header' do 205 | expect(subject.headers).to include('HTTP_ACCEPT' => 'foo/bar') 206 | end 207 | end 208 | 209 | context 'when produces has multiple values' do 210 | let(:produces) { ['foo/bar', 'bar/baz'] } 211 | it 'sets the Accept header to the first' do 212 | expect(subject.headers).to include('HTTP_ACCEPT' => 'foo/bar') 213 | end 214 | end 215 | 216 | context 'when produces is blank' do 217 | it 'does not set the Accept header' do 218 | expect(subject.headers.keys).not_to include('HTTP_ACCEPT') 219 | end 220 | end 221 | 222 | context 'when consumes has a single value' do 223 | let(:consumes) { ['bar/baz'] } 224 | it 'sets the Content-Type header' do 225 | expect(subject.headers).to include('CONTENT_TYPE' => 'bar/baz') 226 | end 227 | end 228 | 229 | context 'when consumes has multiple values' do 230 | let(:consumes) { ['bar/baz', 'flooz/flop'] } 231 | it 'sets the Content-Type header to the first' do 232 | expect(subject.headers).to include('CONTENT_TYPE' => 'bar/baz') 233 | end 234 | end 235 | 236 | context 'when consumes is blank' do 237 | it 'does not set the Content-Type header' do 238 | expect(subject.headers.keys).not_to include('CONTENT_TYPE') 239 | end 240 | end 241 | 242 | context 'with header params' do 243 | it 'returns them in a string' do 244 | expect(subject).to receive(:parameters).with(:header) { { 245 | 'header&X-Magic' => { name: 'X-Magic', in: :header } 246 | } } 247 | expect(instance).to receive('X-Magic'.to_sym) { :pickles } 248 | 249 | expect(subject.headers).to include('X-Magic' => :pickles) 250 | end 251 | end 252 | end 253 | 254 | describe '#path' do 255 | subject { described_class.new(metadata, instance) } 256 | let(:instance) { double('instance') } 257 | 258 | context 'when document includes basePath' do 259 | let(:metadata) { { swagger_path_item: { path: '/path' } } } 260 | 261 | it 'is used as path prefix' do 262 | allow(subject).to receive(:document) { { basePath: '/base' } } 263 | 264 | expect(subject.path).to eq('/base/path') 265 | end 266 | end 267 | 268 | context 'when is templated' do 269 | let(:metadata) { { swagger_path_item: { path: '/sites/{site_id}/accounts/{accountId}' } } } 270 | 271 | it 'variables are replaced with calls to instance' do 272 | allow(subject).to receive(:document) { {} } 273 | expect(instance).to receive(:site_id) { 123 } 274 | expect(instance).to receive(:accountId) { 456 } 275 | 276 | expect(subject.path).to eq('/sites/123/accounts/456') 277 | end 278 | end 279 | end 280 | 281 | describe '#query' do 282 | subject { described_class.new(double('metadata'), instance) } 283 | let(:instance) { double('instance') } 284 | 285 | context 'with no query params' do 286 | it 'returns nil' do 287 | expect(subject).to receive(:parameters).with(:query) { {} } 288 | 289 | expect(subject.query).to be_nil 290 | end 291 | end 292 | 293 | context 'with query params' do 294 | it 'returns them in a string' do 295 | expect(subject).to receive(:parameters).with(:query) { { 296 | 'query&site' => { name: 'site', in: :query } 297 | } } 298 | expect(instance).to receive(:site) { :pickles } 299 | 300 | expect(subject.query).to eq('?site=pickles') 301 | end 302 | end 303 | end 304 | 305 | describe '#body' do 306 | subject { described_class.new(double('metadata'), instance) } 307 | let(:instance) { double('instance') } 308 | 309 | context 'with no body param' do 310 | it 'returns nil' do 311 | expect(subject).to receive(:parameters).with(:body) { {} } 312 | 313 | expect(subject.body).to be_nil 314 | end 315 | end 316 | 317 | context 'with a body param' do 318 | it 'returns a serialized JSON string' do 319 | expect(subject).to receive(:parameters).with(:body) { { 320 | 'body&site' => { same: :here } 321 | } } 322 | expect(instance).to receive(:site) { { name: :pickles, team: :cowboys } } 323 | 324 | expect(subject.body).to eq '{"name":"pickles","team":"cowboys"}' 325 | end 326 | end 327 | end 328 | end 329 | -------------------------------------------------------------------------------- /spec/rspec/rails/swagger/route_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'swagger_helper' 2 | 3 | RSpec.describe RSpec::Rails::Swagger::RouteParser do 4 | subject { described_class.new(controller) } 5 | 6 | describe '#routes' do 7 | let(:controller) { 'posts' } 8 | 9 | it 'extracts the relevant details' do 10 | output = subject.routes 11 | 12 | expect(output.keys).to include('/posts', '/posts/{id}') 13 | expect(output['/posts'][:actions].keys).to include('get', 'post') 14 | expect(output['/posts'][:actions]['get']).to eq(summary: 'list posts') 15 | expect(output['/posts'][:actions]['post']).to eq(summary: 'create post') 16 | expect(output['/posts/{id}'][:actions]['delete']).to eq(summary: 'delete post') 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/rails/swagger' 2 | 3 | # This file was generated by the `rspec --init` command. Conventionally, all 4 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 5 | # The generated `.rspec` file contains `--require spec_helper` which will cause 6 | # this file to always be loaded, without a need to explicitly require it in any 7 | # files. 8 | # 9 | # Given that it is always loaded, you are encouraged to keep this file as 10 | # light-weight as possible. Requiring heavyweight dependencies from this file 11 | # will add to the boot time of your test suite on EVERY test run, even for an 12 | # individual file that may not need all of that loaded. Instead, consider making 13 | # a separate helper file that requires the additional dependencies and performs 14 | # the additional setup, and require it from the spec files that actually need 15 | # it. 16 | # 17 | # The `.rspec` file also contains a few flags that are not defaults but that 18 | # users commonly want. 19 | # 20 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 21 | RSpec.configure do |config| 22 | # rspec-expectations config goes here. You can use an alternate 23 | # assertion/expectation library such as wrong or the stdlib/minitest 24 | # assertions if you prefer. 25 | config.expect_with :rspec do |expectations| 26 | # This option will default to `true` in RSpec 4. It makes the `description` 27 | # and `failure_message` of custom matchers include text for helper methods 28 | # defined using `chain`, e.g.: 29 | # be_bigger_than(2).and_smaller_than(4).description 30 | # # => "be bigger than 2 and smaller than 4" 31 | # ...rather than: 32 | # # => "be bigger than 2" 33 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 34 | end 35 | 36 | # rspec-mocks config goes here. You can use an alternate test double 37 | # library (such as bogus or mocha) by changing the `mock_with` option here. 38 | config.mock_with :rspec do |mocks| 39 | # Prevents you from mocking or stubbing a method that does not exist on 40 | # a real object. This is generally recommended, and will default to 41 | # `true` in RSpec 4. 42 | mocks.verify_partial_doubles = true 43 | end 44 | 45 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 46 | # have no way to turn it off -- the option exists only for backwards 47 | # compatibility in RSpec 3). It causes shared context metadata to be 48 | # inherited by the metadata hash of host groups and examples, rather than 49 | # triggering implicit auto-inclusion in groups with matching metadata. 50 | config.shared_context_metadata_behavior = :apply_to_host_groups 51 | 52 | # The settings below are suggested to provide a good initial experience 53 | # with RSpec, but feel free to customize to your heart's content. 54 | =begin 55 | # This allows you to limit a spec run to individual examples or groups 56 | # you care about by tagging them with `:focus` metadata. When nothing 57 | # is tagged with `:focus`, all examples get run. RSpec also provides 58 | # aliases for `it`, `describe`, and `context` that include `:focus` 59 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 60 | config.filter_run_when_matching :focus 61 | 62 | # Allows RSpec to persist some state between runs in order to support 63 | # the `--only-failures` and `--next-failure` CLI options. We recommend 64 | # you configure your source control system to ignore this file. 65 | config.example_status_persistence_file_path = "spec/examples.txt" 66 | 67 | # Limits the available syntax to the non-monkey patched syntax that is 68 | # recommended. For more details, see: 69 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 70 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 71 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 72 | config.disable_monkey_patching! 73 | 74 | # This setting enables warnings. It's recommended, but in some cases may 75 | # be too noisy due to issues in dependencies. 76 | config.warnings = true 77 | 78 | # Many RSpec users commonly either run the entire suite or an individual 79 | # file, and it's useful to allow more verbose output when running an 80 | # individual spec file. 81 | if config.files_to_run.one? 82 | # Use the documentation formatter for detailed output, 83 | # unless a formatter has already been configured 84 | # (e.g. via a command-line flag). 85 | config.default_formatter = 'doc' 86 | end 87 | 88 | # Print the 10 slowest examples and example groups at the 89 | # end of the spec run, to help surface which specs are running 90 | # particularly slow. 91 | config.profile_examples = 10 92 | 93 | # Run specs in random order to surface order dependencies. If you find an 94 | # order dependency and want to debug it, you can fix the order by providing 95 | # the seed, which is printed after each run. 96 | # --seed 1234 97 | config.order = :random 98 | 99 | # Seed global randomization in this process using the `--seed` CLI option. 100 | # Setting this allows you to use `--seed` to deterministically reproduce 101 | # test failures related to randomization by passing the same `--seed` value 102 | # as the one that triggered the failure. 103 | Kernel.srand config.seed 104 | =end 105 | end 106 | -------------------------------------------------------------------------------- /spec/swagger_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/rails/swagger' 2 | require 'rails_helper' 3 | 4 | RSpec.configure do |config| 5 | # Specify a root folder where Swagger JSON files are generated 6 | config.swagger_root = Rails.root.to_s + '/swagger' 7 | 8 | # Define one or more Swagger documents and global metadata for each. 9 | # 10 | # When you run the "swagger" rake task, the complete Swagger will be 11 | # generated at the provided relative path under `swagger_root` 12 | # 13 | # If the file name ends with .yml or .yaml the contents will be YAML, 14 | # otherwise the file will be JSON. 15 | # 16 | # By default, the operations defined in spec files are added to the first 17 | # document below. You can override this behavior by adding a `swagger_doc` tag 18 | # to the the root example_group in your specs, e.g. 19 | # 20 | # describe '...', swagger_doc: 'v2/swagger.json' 21 | # 22 | config.swagger_docs = { 23 | 'v1/swagger.json' => { 24 | swagger: '2.0', 25 | info: { 26 | title: 'API V1', 27 | version: 'v1' 28 | } 29 | } 30 | } 31 | end 32 | 33 | --------------------------------------------------------------------------------