├── .circleci └── config.yml ├── .editorconfig ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .rspec ├── .rubocop_todo.yml ├── CHANGELOG.md ├── Dockerfile ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── apiary.gemspec ├── bin └── apiary ├── features ├── fetch.feature ├── preview.feature ├── publish.feature ├── step_definitions │ └── file_content_step.rb ├── styleguide.feature ├── support │ ├── apiary.apib │ ├── env.rb │ ├── functions-fail.js │ ├── functions.js │ └── rules.json └── version.feature ├── lib ├── apiary.rb └── apiary │ ├── agent.rb │ ├── cli.rb │ ├── command │ ├── archive.rb │ ├── fetch.rb │ ├── preview.rb │ ├── publish.rb │ └── styleguide.rb │ ├── exceptions.rb │ ├── file_templates │ └── preview.erb │ ├── helpers.rb │ ├── helpers │ └── javascript_helper.rb │ └── version.rb └── spec ├── apiary ├── cli_spec.rb ├── command │ ├── fetch_spec.rb │ ├── preview_spec.rb │ ├── publish_spec.rb │ └── styleguide_spec.rb └── helpers_spec.rb ├── fixtures ├── api_blueprint_and_swagger │ ├── apiary.apib │ ├── swagger.json │ └── swagger.yaml ├── apiary-invalid.apib ├── apiary.apib ├── apiary_with_bom.apib ├── empty_folder │ └── .gitkeep ├── only_api_blueprint │ └── apiary.apib └── only_swagger │ └── swagger.yaml ├── spec.opts ├── spec_helper.rb └── support ├── aruba.rb └── webmock.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | workflows: 3 | version: 2 4 | test: 5 | jobs: 6 | - test-2.4 7 | - test-2.5 8 | - test-2.6 9 | - test-2.7 10 | - test-3.0 11 | jobs: 12 | test-2.4: &test-template 13 | docker: 14 | - image: circleci/ruby:2.4-browsers 15 | steps: 16 | - checkout 17 | - run: 18 | name: Install bundler 19 | command: | 20 | gem install bundler 21 | - run: 22 | name: Install deps 23 | command: | 24 | bundle install 25 | - run: 26 | name: Run rubocop 27 | command: | 28 | bundle exec rubocop --config .rubocop_todo.yml 29 | - run: 30 | name: Run rspec 31 | command: | 32 | bundle exec rspec spec 33 | - run: 34 | name: Run cucumber 35 | command: | 36 | bundle exec cucumber 37 | test-2.5: 38 | <<: *test-template 39 | docker: 40 | - image: circleci/ruby:2.5-browsers 41 | test-2.6: 42 | <<: *test-template 43 | docker: 44 | - image: circleci/ruby:2.6-browsers 45 | test-2.7: 46 | <<: *test-template 47 | docker: 48 | - image: circleci/ruby:2.7-browsers 49 | test-3.0: 50 | <<: *test-template 51 | docker: 52 | - image: circleci/ruby:3.0-browsers 53 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig file: http://EditorConfig.org 2 | ; Install the "EditorConfig" plugin into your editor to use 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | 13 | [README.md] 14 | trim_trailing_whitespace = false 15 | insert_final_newline = false 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: [v*] 5 | jobs: 6 | docker: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: Login to Docker Hub 12 | uses: docker/login-action@v1 13 | with: 14 | username: ${{ secrets.DOCKER_USERNAME }} 15 | password: ${{ secrets.DOCKER_PASSWORD }} 16 | - name: Get the version 17 | id: version 18 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/} 19 | - name: Docker Build and Release 20 | run: | 21 | docker build . -t apiaryio/client 22 | docker tag apiaryio/client apiaryio/client:${{ steps.version.outputs.VERSION }} 23 | docker push apiaryio/client 24 | docker push apiaryio/client:${{ steps.version.outputs.VERSION }} 25 | - name: Publish to RubyGems 26 | run: | 27 | mkdir -p $HOME/.gem 28 | touch $HOME/.gem/credentials 29 | chmod 0600 $HOME/.gem/credentials 30 | printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 31 | gem build *.gemspec 32 | gem push *.gem 33 | env: 34 | GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}} 35 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | types: [opened, reopened, synchronize] 8 | jobs: 9 | test: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: ["windows-latest", "ubuntu-latest"] 14 | ruby: ["3.0", "2.7", "2.6", "2.5"] 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up Ruby 18 | uses: ruby/setup-ruby@v1 19 | with: 20 | ruby-version: ${{ matrix.ruby }} 21 | - name: Set up Bundler 22 | run: gem install bundler --no-document 23 | - name: Install dependencies 24 | run: bundle install 25 | - name: Run test 26 | run: bundle exec rspec spec 27 | - name: Run cucumber tests 28 | if: matrix.os != 'windows-latest' 29 | run: bundle exec cucumber 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /test/tmp/ 9 | /test/version_tmp/ 10 | /tmp/ 11 | /vendor 12 | pmip.rb 13 | 14 | ## Specific to RubyMotion: 15 | .dat* 16 | .repl_history 17 | build/ 18 | 19 | ## Documentation cache and generated files: 20 | /.yardoc/ 21 | /_yardoc/ 22 | /doc/ 23 | /rdoc/ 24 | 25 | ## Environment normalisation: 26 | /.bundle/ 27 | /lib/bundler/man/ 28 | 29 | # for a library or gem, you might want to ignore these files since the code is 30 | # intended to run in multiple environments; otherwise, check them in: 31 | Gemfile.lock 32 | .ruby-version 33 | .ruby-gemset 34 | 35 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 36 | .rvmrc 37 | 38 | # testing 39 | spec/fixtures/test.html 40 | rules.json 41 | functions.js 42 | 43 | # idea ide 44 | /.idea 45 | 46 | # netbeans IDE 47 | /nbproject 48 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | Lint/Eval: 2 | Exclude: 3 | - 'Rakefile' 4 | 5 | Lint/UnusedBlockArgument: 6 | Exclude: 7 | - 'lib/apiary/command/preview.rb' 8 | 9 | Metrics/AbcSize: 10 | Max: 33 11 | 12 | Metrics/BlockLength: 13 | Max: 200 14 | 15 | Metrics/ClassLength: 16 | Max: 250 # or whatever ends up being appropriate 17 | 18 | Metrics/PerceivedComplexity: 19 | Max: 12 20 | 21 | Metrics/CyclomaticComplexity: 22 | Max: 9 23 | 24 | Metrics/LineLength: 25 | Max: 180 26 | Exclude: 27 | - 'spec/*' 28 | - 'spec/apiary/*' 29 | - 'spec/apiary/command/*' 30 | 31 | Metrics/MethodLength: 32 | Max: 33 33 | 34 | Style/ClassAndModuleChildren: 35 | Enabled: false 36 | 37 | Style/ClassVars: 38 | Enabled: false 39 | 40 | Style/Documentation: 41 | Enabled: false 42 | 43 | Lint/HandleExceptions: 44 | Enabled: false 45 | 46 | Lint/AmbiguousBlockAssociation: #https://github.com/bbatsov/rubocop/issues/4345 47 | Enabled: false 48 | 49 | Style/RegexpLiteral: 50 | Enabled: false 51 | EnforcedStyle: slashes 52 | 53 | Style/UnneededInterpolation: 54 | Enabled: false 55 | 56 | Style/MutableConstant: 57 | Exclude: 58 | - 'lib/apiary/helpers/javascript_helper.rb' 59 | - 'spec/apiary/command/fetch_spec.rb' 60 | 61 | Style/FrozenStringLiteralComment: 62 | Enabled: false 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.16.1 4 | 5 | * add option for exclude team API projects 6 | ## 0.16.0 7 | 8 | * add archive command to get all your API projects 9 | ## 0.15.0 10 | * fix: change domain from docs..apiary.io to .docs.apiary.io 11 | * fix(security): [CVE-2020-10663](https://www.ruby-lang.org/en/news/2020/03/19/json-dos-cve-2020-10663/) and [CVE-2020-8184](https://nvd.nist.gov/vuln/detail/CVE-2020-8184) 12 | ## 0.14.1 13 | * fix: [update rack to 2.1.4](https://github.com/advisories/GHSA-j6w9-fv6q-3q52) 14 | 15 | ## 0.14.0 16 | 17 | ### Breaking 18 | * Remove support for ruby 2.3 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7-alpine 2 | RUN apk add --update build-base && rm /var/cache/apk/* 3 | RUN gem install apiaryio 4 | ENTRYPOINT ["apiary"] 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in apiary.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Emili Parreno 2 | Copyright (c) 2012-2016 Apiary Czech Republic, s.r.o. 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Apiary CLI Client 2 | ================= 3 | 4 | [Apiary](https://apiary.io) CLI client, `apiary`. 5 | 6 | [![Build Status](https://travis-ci.org/apiaryio/apiary-client.png?branch=master)](https://travis-ci.org/apiaryio/apiary-client) [![Build status](https://ci.appveyor.com/api/projects/status/0hmkivbnhf9p3f8d/branch/master?svg=true)](https://ci.appveyor.com/project/Apiary/apiary-client/branch/master) 7 | 8 | ## Description 9 | 10 | The Apiary CLI Client gem is a command line tool for developing and previewing 11 | [API Blueprint](https://apiblueprint.org) documents locally. It can also be 12 | used for pushing updated documents to and fetching existing documents from 13 | [Apiary](https://apiary.io). 14 | 15 | 16 | Please see the `apiary help` command and the [full documentation](https://client.apiary.io) for an in-depth look in how to use this tool. 17 | 18 | For instructions on making your own changes, see [Hacking Apiary CLI Client](#hacking-apiary-cli-client), below. 19 | 20 | ## Installation 21 | 22 | ### Install as a Ruby gem 23 | 24 | ``` sh 25 | gem install apiaryio 26 | ``` 27 | 28 | ### Using Docker - alternative if you don't install ruby or installation not work for you 29 | 30 | Download image: 31 | 32 | ``` 33 | docker pull apiaryio/client 34 | ``` 35 | Run instead `apiary` just `docker run apiaryio/client` 36 | 37 | Build from source code: 38 | 39 | ``` 40 | docker build -t "apiaryio/client" . 41 | ``` 42 | 43 | ### Setup Apiary credentials 44 | 45 | *Required only for publish and fetch commands.* 46 | 47 | 48 | 1. Make sure you are a registered user of [Apiary](https://apiary.io). 49 | 2. Retrieve API key (token) on [this page](https://login.apiary.io/tokens). 50 | 3. Export it as an environment variable: 51 | 52 | ```sh 53 | export APIARY_API_KEY= 54 | ``` 55 | ## Command-line Usage 56 | 57 | ``` 58 | $ apiary help 59 | Commands: 60 | apiary archive # Archive All Your API Description Documents from apiary.io to local files named following [api-project-subdomain.apib] pattern. 61 | apiary fetch --api-name=API_NAME # Fetch API Description Document from API_NAME.docs.apiary.io 62 | apiary help [COMMAND] # Describe available commands or one specific command 63 | apiary preview # Show API documentation in browser or write it to file 64 | apiary publish --api-name=API_NAME # Publish API Description Document on API_NAME.docs.apiary.io (API Description must exist on apiary.io) 65 | apiary styleguide # Check API Description Document against styleguide rules (Apiary.io pro plan is required - https://apiary.io/plans ) 66 | apiary version # Show version 67 | 68 | ``` 69 | 70 | ### Details 71 | 72 | #### archive 73 | 74 | ``` 75 | $ apiary help archive 76 | Usage: 77 | apiary archive 78 | 79 | Options: 80 | [--exclude-team-projects], [--no-exclude-team-projects] # Skip team projects 81 | 82 | Archive All Your API Description Documents from apiary.io to local files named following [api-project-subdomain.apib] pattern. 83 | ``` 84 | 85 | #### fetch 86 | 87 | ``` 88 | $ apiary help fetch 89 | Usage: 90 | apiary fetch --api-name=API_NAME 91 | 92 | Options: 93 | --api-name=API_NAME 94 | [--output=FILE] # Write API Description Document into specified file 95 | 96 | Fetch API Description Document from API_NAME.docs.apiary.io 97 | ``` 98 | 99 | #### preview 100 | 101 | ``` 102 | $ apiary help preview 103 | Usage: 104 | apiary preview 105 | 106 | Options: 107 | [--browser=BROWSER] # Show API documentation in specified browser (full command is needed - e.g. `--browser='open -a safari'` in case of osx) 108 | [--output=FILE] # Write generated HTML into specified file 109 | [--path=PATH] # Specify path to API Description Document. When given a directory, it will look for `apiary.apib` and `swagger.yaml` file 110 | [--json], [--no-json] # Specify that Swagger API Description Document is in json format. Document will be converted to yaml before processing 111 | [--server], [--no-server] # Start standalone web server on port 8080 112 | [--port=PORT] # Set port for --server option 113 | [--host=HOST] # Set host for --server option 114 | [--watch], [--no-watch] # Reload API documentation when API Description Document has changed 115 | 116 | Show API documentation in browser or write it to file 117 | ``` 118 | 119 | #### publish 120 | 121 | ``` 122 | $ apiary help publish 123 | Usage: 124 | apiary publish --api-name=API_NAME 125 | 126 | Options: 127 | [--message=COMMIT_MESSAGE] # Publish with custom commit message 128 | [--path=PATH] # Specify path to API Description Document. When given a directory, it will look for `apiary.apib` and `swagger.yaml` file 129 | [--json], [--no-json] # Specify that Swagger API Description Document is in json format. Document will be converted to yaml before processing 130 | [--push], [--no-push] # Push API Description to the GitHub when API Project is associated with GitHub repository in Apiary 131 | # Default: true 132 | --api-name=API_NAME 133 | 134 | Publish API Description Document on API_NAME.docs.apiary.io (API Description must exist on apiary.io) 135 | ``` 136 | 137 | #### styleguide 138 | 139 | ``` 140 | $ apiary help styleguide 141 | Usage: 142 | apiary styleguide 143 | 144 | Options: 145 | [--fetch], [--no-fetch] # Fetch styleguide rules and functions from apiary.io 146 | [--push], [--no-push] # Push styleguide rules and functions to apiary.io 147 | [--add=ADD] # Path to API Description Document. When given a directory, it will look for `apiary.apib` and `swagger.yaml` file 148 | [--functions=FUNCTIONS] # Path to to the file with functions definitions 149 | [--rules=RULES] # Path to to the file with rules definitions - `functions.js` and `rules.json` are loaded if not specified 150 | [--full-report], [--no-full-report] # Get passed assertions ass well. Only failed assertions are included to the result by default 151 | [--json], [--no-json] # Outputs all in json 152 | 153 | Check API Description Document against styleguide rules (Apiary.io pro plan is required - https://apiary.io/plans ) 154 | 155 | ``` 156 | 157 | #### version 158 | 159 | ``` 160 | $ apiary help version 161 | Usage: 162 | apiary version 163 | 164 | Options: 165 | [--{:aliases=>"-v"}={:ALIASES=>"-V"}] 166 | 167 | Show version 168 | ``` 169 | 170 | ## Hacking Apiary CLI Client 171 | 172 | ### Build 173 | 174 | 1. If needed, install bundler: 175 | 176 | ```sh 177 | $ gem install bundler 178 | ``` 179 | 180 | 2. Clone the repo: 181 | 182 | ```sh 183 | $ git clone git@github.com:apiaryio/apiary-client.git 184 | $ cd apiary-client 185 | ``` 186 | 187 | 3. Install dependencies: 188 | 189 | ```sh 190 | $ bundle install 191 | ``` 192 | 193 | ### Test 194 | 195 | Inside the `apiary-client` repository directory run: 196 | 197 | ```sh 198 | $ bundle exec rspec spec 199 | $ bundle exec cucumber 200 | ``` 201 | 202 | 203 | ### Release 204 | 205 | Use `bundle install` to install your changes locally, for manual and ad-hock testing. 206 | 207 | Only gem owners `gem owner apiaryio` can publish new gem into [RubyGems](https://rubygems.org/gems/apiaryio). 208 | 209 | 1. bump version in `lib/apiary/version.rb` 210 | 2. update `CHANGELOG.md` 211 | 3. prepare Github Release with text in `CHANGELOG` 212 | 4. make gem release: 213 | 214 | ```sh 215 | $ git tag $VERSION 216 | $ git push --tags 217 | ``` 218 | 219 | - if release is stuck you need use `$ rake release --trace` to get OTP prompt. 220 | 221 | 222 | ## License 223 | 224 | Copyright 2012-17 (c) Apiary Ltd. 225 | 226 | Released under MIT license. 227 | See [LICENSE](https://raw.githubusercontent.com/apiaryio/apiary-client/master/LICENSE) file for further details. 228 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rspec/core/rake_task' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | task default: :spec 8 | 9 | def gemspec 10 | @gemspec ||= eval(File.read('apiary.gemspec'), binding, '.gemspec') 11 | end 12 | 13 | desc 'Validate the gemspec' 14 | task :gemspec do 15 | gemspec.validate 16 | end 17 | -------------------------------------------------------------------------------- /apiary.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'apiary/version' 6 | 7 | Gem::Specification.new do |gem| 8 | gem.required_ruby_version = '>= 2.3.0' 9 | 10 | gem.name = 'apiaryio' 11 | gem.version = Apiary::VERSION 12 | gem.authors = ['Apiary Ltd.'] 13 | gem.email = ['team@apiary.io'] 14 | 15 | gem.description = 'Apiary.io CLI' 16 | gem.summary = 'Apiary.io CLI' 17 | gem.homepage = 'https://apiary.io' 18 | gem.license = 'MIT' 19 | 20 | gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR) 21 | gem.bindir = 'bin' 22 | gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } 23 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 24 | gem.require_paths = ['lib'] 25 | 26 | gem.add_runtime_dependency 'rest-client', '~> 2.0' 27 | gem.add_runtime_dependency 'rack', '>= 2.2.3', '< 3' 28 | gem.add_runtime_dependency 'thor', '~> 1.1.0' 29 | gem.add_runtime_dependency 'json', '>= 2.3.0' 30 | gem.add_runtime_dependency 'launchy', '~> 2.4' 31 | gem.add_runtime_dependency 'listen', '~> 3.0' 32 | 33 | gem.add_development_dependency 'bundler', '>= 2.2.11' 34 | gem.add_development_dependency 'rake', '>= 12.3.3' 35 | gem.add_development_dependency 'rspec', '~> 3.4' 36 | gem.add_development_dependency 'webmock', '>= 2.2.0' 37 | gem.add_development_dependency 'aruba', '~> 0.14' 38 | gem.add_development_dependency 'cucumber', '>= 2.0' 39 | gem.add_development_dependency 'rubocop', '~> 0.49.0' 40 | end 41 | -------------------------------------------------------------------------------- /bin/apiary: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))) 3 | require 'apiary' 4 | 5 | Apiary::CLI.start 6 | -------------------------------------------------------------------------------- /features/fetch.feature: -------------------------------------------------------------------------------- 1 | Feature: Fetch apiary.apib from API_NAME.apiary.io 2 | 3 | # This is integration testing you have to set APIARY_API_KEY 4 | @needs_apiary_api_key 5 | Scenario: Fetch apiary.apib from API_NAME.apiary.io 6 | 7 | When I run `apiary fetch --api-name=apiaryclitestingdonottouch` 8 | Then the output should contain the content of file "apiary.apib" 9 | -------------------------------------------------------------------------------- /features/preview.feature: -------------------------------------------------------------------------------- 1 | Feature: Show API documentation in specified browser 2 | 3 | # This is integration testing you have to set APIARY_API_KEY 4 | @needs_apiary_api_key 5 | Scenario: Write generated HTML into specified file 6 | 7 | When I run `apiary preview --path ../../spec/fixtures/apiary.apib --output=test.html` 8 | Then a file named "test.html" should exist 9 | -------------------------------------------------------------------------------- /features/publish.feature: -------------------------------------------------------------------------------- 1 | Feature: Publish apiary.apib on API_NAME.docs.apiary.io 2 | 3 | # This is integration testing you have to set APIARY_API_KEY 4 | @needs_apiary_api_key 5 | Scenario: Publish apiary.apib on API_NAME.docs.apiary.io 6 | 7 | # expected to fail 8 | When I run `apiary publish --path=apiary.apib --api-name 1111apiaryclienttest` 9 | Then the exit status should be 1 10 | -------------------------------------------------------------------------------- /features/step_definitions/file_content_step.rb: -------------------------------------------------------------------------------- 1 | Then(/^the output should contain the content of file "(.*)"$/) do |filename| 2 | expected = nil 3 | cd('../../features/support') do 4 | expected = File.read(filename) 5 | end 6 | 7 | actual = all_commands.map(&:output).join("\n") 8 | 9 | expect(unescape_text(actual)).to include(unescape_text(expected)) 10 | end 11 | -------------------------------------------------------------------------------- /features/styleguide.feature: -------------------------------------------------------------------------------- 1 | Feature: Styleguide apiary.apib on API_NAME.docs.apiary.io 2 | 3 | # This is integration testing you have to set APIARY_API_KEY 4 | @needs_apiary_api_key 5 | Scenario: Styleguide validation - pass 6 | 7 | When I run `apiary styleguide --functions=../../features/support --rules=../../features/support --add=../../features/support --full_report` 8 | Then the output should match /(PASSED)/ 9 | And the exit status should be 0 10 | 11 | @needs_apiary_api_key 12 | Scenario: Styleguide validation - fail 13 | 14 | When I run `apiary styleguide --functions=../../features/support/functions-fail.js --rules=../../features/support --add=../../features/support --full_report` 15 | Then the output should match /(FAILED)/ 16 | And the exit status should be 1 17 | 18 | # This is integration testing you have to set APIARY_API_KEY 19 | @needs_apiary_api_key 20 | Scenario: Styleguide fetch 21 | 22 | When I run `apiary styleguide --fetch` 23 | Then the output should match /(has beed succesfully created)/ 24 | And the exit status should be 0 25 | 26 | 27 | -------------------------------------------------------------------------------- /features/support/apiary.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # test 4 | test 5 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'aruba/cucumber' 2 | 3 | Before('@needs_apiary_api_key') do 4 | @aruba_timeout_seconds = 45 5 | end 6 | 7 | Around('@needs_apiary_api_key') do |_scenario, block| 8 | # DEBUG puts "Scenario #{scenario.name} wants APIARY_API_KEY." 9 | original_value = ENV.delete('APIARY_API_KEY') 10 | ENV['APIARY_API_KEY'] = '5a96f04006effca6cc03f1616ed916eb' 11 | block.call 12 | ENV['APIARY_API_KEY'] = original_value 13 | end 14 | 15 | Around('@doesnt_need_apiary_api_key') do |_scenario, block| 16 | # DEBUG puts "Scenario #{scenario.name} doesn't want APIARY_API_KEY." 17 | original_value = ENV.delete('APIARY_API_KEY') 18 | block.call 19 | ENV['APIARY_API_KEY'] = original_value 20 | end 21 | -------------------------------------------------------------------------------- /features/support/functions-fail.js: -------------------------------------------------------------------------------- 1 | /* 2 | @targets: API_Name 3 | */ 4 | 5 | function validateApiName(apiName) { 6 | return 'fail'; 7 | }; 8 | -------------------------------------------------------------------------------- /features/support/functions.js: -------------------------------------------------------------------------------- 1 | /* 2 | @targets: API_Name 3 | */ 4 | 5 | function validateApiName(apiName) { 6 | return true; 7 | }; 8 | -------------------------------------------------------------------------------- /features/support/rules.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "ruleName": "validateApiName", 4 | "functionName": "validateApiName", 5 | "target": "API_Name", 6 | "intent": "validateApiName" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /features/version.feature: -------------------------------------------------------------------------------- 1 | Feature: Version of Apiary client 2 | 3 | @doesnt_need_apiary_api_key 4 | Scenario: Print the semantic version of Apiary client 5 | 6 | # Note the output should be a semantic version (semver.org) 7 | # The matching regex was taken from https://github.com/isaacs/node-semver/issues/32#issue-15023919 8 | 9 | When I run `apiary version` 10 | Then the output should match /^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/ 11 | And the exit status should be 0 12 | -------------------------------------------------------------------------------- /lib/apiary.rb: -------------------------------------------------------------------------------- 1 | require 'apiary/version' 2 | require 'apiary/cli' 3 | 4 | module Apiary 5 | end 6 | -------------------------------------------------------------------------------- /lib/apiary/agent.rb: -------------------------------------------------------------------------------- 1 | module Apiary 2 | USER_AGENT = "apiaryio-gem/#{Apiary::VERSION} (#{RUBY_PLATFORM}) ruby/#{RUBY_VERSION}".freeze 3 | 4 | module_function 5 | 6 | def user_agent 7 | @@user_agent ||= USER_AGENT 8 | end 9 | 10 | def user_agent=(agent) 11 | @@user_agent = agent 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/apiary/cli.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'thor' 4 | require 'apiary/command/fetch' 5 | require 'apiary/command/archive' 6 | require 'apiary/command/preview' 7 | require 'apiary/command/publish' 8 | require 'apiary/command/styleguide' 9 | 10 | module Apiary 11 | class CLI < Thor 12 | 13 | desc 'archive', 'Archive All Your API Description Documents from apiary.io to local files named following [api-project-subdomain.apib] pattern.' 14 | method_option :api_host, type: :string, banner: 'HOST', desc: 'Specify apiary host', hide: true 15 | method_option :exclude_team_projects, type: :boolean, default: false, desc: 'Skip team projects' 16 | 17 | def archive 18 | cmd = Apiary::Command::Archive.new options 19 | cmd.execute 20 | end 21 | 22 | desc 'fetch', 'Fetch API Description Document from API_NAME.docs.apiary.io' 23 | method_option :api_name, type: :string, required: true 24 | method_option :api_host, type: :string, banner: 'HOST', desc: 'Specify apiary host', hide: true 25 | method_option :output, type: :string, banner: 'FILE', desc: 'Write API Description Document into specified file' 26 | 27 | def fetch 28 | cmd = Apiary::Command::Fetch.new options 29 | cmd.execute 30 | end 31 | 32 | desc 'preview', 'Show API documentation in browser or write it to file' 33 | method_option :browser, type: :string, desc: 'Show API documentation in specified browser (full command is needed - e.g. `--browser=\'open -a safari\'` in case of osx)' 34 | method_option :output, type: :string, banner: 'FILE', desc: 'Write generated HTML into specified file' 35 | method_option :path, type: :string, desc: 'Specify path to API Description Document. When given a directory, it will look for `apiary.apib` and `swagger.yaml` file' 36 | method_option :json, type: :boolean, desc: 'Specify that Swagger API Description Document is in json format. Document will be converted to yaml before processing' 37 | method_option :api_host, type: :string, banner: 'HOST', desc: 'Specify apiary host', hide: true 38 | method_option :server, type: :boolean, desc: 'Start standalone web server on port 8080' 39 | method_option :port, type: :numeric, banner: 'PORT', desc: 'Set port for --server option' 40 | method_option :host, type: :string, desc: 'Set host for --server option' 41 | method_option :watch, type: :boolean, desc: 'Reload API documentation when API Description Document has changed' 42 | 43 | def preview 44 | cmd = Apiary::Command::Preview.new options 45 | cmd.execute 46 | end 47 | 48 | desc 'publish', 'Publish API Description Document on API_NAME.docs.apiary.io (API Description must exist on apiary.io)' 49 | method_option :message, type: :string, banner: 'COMMIT_MESSAGE', desc: 'Publish with custom commit message' 50 | method_option :path, type: :string, desc: 'Specify path to API Description Document. When given a directory, it will look for `apiary.apib` and `swagger.yaml` file' 51 | method_option :json, type: :boolean, desc: 'Specify that Swagger API Description Document is in json format. Document will be converted to yaml before processing' 52 | method_option :api_host, type: :string, banner: 'HOST', desc: 'Specify apiary host', hide: true 53 | method_option :push, type: :boolean, default: true, desc: 'Push API Description to the GitHub when API Project is associated with GitHub repository in Apiary' 54 | method_option :api_name, type: :string, required: true 55 | 56 | def publish 57 | cmd = Apiary::Command::Publish.new options 58 | cmd.execute 59 | end 60 | 61 | desc 'styleguide', 'Check API Description Document against styleguide rules (Apiary.io pro plan is required - https://apiary.io/plans )' 62 | method_option :fetch, type: :boolean, desc: 'Fetch styleguide rules and functions from apiary.io' 63 | method_option :push, type: :boolean, desc: 'Push styleguide rules and functions to apiary.io' 64 | method_option :add, type: :string, desc: 'Path to API Description Document. When given a directory, it will look for `apiary.apib` and `swagger.yaml` file' 65 | method_option :functions, type: :string, desc: 'Path to to the file with functions definitions' 66 | method_option :rules, type: :string, desc: 'Path to to the file with rules definitions - `functions.js` and `rules.json` are loaded if not specified' 67 | method_option :full_report, type: :boolean, default: false, desc: 'Get passed assertions ass well. Only failed assertions are included to the result by default' 68 | method_option :json, type: :boolean, default: false, desc: 'Outputs all in json' 69 | def styleguide 70 | cmd = Apiary::Command::Styleguide.new options 71 | cmd.execute 72 | end 73 | 74 | desc 'version', 'Show version' 75 | method_option aliases: '-v' 76 | 77 | def version 78 | puts Apiary::VERSION 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/apiary/command/archive.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rest-client' 4 | require 'json' 5 | 6 | require 'apiary/agent' 7 | 8 | module Apiary::Command 9 | # Retrieve blueprint from apiary 10 | class Archive 11 | def initialize(opts) 12 | @options = OpenStruct.new(opts) 13 | @options.api_host ||= 'api.apiary.io' 14 | @options.api_key ||= ENV['APIARY_API_KEY'] 15 | @options.proxy ||= ENV['http_proxy'] 16 | @options.headers ||= { 17 | accept: 'application/json', 18 | content_type: 'application/json', 19 | authorization: "Bearer #{@options.api_key}", 20 | user_agent: Apiary.user_agent 21 | } 22 | end 23 | 24 | def execute 25 | response = apilist_from_apiary 26 | 27 | return unless response.instance_of? String 28 | 29 | puts response 30 | end 31 | 32 | def apilist_from_apiary 33 | unless @options.api_key 34 | abort 'API key must be provided through environment variable APIARY_API_KEY. Please go to https://login.apiary.io/tokens to obtain it.' 35 | end 36 | 37 | response = query_apiary 38 | 39 | response['apis'].each do |api| 40 | if api['apiIsTeam'] == true && @options.exclude_team_projects == true 41 | puts "#{api['apiSubdomain']}... Team API skipping" 42 | next 43 | end 44 | puts api['apiSubdomain'] 45 | 46 | @options = OpenStruct.new 47 | @options.api_host ||= 'api.apiary.io' 48 | @options.api_name ||= api['apiSubdomain'] 49 | @options.api_key ||= ENV['APIARY_API_KEY'] 50 | @options.proxy ||= ENV['http_proxy'] 51 | @options.output ||= api['apiSubdomain'] + '.apib' 52 | @options.headers ||= { 53 | accept: 'text/html', 54 | content_type: 'text/plain', 55 | authentication: "Token #{@options.api_key}", 56 | user_agent: Apiary.user_agent 57 | } 58 | cmd = Apiary::Command::Fetch.new(@options) 59 | cmd.execute 60 | end 61 | end 62 | 63 | def query_apiary 64 | url = "https://#{@options.api_host}/me/apis" 65 | RestClient.proxy = @options.proxy 66 | 67 | begin 68 | response = RestClient.get url, @options.headers 69 | rescue RestClient::Exception => e 70 | abort "Apiary service responded with an error: #{e.message}" 71 | end 72 | JSON.parse response.body 73 | end 74 | 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/apiary/command/fetch.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rest-client' 4 | require 'json' 5 | 6 | require 'apiary/agent' 7 | 8 | module Apiary::Command 9 | # Retrieve blueprint from apiary 10 | class Fetch 11 | def initialize(opts) 12 | @options = OpenStruct.new(opts) 13 | @options.api_host ||= 'api.apiary.io' 14 | @options.api_name ||= false 15 | @options.api_key ||= ENV['APIARY_API_KEY'] 16 | @options.proxy ||= ENV['http_proxy'] 17 | @options.headers ||= { 18 | accept: 'text/html', 19 | content_type: 'text/plain', 20 | authentication: "Token #{@options.api_key}", 21 | user_agent: Apiary.user_agent 22 | } 23 | end 24 | 25 | def execute 26 | response = fetch_from_apiary 27 | 28 | return unless response.instance_of? String 29 | 30 | puts response 31 | end 32 | 33 | def fetch_from_apiary 34 | unless @options.api_name 35 | abort 'Please provide an api-name option (subdomain part from your https://.docs.apiary.io/)' 36 | end 37 | 38 | unless @options.api_key 39 | abort 'API key must be provided through environment variable APIARY_API_KEY. Please go to https://login.apiary.io/tokens to obtain it.' 40 | end 41 | 42 | response = query_apiary 43 | 44 | if @options.output 45 | write_generated_path(response['code'], @options.output) 46 | else 47 | response['code'] 48 | end 49 | end 50 | 51 | def query_apiary 52 | url = "https://#{@options.api_host}/blueprint/get/#{@options.api_name}" 53 | RestClient.proxy = @options.proxy 54 | 55 | begin 56 | response = RestClient.get url, @options.headers 57 | rescue RestClient::Exception => e 58 | abort "Apiary service responded with an error: #{e.message}" 59 | end 60 | JSON.parse response.body 61 | end 62 | 63 | def write_generated_path(data, outfile) 64 | File.open(outfile, 'w') do |file| 65 | file.write(data) 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/apiary/command/preview.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rest-client' 4 | require 'rack' 5 | require 'json' 6 | require 'tmpdir' 7 | require 'erb' 8 | require 'launchy' 9 | require 'listen' 10 | 11 | require 'apiary/agent' 12 | require 'apiary/helpers' 13 | require 'apiary/helpers/javascript_helper' 14 | 15 | module Apiary::Command 16 | # Display preview of local blueprint file 17 | class Preview 18 | include Apiary::Helpers 19 | include Apiary::Helpers::JavascriptHelper 20 | 21 | PREVIEW_TEMPLATE_PATH = "#{File.expand_path File.dirname(__FILE__)}/../file_templates/preview.erb".freeze 22 | 23 | attr_reader :options 24 | 25 | def initialize(opts) 26 | @options = OpenStruct.new(opts) 27 | @options.path ||= '.' 28 | @options.api_host ||= 'api.apiary.io' 29 | @options.port ||= 8080 30 | @options.proxy ||= ENV['http_proxy'] 31 | @options.server ||= false 32 | @options.json ||= false 33 | @options.watch ||= false 34 | @options.interval ||= 1000 35 | @options.host ||= '127.0.0.1' 36 | @options.headers ||= { 37 | accept: 'text/html', 38 | content_type: 'text/plain', 39 | user_agent: Apiary.user_agent 40 | } 41 | 42 | @changed = timestamp 43 | 44 | begin 45 | @source_path = api_description_source_path(@options.path) 46 | rescue StandardError => e 47 | abort "#{e.message}" 48 | end 49 | end 50 | 51 | def execute 52 | if @options.server || @options.watch 53 | watch 54 | server 55 | else 56 | show 57 | end 58 | end 59 | 60 | def watch 61 | return unless @options.watch 62 | listener = Listen.to(File.dirname(@source_path), only: /#{File.basename(@source_path)}/) do |modified| 63 | @changed = timestamp 64 | end 65 | listener.start 66 | end 67 | 68 | def timestamp 69 | Time.now.getutc.to_i.to_s 70 | end 71 | 72 | def server 73 | generate_app = get_app(path: '/') do 74 | generate 75 | end 76 | 77 | change_app = get_app(path: '/changed', options: { 'Content-Type' => 'text/plain' }) do 78 | @changed 79 | end 80 | 81 | source_app = get_app(path: '/source', options: { 'Content-Type' => 'text/plain' }) do 82 | api_description_source(@source_path) 83 | end 84 | 85 | app = Rack::Builder.new do 86 | run Rack::Cascade.new([source_app, change_app, generate_app]) 87 | end 88 | 89 | Rack::Server.start(Port: @options.port, Host: @options.host, app: app) 90 | end 91 | 92 | def show 93 | preview_string = generate 94 | 95 | File.open(preview_path, 'w') do |file| 96 | file.write preview_string 97 | file.flush 98 | @options.output ? write_generated_path(file.path, @options.output) : open_generated_page(file.path) 99 | end 100 | end 101 | 102 | def get_app(path: '/', options: {}) 103 | Rack::Builder.new do 104 | map path do 105 | run ->(env) { [200, options, [yield]] } 106 | end 107 | end 108 | end 109 | 110 | def open_generated_page(path) 111 | def_browser = ENV['BROWSER'] 112 | ENV['BROWSER'] = @options.browser 113 | 114 | Launchy.open(path) do |e| 115 | puts "Attempted to open `#{path}` and failed because #{e}" 116 | end 117 | ENV['BROWSER'] = def_browser 118 | end 119 | 120 | def write_generated_path(path, outfile) 121 | File.write(outfile, File.read(path)) 122 | end 123 | 124 | def generate 125 | template = load_preview_template 126 | source = api_description_source(@source_path) 127 | 128 | return if source.nil? 129 | 130 | begin 131 | JSON.parse(source) 132 | abort('Did you forget the --json flag') unless @options.json 133 | rescue; end 134 | source = convert_from_json(source) if @options.json 135 | 136 | data = { 137 | title: File.basename(@source_path, '.*'), 138 | source: source, 139 | interval: @options.interval, 140 | watch: @options.watch 141 | } 142 | 143 | template.result(binding) 144 | end 145 | 146 | def preview_path 147 | basename = File.basename(@source_path, '.*') 148 | temp = Dir.tmpdir 149 | "#{temp}/#{basename}-preview.html" 150 | end 151 | 152 | def load_preview_template 153 | file = File.open(PREVIEW_TEMPLATE_PATH, 'r') 154 | template_string = file.read 155 | ERB.new(template_string) 156 | end 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /lib/apiary/command/publish.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rest-client' 4 | require 'json' 5 | 6 | require 'apiary/agent' 7 | require 'apiary/helpers' 8 | 9 | module Apiary::Command 10 | # Display preview of local blueprint file 11 | class Publish 12 | include Apiary::Helpers 13 | 14 | attr_reader :options 15 | 16 | def initialize(opts) 17 | @options = OpenStruct.new(opts) 18 | @options.path ||= '.' 19 | @options.json ||= false 20 | @options.api_host ||= 'api.apiary.io' 21 | @options.api_name ||= false 22 | @options.api_key ||= ENV['APIARY_API_KEY'] 23 | @options.proxy ||= ENV['http_proxy'] 24 | @options.headers ||= { 25 | accept: 'text/html', 26 | content_type: 'text/plain', 27 | authentication: "Token #{@options.api_key}", 28 | user_agent: Apiary.user_agent 29 | } 30 | @options.message ||= 'Saving API Description Document from apiary-client' 31 | 32 | begin 33 | @source_path = api_description_source_path(@options.path) 34 | rescue StandardError => e 35 | abort "#{e.message}" 36 | end 37 | end 38 | 39 | def execute 40 | publish_on_apiary 41 | end 42 | 43 | def publish_on_apiary 44 | unless @options.api_name 45 | abort 'Please provide an api-name option (subdomain part from your https://.docs.apiary.io/)' 46 | end 47 | 48 | unless @options.api_key 49 | abort 'API key must be provided through environment variable APIARY_API_KEY. \Please go to https://login.apiary.io/tokens to obtain it.' 50 | end 51 | 52 | query_apiary 53 | end 54 | 55 | def query_apiary 56 | url = "https://#{@options.api_host}/blueprint/publish/#{@options.api_name}" 57 | source = api_description_source(@source_path) 58 | 59 | return if source.nil? 60 | 61 | source = convert_from_json(source) if @options.json 62 | 63 | data = { 64 | code: source, 65 | messageToSave: @options.message, 66 | shouldCommit: @options.push ? 'yes' : 'no' 67 | } 68 | 69 | RestClient.proxy = @options.proxy 70 | 71 | begin 72 | RestClient.post url, data, @options.headers 73 | rescue RestClient::BadRequest => e 74 | err = JSON.parse e.response 75 | if err.key? 'parserError' 76 | abort "#{err['message']}: #{err['parserError']}" 77 | else 78 | abort "Apiary service responded with an error: #{err['message']}" 79 | end 80 | rescue RestClient::Exception => e 81 | err = JSON.parse e.response 82 | if err.key? 'message' 83 | abort "Apiary service responded with an error: #{err['message']}" 84 | else 85 | abort "Apiary service responded with an error: #{e.message}" 86 | end 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/apiary/command/styleguide.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rest-client' 4 | require 'json' 5 | 6 | require 'apiary/agent' 7 | require 'apiary/helpers' 8 | require 'apiary/exceptions' 9 | 10 | module Apiary::Command 11 | class Styleguide 12 | include Apiary::Helpers 13 | 14 | attr_reader :options 15 | 16 | def initialize(opts) 17 | @options = OpenStruct.new(opts) 18 | @options.fetch ||= false 19 | @options.add ||= '.' 20 | @options.functions ||= '.' 21 | @options.rules ||= '.' 22 | @options.api_key ||= ENV['APIARY_API_KEY'] 23 | @options.proxy ||= ENV['http_proxy'] 24 | @options.api_host ||= 'api.apiary.io' 25 | @options.vk_url ||= 'https://voight-kampff.apiary-services.com' 26 | @options.headers ||= { 27 | content_type: :json, 28 | accept: :json, 29 | user_agent: Apiary.user_agent 30 | } 31 | @options.failedOnly = !@options.full_report 32 | @options.json 33 | end 34 | 35 | def execute 36 | check_api_key 37 | if @options.fetch 38 | fetch 39 | elsif @options.push 40 | push 41 | else 42 | validate 43 | end 44 | end 45 | 46 | def push 47 | begin 48 | load(add: false) 49 | rescue StandardError => e 50 | abort "Error: #{e.message}" 51 | end 52 | 53 | path = 'styleguide-cli/set-assertions/' 54 | headers = @options.headers.clone 55 | headers[:authentication] = "Token #{@options.api_key}" 56 | 57 | data = { 58 | functions: @functions, 59 | rules: @rules 60 | }.to_json 61 | 62 | begin 63 | call_apiary(path, data, headers, :post) 64 | rescue => e 65 | puts e 66 | abort "Error: Can not write into the rules/functions file: #{e}" 67 | end 68 | end 69 | 70 | def fetch 71 | begin 72 | assertions = fetch_from_apiary 73 | assertions = JSON.parse(assertions) 74 | rescue => e 75 | abort "Error: Can not fetch rules and functions: #{e}" 76 | end 77 | 78 | begin 79 | File.write("./#{default_functions_file_name}", assertions['functions']['functions']) 80 | File.write("./#{default_rules_file_name}", JSON.pretty_generate(assertions['rules']['rules'])) 81 | puts "`./#{default_functions_file_name}` and `./#{default_rules_file_name}` has beed succesfully created" 82 | rescue => e 83 | abort "Error: Can not write into the rules/functions file: #{e}" 84 | end 85 | end 86 | 87 | def validate 88 | token = jwt 89 | 90 | begin 91 | token = JSON.parse(token)['jwt'] 92 | rescue JSON::ParserError => e 93 | abort "Can not authenticate: #{e}" 94 | end 95 | 96 | begin 97 | load 98 | rescue StandardError => e 99 | abort "Error: #{e.message}" 100 | end 101 | 102 | data = { 103 | functions: @functions, 104 | rules: @rules, 105 | add: @add, 106 | failedOnly: @options.failedOnly 107 | }.to_json 108 | 109 | headers = @options.headers.clone 110 | headers[:Authorization] = "Bearer #{token}" 111 | headers['Accept-Encoding'] = 'identity' 112 | 113 | output call_resource(@options.vk_url, data, headers, :post) 114 | end 115 | 116 | def print_output_text(result) 117 | lines = if result['sourcemapLines']['start'] == result['sourcemapLines']['end'] 118 | "on line #{result['sourcemapLines']['start']}" 119 | else 120 | "on lines #{result['sourcemapLines']['start']} - #{result['sourcemapLines']['end']}" 121 | end 122 | 123 | if result['result'] == true 124 | puts " [\u2713] PASSED: #{(result['path'] || '').gsub('-', ' #')} #{lines}" 125 | else 126 | puts " [\u274C] FAILED: #{(result['path'] || '').gsub('-', ' #')} #{lines} - `#{result['result']}`" 127 | end 128 | end 129 | 130 | def json_output(json_response) 131 | puts JSON.pretty_generate json_response 132 | end 133 | 134 | def human_output(json_response) 135 | if json_response.empty? 136 | puts 'All tests has passed' 137 | exit 0 138 | end 139 | 140 | puts '' 141 | 142 | at_least_one_failed = false 143 | 144 | json_response.each do |response| 145 | puts " #{(response['intent'] || response['ruleName'] || response['functionName'])}" 146 | 147 | (response['results'] || []).each do |result| 148 | print_output_text result 149 | at_least_one_failed = true if result['result'] != true 150 | end 151 | 152 | puts '' 153 | end 154 | 155 | if at_least_one_failed 156 | exit 1 157 | else 158 | exit 0 159 | end 160 | end 161 | 162 | def output(raw_response) 163 | begin 164 | json_response = JSON.parse(raw_response) 165 | rescue 166 | abort "Error: Can not parse result: #{raw_response}" 167 | end 168 | 169 | if @options.json 170 | json_output json_response 171 | else 172 | human_output json_response 173 | end 174 | end 175 | 176 | def default_rules_file_name 177 | 'rules.json' 178 | end 179 | 180 | def default_functions_file_name 181 | 'functions.js' 182 | end 183 | 184 | def call_apiary(path, data, headers, method) 185 | call_resource("https://#{@options.api_host}/#{path}", data, headers, method) 186 | end 187 | 188 | def call_resource(url, data, headers, method) 189 | RestClient.proxy = @options.proxy 190 | 191 | method = :post unless method 192 | 193 | begin 194 | response = RestClient::Request.execute(method: method, url: url, payload: data, headers: headers) 195 | rescue RestClient::Exception => e 196 | begin 197 | err = JSON.parse e.response 198 | rescue JSON::ParserError 199 | err = {} 200 | end 201 | 202 | message = 'Error: Apiary service responded with:' 203 | 204 | if err.key? 'message' 205 | abort "#{message} #{e.http_code} #{err['message']}" 206 | else 207 | abort "#{message} #{e.message}" 208 | end 209 | end 210 | response 211 | end 212 | 213 | def jwt 214 | path = 'styleguide-cli/get-token/' 215 | headers = @options.headers.clone 216 | headers[:authentication] = "Token #{@options.api_key}" 217 | call_apiary(path, {}, headers, :get) 218 | end 219 | 220 | def check_api_key 221 | return if @options.api_key && @options.api_key != '' 222 | abort 'Error: API key must be provided through environment variable APIARY_API_KEY. \Please go to https://login.apiary.io/tokens to obtain it.' 223 | end 224 | 225 | def load(add: true, functions: true, rules: true) 226 | if add 227 | @add_path = api_description_source_path(@options.add) 228 | @add = api_description_source(@add_path) 229 | end 230 | @functions = get_functions(@options.functions) if functions 231 | @rules = get_rules(@options.rules) if rules 232 | end 233 | 234 | def fetch_from_apiary 235 | path = 'styleguide-cli/get-assertions/' 236 | headers = @options.headers.clone 237 | headers[:authentication] = "Token #{@options.api_key}" 238 | call_apiary(path, {}, headers, :get) 239 | end 240 | 241 | def get_rules(path) 242 | JSON.parse get_file_content(get_path(path, 'rules')) 243 | end 244 | 245 | def get_functions(path) 246 | get_file_content(get_path(path, 'functions')) 247 | end 248 | 249 | def get_path(path, type) 250 | raise "`#{path}` not found" unless File.exist? path 251 | 252 | return path if File.file? path 253 | 254 | file = case type 255 | when 'rules' 256 | default_rules_file_name 257 | else 258 | default_functions_file_name 259 | end 260 | 261 | path = File.join(path, file) 262 | 263 | return path if File.file? path 264 | raise "`#{path}` not found" 265 | end 266 | 267 | def get_file_content(path) 268 | source = nil 269 | File.open(path, 'r:bom|utf-8') { |file| source = file.read } 270 | source 271 | end 272 | end 273 | end 274 | -------------------------------------------------------------------------------- /lib/apiary/exceptions.rb: -------------------------------------------------------------------------------- 1 | class RulesLoadError < StandardError 2 | end 3 | -------------------------------------------------------------------------------- /lib/apiary/file_templates/preview.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= data[:title] %> 6 | 7 | 8 | 9 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/apiary/helpers.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | module Apiary 6 | module Helpers 7 | def api_description_source_path(path) 8 | raise "Invalid path #{path}" unless File.exist? path 9 | return path if File.file? path 10 | source_path = choose_one(path) 11 | return source_path unless source_path.nil? 12 | raise 'No API Description Document found' 13 | end 14 | 15 | def api_description_source(path) 16 | source_path = api_description_source_path(path) 17 | source = nil 18 | File.open(source_path, 'r:bom|utf-8') { |file| source = file.read } 19 | source 20 | end 21 | 22 | def convert_from_json(add) 23 | JSON.parse(add).to_yaml 24 | rescue JSON::ParserError => e 25 | abort "Unable to convert input document to yaml: #{e.message.lines.first}" 26 | end 27 | 28 | protected 29 | 30 | def choose_one(path) 31 | apib_path = api_blueprint(path) 32 | swagger_path = swagger(path) 33 | 34 | if apib_path && swagger_path 35 | warn 'WARNING: Both apiary.apib and swagger.yaml are present. The apiary.apib file will be used. To override this selection specify path to desired file' 36 | end 37 | 38 | apib_path || swagger_path 39 | end 40 | 41 | def api_blueprint(path) 42 | source_path = File.join(path, 'apiary.apib') 43 | return source_path if File.exist? source_path 44 | nil 45 | end 46 | 47 | def swagger(path) 48 | source_path = File.join(path, 'swagger.yaml') 49 | return source_path if File.exist? source_path 50 | nil 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/apiary/helpers/javascript_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module Apiary::Helpers::JavascriptHelper 4 | JS_ESCAPE_MAP = { 5 | '\\' => '\\\\', 6 | ' '<\/', 7 | "\r\n" => '\n', 8 | "\n" => '\n', 9 | "\r" => '\n', 10 | '"' => '\\"', 11 | "'" => "\\'" 12 | } 13 | 14 | JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = '
' 15 | JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = '
' 16 | 17 | def escape_javascript(javascript) 18 | if javascript 19 | javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] } 20 | else 21 | '' 22 | end 23 | end 24 | 25 | alias j escape_javascript 26 | end 27 | -------------------------------------------------------------------------------- /lib/apiary/version.rb: -------------------------------------------------------------------------------- 1 | module Apiary 2 | VERSION = '0.17.0'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/apiary/cli_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Apiary::CLI do 4 | # Don't let Thor fold or truncate lines 5 | ENV['THOR_COLUMNS'] = '1000' 6 | 7 | # The documentation that ought to match the code 8 | READMETEXT = open('README.md', &:read) 9 | 10 | it 'has help' do 11 | help = open('|ruby bin/apiary help', &:read) 12 | expect(help).to include('Commands:') 13 | expect(help.lines.count).to be >= 5 14 | end 15 | 16 | it 'has README.md' do 17 | expect(READMETEXT).to include('apiary help') 18 | expect(READMETEXT.lines.count).to be >= 5 19 | end 20 | 21 | # Confirm that all subcommands are documented, verbatim 22 | ([''] + (open('|ruby bin/apiary help', 'r', &:readlines) 23 | .map { |l| /^ +apiary / =~ l ? l : nil } 24 | .map { |l| /^ *apiary help/ =~ l ? nil : l } 25 | .compact 26 | .map { |l| /^ *apiary ([^ ]*)/.match(l)[1] + ' ' } 27 | ) 28 | ).each do |cmd| 29 | it "includes help #{cmd}in README.md" do 30 | puts cmd 31 | helptext = open("|ruby bin/apiary help #{cmd}", &:read) 32 | 33 | expect(helptext).to include("apiary #{cmd.strip}") 34 | expect(READMETEXT).to include("apiary #{cmd.strip}") 35 | expect(READMETEXT).to include(helptext) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/apiary/command/fetch_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Apiary::Command::Fetch do 4 | it 'pass command without params' do 5 | opts = {} 6 | command = Apiary::Command::Fetch.new(opts) 7 | expect { command.fetch_from_apiary }.to raise_error('Please provide an api-name option (subdomain part from your https://.docs.apiary.io/)') 8 | end 9 | 10 | it 'pass command only with api_name', api_key: true do 11 | opts = { 12 | api_name: 'test_api' 13 | } 14 | 15 | command = Apiary::Command::Fetch.new(opts) 16 | expect { command.fetch_from_apiary }.to raise_error('API key must be provided through environment variable APIARY_API_KEY. Please go to https://login.apiary.io/tokens to obtain it.') 17 | end 18 | 19 | it 'check request for fetch to apiary' do 20 | API_NAME = 'test_api'.freeze 21 | 22 | opts = { 23 | api_name: API_NAME, 24 | api_key: '1234567890' 25 | } 26 | command = Apiary::Command::Fetch.new(opts) 27 | 28 | BODY_EXAMPLE = '{ 29 | "error": false, 30 | "message": "", 31 | "code": "FORMAT: 1A\nHOST: http://www.testing.com\n\n# Notes API test 123\nNotes API is a *short texts saving* service similar to its physical paper presence on your table.\n\n# Group Notes\nNotes related resources of the **Notes API**\n\n## Notes Collection [/notes]\n### List all Notes [GET]\n+ Response 200 (application/json)\n\n [{\n \"id\": 1, \"title\": \"Jogging in park\"\n }, {\n \"id\": 2, \"title\": \"Pick-up posters from post-office\"\n }]\n\n### Create a Note [POST]\n+ Request (application/json)\n\n { \"title\": \"Buy cheese and bread for breakfast.\" }\n\n+ Response 201 (application/json)\n\n { \"id\": 3, \"title\": \"Buy cheese and bread for breakfast.\" }\n\n## Note [/notes/{id}]\nA single Note object with all its details\n\n+ Parameters\n + id (required, number, `1`) ... Numeric `id` of the Note to perform action with. Has example value.\n\n### Retrieve a Note [GET]\n+ Response 200 (application/json)\n\n + Header\n\n X-My-Header: The Value\n\n + Body\n\n { \"id\": 2, \"title\": \"Pick-up posters from post-office\" }\n\n### Remove a Note [DELETE]\n+ Response 204\n" 32 | }' 33 | 34 | BLUEPRINT_EXAMPLE = %{FORMAT: 1A 35 | HOST: http://www.testing.com 36 | 37 | # Notes API test 123 38 | Notes API is a *short texts saving* service similar to its physical paper presence on your table. 39 | 40 | # Group Notes 41 | Notes related resources of the **Notes API** 42 | 43 | ## Notes Collection [/notes] 44 | ### List all Notes [GET] 45 | + Response 200 (application/json) 46 | 47 | [{ 48 | "id": 1, "title": "Jogging in park" 49 | }, { 50 | "id": 2, "title": "Pick-up posters from post-office" 51 | }] 52 | 53 | ### Create a Note [POST] 54 | + Request (application/json) 55 | 56 | { "title": "Buy cheese and bread for breakfast." } 57 | 58 | + Response 201 (application/json) 59 | 60 | { "id": 3, "title": "Buy cheese and bread for breakfast." } 61 | 62 | ## Note [/notes/{id}] 63 | A single Note object with all its details 64 | 65 | + Parameters 66 | + id (required, number, `1`) ... Numeric `id` of the Note to perform action with. Has example value. 67 | 68 | ### Retrieve a Note [GET] 69 | + Response 200 (application/json) 70 | 71 | + Header 72 | 73 | X-My-Header: The Value 74 | 75 | + Body 76 | 77 | { "id": 2, "title": "Pick-up posters from post-office" } 78 | 79 | ### Remove a Note [DELETE] 80 | + Response 204 81 | } 82 | 83 | stub_request(:get, "https://api.apiary.io/blueprint/get/#{API_NAME}").to_return(status: 200, body: BODY_EXAMPLE, headers: { 'Content-Type' => 'text/plain' }) 84 | expect(command.fetch_from_apiary).to eq(BLUEPRINT_EXAMPLE) 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /spec/apiary/command/preview_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Apiary::Command::Preview do 4 | let(:command) do 5 | opts = { 6 | path: "#{File.expand_path File.dirname(__FILE__)}/../../fixtures/apiary.apib" 7 | } 8 | Apiary::Command::Preview.new(opts) 9 | end 10 | 11 | it 'check tmp path if contains filename' do 12 | expect(command.preview_path).to end_with('apiary-preview.html') 13 | end 14 | 15 | it 'shoud contain html5 doctype' do 16 | expect(command.generate).to include('') 17 | end 18 | 19 | it 'should contain embed javascript' do 20 | expect(command.generate).to include('https://api.apiary.io/seeds/embed.js') 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/apiary/command/publish_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Apiary::Command::Publish do 4 | context 'when constructed without a message' do 5 | let(:message) do 6 | Apiary::Command::Publish.new(api_name: 'myapi', 7 | path: 'spec/fixtures/apiary.apib', 8 | api_key: 'testkey').options.message 9 | end 10 | 11 | it 'uses the default message' do 12 | expect(message).to eq('Saving API Description Document from apiary-client') 13 | end 14 | end 15 | 16 | context 'when constructed with a message' do 17 | let(:message) do 18 | Apiary::Command::Publish.new(api_name: 'myapi', 19 | message: 'Custom message', 20 | path: 'spec/fixtures/apiary.apib', 21 | api_key: 'testkey').options.message 22 | end 23 | 24 | it 'stores the message in the opts' do 25 | expect(message).to eq('Custom message') 26 | end 27 | end 28 | 29 | describe '#execute' do 30 | context 'when calling with a custom message' do 31 | before(:all) do 32 | WebMock.stub_request(:post, 'https://api.apiary.io/blueprint/publish/myapi') 33 | Apiary::Command::Publish.new(api_name: 'myapi', 34 | message: 'Custom message', 35 | path: 'spec/fixtures/apiary.apib', 36 | api_key: 'testkey').execute 37 | end 38 | 39 | it 'sends the message when publishing' do 40 | expect(WebMock).to have_requested(:post, 'https://api.apiary.io/blueprint/publish/myapi') 41 | .with { |request| request.body.include? 'messageToSave=Custom+message' } 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/apiary/command/styleguide_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | def test_abort(command, message) 4 | expect do 5 | begin 6 | command.execute 7 | rescue SystemExit 8 | end 9 | end.to output(/#{message}/).to_stderr 10 | end 11 | 12 | describe Apiary::Command::Styleguide do 13 | describe 'fetch' do 14 | it 'call command without APIARY_API_KEY set' do 15 | opts = { 16 | fetch: true, 17 | api_key: '' 18 | } 19 | command = Apiary::Command::Styleguide.new(opts) 20 | test_abort(command, 'Error: API key must be provided through environment variable APIARY_API_KEY.') 21 | end 22 | 23 | it 'call command with incorrect APIARY_API_KEY' do 24 | opts = { 25 | fetch: true, 26 | api_key: 'xxx' 27 | } 28 | 29 | command = Apiary::Command::Styleguide.new(opts) 30 | stub_request(:get, "https://#{command.options.api_host}/styleguide-cli/get-assertions/").to_return(status: [403, 'This resource requires authenticated API call.']) 31 | 32 | test_abort(command, 'Error: Apiary service responded with: 403') 33 | end 34 | 35 | it 'call command with correct APIARY_API_KEY' do 36 | opts = { 37 | fetch: true, 38 | api_key: 'xxx' 39 | } 40 | 41 | command = Apiary::Command::Styleguide.new(opts) 42 | 43 | body = '{"rules":{"_id":"598af267b999bef906c10ef8","organisation":"5577dbc613036160198734cf","createdAt":"2017-08-22T12:57:05.123Z","rules":[{"intent":"validateApiName","target":"API_Name","functionName":"validateApiName","ruleName":"validateApiName"}]},"functions":{"_id":"598af267b999bef906c10ef9","organisation":"5577dbc613036160198734cf","language":"JavaScript","functions":"/*\r\n @targets: API_Name\r\n */\r\n\r\nfunction validateApiName(apiName) {\r\n return true;\r\n};\r\n"}}' 44 | stub_request(:get, "https://#{command.options.api_host}/styleguide-cli/get-assertions/").to_return(status: 200, body: body) 45 | 46 | expect do 47 | begin 48 | command.execute 49 | rescue SystemExit 50 | end 51 | end.to output("`./functions.js` and `./rules.json` has beed succesfully created\n").to_stdout 52 | 53 | assertions = JSON.parse(body) 54 | 55 | functions = nil 56 | File.open('./functions.js', 'r:bom|utf-8') { |file| functions = file.read } 57 | rules = nil 58 | File.open('./rules.json', 'r:bom|utf-8') { |file| rules = file.read } 59 | 60 | expect(assertions['functions']['functions']).to eq(functions) 61 | expect(JSON.pretty_generate(assertions['rules']['rules'])).to eq(rules) 62 | end 63 | end 64 | 65 | describe 'validate' do 66 | it 'call command without APIARY_API_KEY set' do 67 | opts = { 68 | api_key: '' 69 | } 70 | command = Apiary::Command::Styleguide.new(opts) 71 | expect do 72 | command.execute 73 | end.to raise_error(SystemExit) 74 | 75 | test_abort(command, 'Error: API key must be provided through environment variable APIARY_API_KEY') 76 | end 77 | 78 | it 'call command with incorrect APIARY_API_KEY' do 79 | opts = { 80 | api_key: 'xxx' 81 | } 82 | 83 | command = Apiary::Command::Styleguide.new(opts) 84 | stub_request(:get, "https://#{command.options.api_host}/styleguide-cli/get-token/").to_return(status: 403) 85 | 86 | test_abort(command, 'Error: Apiary service responded with: 403') 87 | end 88 | 89 | it 'call command with incorrect ADD path' do 90 | opts = { 91 | api_key: 'xxx', 92 | functions: 'features/support', 93 | rules: 'features/support', 94 | add: 'features/supportXXX' 95 | } 96 | 97 | command = Apiary::Command::Styleguide.new(opts) 98 | stub_request(:get, "https://#{command.options.api_host}/styleguide-cli/get-token/").to_return(status: 200, body: '{"jwt":"xxx"}') 99 | 100 | test_abort(command, 'Invalid path features/supportXXX') 101 | end 102 | 103 | it 'call command with incorrect functions path' do 104 | opts = { 105 | api_key: 'xxx', 106 | functions: 'features/supportXXY', 107 | rules: 'features/support', 108 | add: 'features/support' 109 | } 110 | 111 | command = Apiary::Command::Styleguide.new(opts) 112 | stub_request(:get, "https://#{command.options.api_host}/styleguide-cli/get-token/").to_return(status: 200, body: '{"jwt":"xxx"}') 113 | 114 | test_abort(command, 'supportXXY` not found') 115 | end 116 | 117 | it 'call command with incorrect rules path' do 118 | opts = { 119 | api_key: 'xxx', 120 | functions: 'features/support', 121 | rules: 'features/supportsupportXXZ', 122 | add: 'features/support' 123 | } 124 | 125 | command = Apiary::Command::Styleguide.new(opts) 126 | stub_request(:get, "https://#{command.options.api_host}/styleguide-cli/get-token/").to_return(status: 200, body: '{"jwt":"xxx"}') 127 | 128 | test_abort(command, 'supportXXZ` not found') 129 | end 130 | 131 | it 'call command with correct options which should fail' do 132 | opts = { 133 | api_key: 'xxx', 134 | functions: 'features/support/functions-fail.js', 135 | rules: 'features/support', 136 | add: 'features/support', 137 | full_report: true 138 | } 139 | 140 | command = Apiary::Command::Styleguide.new(opts) 141 | stub_request(:get, "https://#{command.options.api_host}/styleguide-cli/get-token/").to_return(status: 200, body: '{"jwt":"xxx"}') 142 | response = '[{"ruleName":"validateApiName","functionName":"validateApiName","target":"API_Name","intent":"validateApiName","code":"function validateApiName(apiName) {\n return \'fail\';\n}","functionComment":"\n @targets: API_Name\n ","allowedPaths":["API_Name"],"ref":["API_Name-0"],"results":[{"validatorError":false,"result":"fail","path":"API_Name-0","data":"test","sourcemap":[[12,7]],"sourcemapLines":{"start":2,"end":2}}]}]' 143 | stub_request(:post, command.options.vk_url).to_return(status: 200, body: response) 144 | 145 | expect do 146 | begin 147 | command.execute 148 | rescue SystemExit 149 | end 150 | end.to output("\n validateApiName\n [❌] FAILED: API_Name #0 on line 2 - `fail`\n\n").to_stdout 151 | end 152 | 153 | it 'call command with correct options and json output' do 154 | opts = { 155 | api_key: 'xxx', 156 | functions: 'features/support', 157 | rules: 'features/support', 158 | add: 'features/support', 159 | full_report: true, 160 | json: true 161 | } 162 | 163 | command = Apiary::Command::Styleguide.new(opts) 164 | stub_request(:get, "https://#{command.options.api_host}/styleguide-cli/get-token/").to_return(status: 200, body: '{"jwt":"xxx"}') 165 | stub_request(:post, command.options.vk_url).to_return(status: 200, body: '[]') 166 | 167 | expect do 168 | begin 169 | command.execute 170 | rescue SystemExit 171 | end 172 | end.to output("[\n\n]\n").to_stdout 173 | end 174 | end 175 | 176 | describe 'push' do 177 | it 'call command without APIARY_API_KEY set' do 178 | opts = { 179 | push: true, 180 | api_key: '', 181 | functions: 'features/support', 182 | rules: 'features/support' 183 | } 184 | command = Apiary::Command::Styleguide.new(opts) 185 | test_abort(command, 'Error: API key must be provided through environment variable APIARY_API_KEY.') 186 | end 187 | 188 | it 'call command with incorrect APIARY_API_KEY' do 189 | opts = { 190 | push: true, 191 | api_key: 'xxx' 192 | } 193 | 194 | command = Apiary::Command::Styleguide.new(opts) 195 | stub_request(:post, "https://#{command.options.api_host}/styleguide-cli/set-assertions/").to_return(status: [403, 'This resource requires authenticated API call.']) 196 | 197 | test_abort(command, 'Error: Apiary service responded with: 403') 198 | end 199 | 200 | it 'call command with correct APIARY_API_KEY' do 201 | opts = { 202 | push: true, 203 | api_key: 'xxx', 204 | functions: 'function testFunction(data) { return "failed"; }', 205 | rules: '[{"ruleName": "testName","functionName": "testFunction","target": "Request_Body","intent": "testIntent"}]' 206 | } 207 | 208 | command = Apiary::Command::Styleguide.new(opts) 209 | stub_request(:post, "https://#{command.options.api_host}/styleguide-cli/set-assertions/").to_return(status: 200, body: '') 210 | 211 | expect do 212 | begin 213 | command.execute 214 | rescue SystemExit 215 | end 216 | end.to output('').to_stdout 217 | end 218 | end 219 | end 220 | -------------------------------------------------------------------------------- /spec/apiary/helpers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Apiary::Helpers do 4 | include Apiary::Helpers 5 | 6 | describe '#api_description_source_path' do 7 | context 'path doesn\'t exists' do 8 | it 'should raise error saying that Directory doesn\'t exists' do 9 | path = 'spec/fixtures/invalid_path' 10 | expect { api_description_source_path(path) }.to raise_error(/Invalid path/) 11 | end 12 | end 13 | 14 | context 'missing file is in path' do 15 | it 'should raise error saying that file doesn\'t exists' do 16 | path = 'spec/fixtures/only_api_blueprint/swagger.yaml' 17 | expect { api_description_source_path(path) }.to raise_error(/Invalid path/) 18 | end 19 | end 20 | 21 | context 'directory is in path and contains API Blueprint only' do 22 | it 'should return path ending to `apiary.apib`' do 23 | path = 'spec/fixtures/only_api_blueprint' 24 | expect(api_description_source_path(path)).to match(/apiary\.apib$/) 25 | end 26 | end 27 | 28 | context 'directory is in path and contains Swagger only' do 29 | it 'should return path ending to `swagger.yaml`' do 30 | path = 'spec/fixtures/only_swagger' 31 | expect(api_description_source_path(path)).to match(/swagger\.yaml$/) 32 | end 33 | end 34 | 35 | context 'directory is in path and contains both API Blueprint and Swagger' do 36 | it 'should prefere API Blueprint and return path ending to `apiary.apib`' do 37 | path = 'spec/fixtures/api_blueprint_and_swagger' 38 | expect(api_description_source_path(path)).to match(/apiary\.apib$/) 39 | expect { api_description_source_path(path) }.to output("WARNING: Both apiary.apib and swagger.yaml are present. The apiary.apib file will be used. To override this selection specify path to desired file\n").to_stderr 40 | end 41 | end 42 | 43 | context 'empty folder' do 44 | it 'should raise error saying that file doesn\'t exists' do 45 | path = 'spec/fixtures/empty_folder' 46 | expect { api_description_source_path(path) }.to raise_error('No API Description Document found') 47 | end 48 | end 49 | 50 | context 'existing file is in path' do 51 | it 'should return same path as was entered' do 52 | path = 'spec/fixtures/only_api_blueprint/apiary.apib' 53 | expect(api_description_source_path(path)).to equal(path) 54 | end 55 | end 56 | end 57 | 58 | describe '#api_description_source' do 59 | it 'get file with and without BOM' do 60 | file1 = api_description_source('spec/fixtures/apiary.apib') 61 | file2 = api_description_source('spec/fixtures/apiary_with_bom.apib') 62 | expect(file1).not_to be_nil 63 | expect(file2).not_to be_nil 64 | expect(file1).to eq(file2) 65 | end 66 | end 67 | 68 | describe '#convert_from_json' do 69 | it 'converts swagger in yaml to swagger to json' do 70 | yaml_source = api_description_source('spec/fixtures/api_blueprint_and_swagger/swagger.yaml') 71 | json_source = api_description_source('spec/fixtures/api_blueprint_and_swagger/swagger.json') 72 | expect(yaml_source).to eq(convert_from_json(json_source)) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/fixtures/api_blueprint_and_swagger/apiary.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # Minimal 4 | 5 | Minimal API Blueprint 6 | -------------------------------------------------------------------------------- /spec/fixtures/api_blueprint_and_swagger/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "version": "1.0.0", 5 | "title": "Minimal", 6 | "description": "Minimal Swagger" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /spec/fixtures/api_blueprint_and_swagger/swagger.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: '2.0' 3 | info: 4 | version: 1.0.0 5 | title: Minimal 6 | description: Minimal Swagger 7 | -------------------------------------------------------------------------------- /spec/fixtures/apiary-invalid.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # Apiary Client Test 4 | 5 | This document is used for testing apiary-client 6 | 7 | # Group Notes 8 | Notes related resources of the **Notes API** 9 | 10 | ## Notes Collection [/notes 11 | ### List all Notes [GET] 12 | + Response 200 (application/json) 13 | 14 | [{ 15 | "id": 1, "title": "Jogging in park" 16 | }, { 17 | "id": 2, "title": "Pick-up posters from post-office" 18 | }] 19 | 20 | ### Create a Note [POST 21 | 22 | + Request 23 | 24 | { "title": "Buy cheese and bread for breakfast." } 25 | 26 | + Response 201 (application/json 27 | 28 | { "id": 3, "title": "Buy cheese and bread for breakfast." } 29 | 30 | ## Note [/notes/{id}] 31 | A single Note object with all its details 32 | 33 | + Parameters 34 | + id (required, number, `1`) ... Numeric `id` of the Note to perform action with. Has example value. 35 | 36 | ### Retrieve a Note [GET] 37 | + Response 200 (application/json) 38 | 39 | + Header 40 | 41 | X-My-Header: The Value 42 | 43 | + Body 44 | 45 | { "id": 2, "title": "Pick-up posters from post-office" } 46 | 47 | ### Remove a Note [DELETE] 48 | + Response 204 49 | -------------------------------------------------------------------------------- /spec/fixtures/apiary.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # Apiary Client Test - DON'T CHANGE DOCUMENT! 4 | 5 | This document is used for testing apiary-client 6 | 7 | # Group Notes 8 | Notes related resources of the **Notes API** 9 | 10 | ## Notes Collection [/notes 11 | ### List all Notes [GET] 12 | + Response 200 (application/json) 13 | 14 | [{ 15 | "id": 1, "title": "Jogging in park" 16 | }, { 17 | "id": 2, "title": "Pick-up posters from post-office" 18 | }] 19 | 20 | ### Create a Note [POST 21 | 22 | + Request 23 | 24 | sflmvs;mv;dsm{ "title": "Buy cheese and bread for breakfast." } 25 | 26 | + Response 201 (application/json) 27 | 28 | { "id": 3, "title": "Buy cheese and bread for breakfast." } 29 | 30 | ## Note [/notes/{id}] 31 | A single Note object with all its details 32 | 33 | + Parameters 34 | + id (required, number, `1`) ... Numeric `id` of the Note to perform action with. Has example value. 35 | 36 | ### Retrieve a Note [GET] 37 | + Response 200 (application/json) 38 | 39 | + Header 40 | 41 | X-My-Header: The Value 42 | 43 | + Body 44 | 45 | { "id": 2, "title": "Pick-up posters from post-office" } 46 | 47 | ### Remove a Note [DELETE] 48 | + Response 204 49 | -------------------------------------------------------------------------------- /spec/fixtures/apiary_with_bom.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # Apiary Client Test - DON'T CHANGE DOCUMENT! 4 | 5 | This document is used for testing apiary-client 6 | 7 | # Group Notes 8 | Notes related resources of the **Notes API** 9 | 10 | ## Notes Collection [/notes 11 | ### List all Notes [GET] 12 | + Response 200 (application/json) 13 | 14 | [{ 15 | "id": 1, "title": "Jogging in park" 16 | }, { 17 | "id": 2, "title": "Pick-up posters from post-office" 18 | }] 19 | 20 | ### Create a Note [POST 21 | 22 | + Request 23 | 24 | sflmvs;mv;dsm{ "title": "Buy cheese and bread for breakfast." } 25 | 26 | + Response 201 (application/json) 27 | 28 | { "id": 3, "title": "Buy cheese and bread for breakfast." } 29 | 30 | ## Note [/notes/{id}] 31 | A single Note object with all its details 32 | 33 | + Parameters 34 | + id (required, number, `1`) ... Numeric `id` of the Note to perform action with. Has example value. 35 | 36 | ### Retrieve a Note [GET] 37 | + Response 200 (application/json) 38 | 39 | + Header 40 | 41 | X-My-Header: The Value 42 | 43 | + Body 44 | 45 | { "id": 2, "title": "Pick-up posters from post-office" } 46 | 47 | ### Remove a Note [DELETE] 48 | + Response 204 49 | -------------------------------------------------------------------------------- /spec/fixtures/empty_folder/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apiaryio/apiary-client/d260860c3ac78134bcd9ca8925c696f42af4c4da/spec/fixtures/empty_folder/.gitkeep -------------------------------------------------------------------------------- /spec/fixtures/only_api_blueprint/apiary.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # Minimal 4 | 5 | Minimal API Blueprint 6 | -------------------------------------------------------------------------------- /spec/fixtures/only_swagger/swagger.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | swagger: '2.0' 3 | info: 4 | version: 1.0.0 5 | title: Minimal 6 | description: Minimal Swagger 7 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | 3 | if RUBY_VERSION < '1.9.3' 4 | ::Dir.glob(::File.expand_path('../support/**/*.rb', __FILE__)).each { |f| require File.join(File.dirname(f), File.basename(f, '.rb')) } 5 | else 6 | ::Dir.glob(::File.expand_path('../support/**/*.rb', __FILE__)).each { |f| require_relative f } 7 | end 8 | 9 | require 'apiary' 10 | 11 | RSpec.configure do |config| 12 | config.filter_run_excluding api_key: true if ENV['APIARY_API_KEY'] 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/aruba.rb: -------------------------------------------------------------------------------- 1 | require 'aruba/rspec' 2 | -------------------------------------------------------------------------------- /spec/support/webmock.rb: -------------------------------------------------------------------------------- 1 | require 'webmock/rspec' 2 | --------------------------------------------------------------------------------