├── .rspec ├── Gemfile ├── lib ├── rambo │ ├── rake.rb │ ├── rspec.rb │ ├── raml_models.rb │ ├── rspec │ │ ├── templates │ │ │ ├── matcher_file_template.erb │ │ │ ├── spec_file_template.erb │ │ │ ├── rambo_helper_file_template.erb │ │ │ └── example_group_template.erb │ │ ├── examples.rb │ │ ├── spec_file.rb │ │ ├── helper_file.rb │ │ └── example_group.rb │ ├── raml_models │ │ ├── response.rb │ │ ├── headers.rb │ │ ├── resource.rb │ │ ├── body.rb │ │ ├── security_scheme.rb │ │ ├── method.rb │ │ └── api.rb │ ├── rake │ │ └── task.rb │ └── document_generator.rb ├── version.rb ├── rambo.rb └── cli.rb ├── spec ├── support │ ├── foobar.yml │ ├── foobar.raml │ ├── raml_with_media_type.raml │ ├── foo.raml │ ├── basic_raml_with_post_route.raml │ ├── post_with_request_headers.raml │ ├── multiple_headers.raml │ ├── multiple_resources.raml │ └── secured_api.raml ├── lib │ ├── rambo │ │ ├── rake │ │ │ └── task_spec.rb │ │ ├── rspec │ │ │ ├── helper_file_spec.rb │ │ │ ├── examples_spec.rb │ │ │ ├── spec_file_spec.rb │ │ │ └── example_group_spec.rb │ │ ├── raml_models │ │ │ ├── response_spec.rb │ │ │ ├── security_scheme_spec.rb │ │ │ ├── body_spec.rb │ │ │ ├── resource_spec.rb │ │ │ ├── api_spec.rb │ │ │ ├── method_spec.rb │ │ │ └── headers_spec.rb │ │ ├── cli_spec.rb │ │ └── document_generator_spec.rb │ └── rambo_spec.rb └── spec_helper.rb ├── features ├── support │ ├── examples │ │ ├── rspec │ │ │ ├── empty_spec.rb.example │ │ │ ├── spec_helper_only_rack_test.rb.example │ │ │ ├── spec_helper_only_json.rb.example │ │ │ ├── spec_helper_rack_test_added.rb.example │ │ │ ├── spec_helper_json_added.rb.example │ │ │ ├── simple_spec_file_with_example.rb.example │ │ │ ├── simple_spec_file_with_schema.rb.example │ │ │ ├── simple_spec_file_with_post_route.rb.example │ │ │ └── spec_file_with_request_headers.rb.example │ │ ├── raml │ │ │ ├── empty_raml.raml │ │ │ ├── multiple_routes.raml │ │ │ ├── basic_raml_with_schema.raml │ │ │ ├── basic_raml_with_example.raml │ │ │ ├── basic_raml_with_post_route.raml │ │ │ └── post_with_request_headers.raml │ │ └── json │ │ │ ├── basic_raml_with_schema_response.json │ │ │ ├── basic_raml_with_example_response.json │ │ │ └── basic_raml_with_post_route_response.json │ ├── env.rb │ ├── cucumber_helper.rb │ └── foobar.raml ├── modify_spec_helper.feature ├── error_modes.feature ├── generate_post_specs.feature ├── create_files.feature ├── generate_simple_api_specs.feature └── step_definitions │ └── file_steps.rb ├── .simplecov ├── assets └── logo.txt ├── Rakefile ├── .travis.yml ├── bin └── rambo ├── .gitignore ├── LICENSE ├── rambo_ruby.gemspec ├── ISSUE_TEMPLATE.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'pry' 6 | -------------------------------------------------------------------------------- /lib/rambo/rake.rb: -------------------------------------------------------------------------------- 1 | Dir["#{File.dirname(__FILE__)}/rake/**/*.rb"].each {|file| require file } 2 | -------------------------------------------------------------------------------- /lib/rambo/rspec.rb: -------------------------------------------------------------------------------- 1 | Dir["#{File.dirname(__FILE__)}/rspec/**/*.rb"].each {|file| require file } 2 | -------------------------------------------------------------------------------- /lib/rambo/raml_models.rb: -------------------------------------------------------------------------------- 1 | Dir["#{File.dirname(__FILE__)}/raml_models/**/*.rb"].each {|file| require file } 2 | -------------------------------------------------------------------------------- /spec/support/foobar.yml: -------------------------------------------------------------------------------- 1 | title: e-BookMobile API 2 | baseUri: http://api.e-bookmobile.com/{version} 3 | version: v1 -------------------------------------------------------------------------------- /features/support/examples/rspec/empty_spec.rb.example: -------------------------------------------------------------------------------- 1 | require "rambo_helper" 2 | 3 | RSpec.describe "e-BookMobile API", type: :request do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /features/modify_spec_helper.feature: -------------------------------------------------------------------------------- 1 | Feature: Modify spec helper 2 | 3 | Background: 4 | Given a file named "foo.raml" like "basic_raml_with_example.raml" 5 | -------------------------------------------------------------------------------- /features/support/examples/raml/empty_raml.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'coveralls' 3 | 4 | SimpleCov.formatter = Coveralls::SimpleCov::Formatter 5 | SimpleCov.start do 6 | add_filter 'raml-rb' 7 | end 8 | -------------------------------------------------------------------------------- /lib/version.rb: -------------------------------------------------------------------------------- 1 | module Rambo 2 | MAJOR = '0' 3 | MINOR = '7' 4 | PATCH = '1' 5 | 6 | def self.version 7 | [Rambo::MAJOR, Rambo::MINOR, Rambo::PATCH].join('.') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /assets/logo.txt: -------------------------------------------------------------------------------- 1 | ____ ___ __ ___ ____ ____ 2 | / __ \ / | / |/ // __ ) / __ \ 3 | / /_/ // /| | / /|_/ // __ |/ / / / 4 | / _, _// ___ | / / / // /_/ // /_/ / 5 | /_/ |_|/_/ |_|/_/ /_//_____/ \____/ 6 | -------------------------------------------------------------------------------- /lib/rambo/rspec/templates/matcher_file_template.erb: -------------------------------------------------------------------------------- 1 | require "rspec/expectations" 2 | require "json-schema" 3 | 4 | RSpec::Matchers.define :match_schema do |expected| 5 | match do |actual| 6 | JSON::Validator.validate(expected, actual) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | require 'cucumber' 3 | require 'cucumber/rake/task' 4 | 5 | RSpec::Core::RakeTask.new(:spec) 6 | Cucumber::Rake::Task.new(:cucumber) do |t| 7 | t.cucumber_opts = '--format pretty' 8 | end 9 | 10 | task :default => [:spec, :cucumber] -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1.8 4 | - 2.2.0 5 | - 2.3.1 6 | script: 7 | - bundle exec rspec 8 | env: 9 | - COVERAGE=true 10 | script: 11 | - bundle exec rspec 12 | branches: 13 | only: 14 | - master 15 | sudo: false 16 | notifications: 17 | - email: false 18 | -------------------------------------------------------------------------------- /features/support/examples/json/basic_raml_with_schema_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "schema for a list of authors", 4 | "type": "object", 5 | "properties": { 6 | "data": { 7 | "type": "number" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /features/support/examples/rspec/spec_helper_only_rack_test.rb.example: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "rspec/core" 3 | require "rspec/matchers" 4 | require "rspec/expectations" 5 | require "raml-rb" 6 | 7 | path = File.expand_path('../../lib', __FILE__) 8 | 9 | Dir.foreach(path) {|f| require f if f.match(/.*\.rb\z/) } 10 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | 3 | Coveralls.wear! if ENV["COVERAGE"] == "true" 4 | 5 | require 'aruba/cucumber' 6 | require_relative './cucumber_helper' 7 | 8 | World(CucumberHelper) 9 | 10 | CUCUMBER_DIR_ROOT = File.expand_path("../", __FILE__) 11 | RAMBO_ROOT = File.expand_path("../../lib", __FILE__) 12 | -------------------------------------------------------------------------------- /features/support/examples/rspec/spec_helper_only_json.rb.example: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "rspec/core" 3 | require "rspec/matchers" 4 | require "rspec/expectations" 5 | require "raml-rb" 6 | require "json" 7 | 8 | path = File.expand_path('../../lib', __FILE__) 9 | 10 | Dir.foreach(path) {|f| require f if f.match(/.*\.rb\z/) } 11 | -------------------------------------------------------------------------------- /features/support/examples/rspec/spec_helper_rack_test_added.rb.example: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "rspec/core" 3 | require "rspec/matchers" 4 | require "rspec/expectations" 5 | require "raml-rb" 6 | require "json" 7 | 8 | path = File.expand_path('../../lib', __FILE__) 9 | 10 | Dir.foreach(path) {|f| require f if f.match(/.*\.rb\z/) } 11 | -------------------------------------------------------------------------------- /features/support/examples/rspec/spec_helper_json_added.rb.example: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "rspec/core" 3 | require "rspec/matchers" 4 | require "rspec/expectations" 5 | require "raml-rb" 6 | require "rack/test" 7 | require "json" 8 | 9 | path = File.expand_path('../../lib', __FILE__) 10 | 11 | Dir.foreach(path) {|f| require f if f.match(/.*\.rb\z/) } 12 | -------------------------------------------------------------------------------- /features/support/examples/json/basic_raml_with_example_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 1, 5 | "first_name": "Hermann", 6 | "last_name": "Hesse" 7 | }, 8 | { 9 | "id": 2, 10 | "first_name": "Charles", 11 | "last_name": "Dickens" 12 | } 13 | ], 14 | "success": true, 15 | "status": 200 16 | } 17 | -------------------------------------------------------------------------------- /features/support/examples/raml/multiple_routes.raml: -------------------------------------------------------------------------------- 1 | #% RAML 0.8 2 | --- 3 | title: e-Bookmobile API 4 | baseUri: http://api.ebookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | get: 9 | description: Retrieve a list of authors 10 | responses: 11 | 201: 12 | post: 13 | description: Add an author to the database 14 | responses: 15 | 204: 16 | -------------------------------------------------------------------------------- /lib/rambo/raml_models/response.rb: -------------------------------------------------------------------------------- 1 | module Rambo 2 | module RamlModels 3 | class Response 4 | attr_reader :schema 5 | 6 | def initialize(raml) 7 | @schema = raml 8 | end 9 | 10 | def status_code 11 | schema.code 12 | end 13 | 14 | def bodies 15 | @bodies ||= schema.bodies.map {|body| Rambo::RamlModels::Body.new(body) } 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /features/error_modes.feature: -------------------------------------------------------------------------------- 1 | Feature: Error handling 2 | 3 | Scenario: No filename is given 4 | When I run `rambo` 5 | Then it should fail with "USAGE: rambo [FILE]" 6 | 7 | Scenario: File given is not a RAML file 8 | Given a file "foobar.yml" with: 9 | """ 10 | foo: bar 11 | """ 12 | When I run `rambo foobar.yml` 13 | Then it should fail with "Unsupported file format. Please choose a RAML file." 14 | -------------------------------------------------------------------------------- /features/support/cucumber_helper.rb: -------------------------------------------------------------------------------- 1 | module CucumberHelper 2 | def read_example(example) 3 | possible_paths = [ 4 | File.join(CUCUMBER_DIR_ROOT, "examples/raml/#{example}"), 5 | File.join(CUCUMBER_DIR_ROOT, "examples/rspec/#{example}"), 6 | File.join(CUCUMBER_DIR_ROOT, "examples/rspec/#{example}") 7 | ] 8 | 9 | possible_paths.each do |path| 10 | return File.read(path) rescue next 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /features/support/examples/json/basic_raml_with_post_route_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "description": "schema for a list of authors", 4 | "type": "object", 5 | "properties": { 6 | "author": { 7 | "type": "object", 8 | "properties": { 9 | "id": { "type": "integer" }, 10 | "first_name": { "type": "string" }, 11 | "last_name": { "type": "string" } 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/rambo/raml_models/headers.rb: -------------------------------------------------------------------------------- 1 | module Rambo 2 | module RamlModels 3 | class Headers 4 | attr_accessor :headers 5 | 6 | def initialize(headers) 7 | @headers = headers 8 | end 9 | 10 | def add(hash) 11 | headers.merge!(hash) 12 | self 13 | end 14 | 15 | def merge!(other) 16 | add(other.headers) 17 | end 18 | 19 | def pretty 20 | JSON.pretty_generate(headers).gsub("\":", "\" =>") 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/rambo/rspec/templates/spec_file_template.erb: -------------------------------------------------------------------------------- 1 | require "rambo_helper" 2 | 3 | RSpec.describe "<%= @raml.title %>", type: :rambo do 4 | 5 | # Delete output files from previous test run prior to running tests again 6 | before(:all) do 7 | Dir.foreach("spec/contract/output") do |file| 8 | next unless file.match(/\.json$/) 9 | 10 | File.delete(File.join("spec/contract/output", file)) 11 | end 12 | end 13 | <%- if @examples.generate! && @examples.compose.size > 0 %> 14 | <%= @examples.compose.chomp %><%- end %> 15 | end 16 | -------------------------------------------------------------------------------- /spec/lib/rambo/rake/task_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::Rake::Task do 2 | describe "#initialize" do 3 | it "defines the task" do 4 | expect_any_instance_of(Rambo::Rake::Task) 5 | .to receive(:define_task) 6 | described_class.new 7 | end 8 | end 9 | 10 | describe "rambo task" do 11 | before(:each) do 12 | Rambo::Rake::Task.new 13 | end 14 | 15 | it "calls generate_contract_tests!" do 16 | expect(Rambo).to receive(:generate_contract_tests!) 17 | Rake::Task[:rambo].invoke 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/rambo/rspec/templates/rambo_helper_file_template.erb: -------------------------------------------------------------------------------- 1 | require "<%= @options.fetch(:framework, nil).nil? || @options.fetch(:framework) == :rails ? "rails_helper" : "spec_helper" %>" 2 | require "rack/test" 3 | require_relative "./support/matchers/rambo_matchers" 4 | 5 | module ApiHelper 6 | include Rack::Test::Methods 7 | 8 | def app 9 | require "active_support/core_ext/class/subclasses" 10 | <%= app_classes.fetch(@options.fetch(:framework, :rails)) %> 11 | end 12 | end 13 | 14 | RSpec.configure do |config| 15 | config.include ApiHelper, type: :rambo 16 | end -------------------------------------------------------------------------------- /features/generate_post_specs.feature: -------------------------------------------------------------------------------- 1 | Feature: Generate POST specs 2 | Scenario: Simple POST route 3 | Given a file named "foo.raml" like "basic_raml_with_post_route.raml" 4 | When I run `rambo foo.raml` 5 | Then the file "spec/contract/foo_spec.rb" should be like "simple_spec_file_with_post_route.rb.example" 6 | 7 | Scenario: POST route with headers 8 | Given a file named "foo.raml" like "post_with_request_headers.raml" 9 | When I run `rambo foo.raml` 10 | Then the file "spec/contract/foo_spec.rb" should be like "spec_file_with_request_headers.rb.example" 11 | -------------------------------------------------------------------------------- /spec/lib/rambo/rspec/helper_file_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RSpec::HelperFile do 2 | let(:template_path) { 3 | File.join(RAMBO_ROOT, "rambo/rspec/templates/matcher_file_template.erb") 4 | } 5 | 6 | let(:file_path) { 7 | File.join(SPEC_DIR_ROOT, "support/matchers/rambo_matchers.rb") 8 | } 9 | 10 | subject { described_class.new(template_path: template_path, file_path: file_path) } 11 | 12 | describe "generate" do 13 | it "writes the file" do 14 | expect(File).to receive(:write).with(file_path, File.read(template_path)) 15 | subject.generate 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/rambo/raml_models/resource.rb: -------------------------------------------------------------------------------- 1 | module Rambo 2 | module RamlModels 3 | class Resource 4 | 5 | attr_reader :schema, :headers 6 | private :headers, :schema 7 | 8 | def initialize(raml_resource, headers=Rambo::RamlModels::Headers.new({})) 9 | @schema = raml_resource 10 | @headers = headers 11 | end 12 | 13 | def uri_partial 14 | schema.uri_partial 15 | end 16 | 17 | alias_method :to_s, :uri_partial 18 | 19 | def http_methods 20 | @http_methods ||= schema.http_methods.map {|method| Rambo::RamlModels::Method.new(method, headers) } 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /features/create_files.feature: -------------------------------------------------------------------------------- 1 | Feature: Create specs from RAML 2 | 3 | The example files can be found in features/support/examples 4 | 5 | Scenario: Generate specs from a simple RAML file 6 | Given a file named "foo.raml" like "empty_raml.raml" 7 | When I run `rambo foo.raml` 8 | Then the directory "spec/contract" should exist 9 | And the file "spec/contract/foo_spec.rb" should exist 10 | And the file "spec/rambo_helper.rb" should exist 11 | And the file "spec/rambo_helper.rb" should contain: 12 | """ 13 | require "spec_helper" 14 | """ 15 | And the file "spec/contract/foo_spec.rb" should be like "empty_spec.rb.example" 16 | And the exit status should be 0 17 | -------------------------------------------------------------------------------- /lib/rambo/rake/task.rb: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("../../..", __FILE__) 2 | root = File.expand_path("../../..") 3 | 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | require "rake" 7 | require "yaml" 8 | require "colorize" 9 | require "rambo" 10 | 11 | module Rambo 12 | module Rake 13 | class Task 14 | include ::Rake::DSL 15 | 16 | def initialize 17 | define_task 18 | end 19 | 20 | private 21 | 22 | def define_task 23 | desc "Generate contract tests" 24 | task :rambo do 25 | Rambo.generate_contract_tests! 26 | 27 | puts "Done generating contract tests.".green 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /features/support/examples/raml/basic_raml_with_schema.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | get: 9 | description: Retrieve a list of authors 10 | responses: 11 | 200: 12 | body: 13 | application/json: 14 | schema: | 15 | { 16 | "$schema": "http://json-schema.org/draft-04/schema#", 17 | "description": "schema for a list of authors", 18 | "type": "object", 19 | "properties": { 20 | "data": { 21 | "type": "number" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/rambo/raml_models/body.rb: -------------------------------------------------------------------------------- 1 | require "json_test_data" 2 | 3 | module Rambo 4 | module RamlModels 5 | class Body 6 | 7 | attr_reader :body, :type 8 | 9 | def initialize(raml) 10 | @body = raml 11 | end 12 | 13 | def content_type 14 | body.content_type 15 | end 16 | 17 | def example 18 | @example ||= body.example || generate_from_schema || {}.to_json 19 | end 20 | 21 | def schema 22 | @schema ||= body.schema 23 | end 24 | 25 | private 26 | 27 | def generate_from_schema 28 | return nil unless body.schema 29 | JSON.pretty_generate(JsonTestData.generate!(body.schema, ruby: true)) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "coveralls" 2 | 3 | Coveralls.wear! if ENV["COVERAGE"] == "true" 4 | 5 | require "rspec" 6 | require "rspec/core" 7 | require "rspec/matchers" 8 | require "rspec/expectations" 9 | require "raml-rb" 10 | require "json_test_data" 11 | require "fileutils" 12 | 13 | SPEC_DIR_ROOT = File.expand_path("../", __FILE__) 14 | RAMBO_ROOT = File.expand_path("../../lib", __FILE__) 15 | lib = File.expand_path(File.join(RAMBO_ROOT, "**/*.rb")) 16 | Dir[lib].each {|f| require f } 17 | 18 | RSpec.configure do |c| 19 | c.disable_monkey_patching! 20 | c.order = :random 21 | 22 | c.after(:each) do 23 | File.delete("spec/rambo_helper.rb") rescue nil 24 | FileUtils.rm_rf("spec/support/matchers") rescue nil 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/lib/rambo/raml_models/response_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RamlModels::Response do 2 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/foobar.raml") } 3 | let(:raml) { Raml::Parser.parse_file(raml_file) } 4 | let(:response) { raml.resources.first.http_methods.first.responses.first } 5 | 6 | subject { described_class.new(response) } 7 | 8 | describe "#status_code" do 9 | it "returns the response status code" do 10 | expect(subject.status_code).to eql response.code 11 | end 12 | end 13 | 14 | describe "bodies" do 15 | it "creates an array of Body objects" do 16 | all_are_bodies = subject.bodies.all? {|body| body.is_a?(Rambo::RamlModels::Body) } 17 | expect(all_are_bodies).to be true 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/rambo/rspec/examples.rb: -------------------------------------------------------------------------------- 1 | require "rambo/rspec/example_group" 2 | 3 | module Rambo 4 | module RSpec 5 | class Examples 6 | attr_reader :raml, :resources, :examples, :options 7 | 8 | def initialize(raml, options=nil) 9 | @raml = raml 10 | @options = options 11 | end 12 | 13 | def compose 14 | return '' unless examples 15 | 16 | examples.join("\n\n") 17 | end 18 | 19 | def resources 20 | @resources ||= raml.resources 21 | end 22 | 23 | def example_groups 24 | @example_groups ||= resources.map {|r| ExampleGroup.new(r, options) } 25 | end 26 | 27 | def generate! 28 | @examples = example_groups.map(&:render) 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /bin/rambo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | lib = File.expand_path("../../lib", __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | 6 | require "fileutils" 7 | require "cli" 8 | require "optparse" 9 | 10 | filename = ARGV[0] ? File.expand_path(ARGV[0], FileUtils.pwd) : nil 11 | 12 | options = {} 13 | 14 | OptionParser.new do |opts| 15 | opts.banner = "Usage: rambo [FILE] [OPTIONS]" 16 | 17 | options[:framework] = :rails 18 | opts.on("--framework=FRAMEWORK", "Choose rails (default), sinatra:classic, sinatra:modular, or grape") do |v| 19 | options[:framework] = v.to_sym 20 | end 21 | opts.on("--token=TOKEN", "-T=TOKEN", "Specify an API token") do |v| 22 | options[:token] = v 23 | end 24 | end.parse! 25 | 26 | Rambo::CLI.new(filename, options, STDOUT).run! 27 | -------------------------------------------------------------------------------- /features/generate_simple_api_specs.feature: -------------------------------------------------------------------------------- 1 | Feature: Generate simple API specs 2 | 3 | Scenario: Route with example 4 | Given a file named "foo.raml" like "basic_raml_with_example.raml" 5 | When I run `rambo foo.raml` 6 | Then the file "spec/contract/foo_spec.rb" should be like "simple_spec_file_with_example.rb.example" 7 | And the file "spec/support/examples/authors_response_body.json" should be like "basic_raml_with_example_response.json" 8 | 9 | Scenario: Route with schema 10 | Given a file named "foo.raml" like "basic_raml_with_schema.raml" 11 | When I run `rambo foo.raml` 12 | Then the file "spec/contract/foo_spec.rb" should be like "simple_spec_file_with_schema.rb.example" 13 | And the file "spec/support/examples/authors_response_schema.json" should be like "basic_raml_with_schema_response.json" 14 | -------------------------------------------------------------------------------- /spec/support/foobar.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | get: 9 | description: Retrieve a list of authors 10 | responses: 11 | 200: 12 | body: 13 | application/json: 14 | example: | 15 | { 16 | "data": [ 17 | { 18 | "id": 1, 19 | "first_name": "Hermann", 20 | "last_name": "Hesse" 21 | }, 22 | { 23 | "id":2, 24 | "first_name": "Charles", 25 | "last_name": "Dickens" 26 | } 27 | ], 28 | "success": true, 29 | "status": 200 30 | } 31 | -------------------------------------------------------------------------------- /features/support/foobar.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | get: 9 | description: Retrieve a list of authors 10 | responses: 11 | 200: 12 | body: 13 | application/json: 14 | example: | 15 | { 16 | "data": [ 17 | { 18 | "id": 1, 19 | "first_name": "Hermann", 20 | "last_name": "Hesse" 21 | }, 22 | { 23 | "id": 2, 24 | "first_name": "Charles", 25 | "last_name": "Dickens" 26 | } 27 | ], 28 | "success": true, 29 | "status": 200 30 | } 31 | -------------------------------------------------------------------------------- /lib/rambo/rspec/spec_file.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'raml' 3 | 4 | require "rambo/rspec/examples" 5 | require "rambo/raml_models" 6 | 7 | module Rambo 8 | module RSpec 9 | class SpecFile 10 | attr_reader :raml, :examples, :options 11 | 12 | TEMPLATE_PATH = File.expand_path('../templates/spec_file_template.erb', __FILE__) 13 | 14 | def initialize(raml, options={}) 15 | @raml = Rambo::RamlModels::Api.new(raml, options) 16 | @options = options 17 | @examples = Examples.new(@raml, @options) 18 | end 19 | 20 | def template 21 | @template ||= File.read(TEMPLATE_PATH) 22 | end 23 | 24 | def render 25 | b = binding 26 | ERB.new(template, 0, "-", "@result").result(raml.instance_eval { b }) 27 | @result 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | ## Specific to RubyMotion: 14 | .dat* 15 | .repl_history 16 | build/ 17 | 18 | ## Documentation cache and generated files: 19 | /.yardoc/ 20 | /_yardoc/ 21 | /doc/ 22 | /rdoc/ 23 | 24 | ## Environment normalization: 25 | /.bundle/ 26 | /vendor/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 | /foobar.raml 39 | /foo.raml 40 | raml.rb 41 | 42 | /spec/contract/ 43 | /spec/rambo_helper.rb 44 | -------------------------------------------------------------------------------- /features/support/examples/raml/basic_raml_with_example.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | get: 9 | description: Retrieve a list of authors 10 | responses: 11 | 200: 12 | body: 13 | application/json: 14 | example: | 15 | { 16 | "data": [ 17 | { 18 | "id": 1, 19 | "first_name": "Hermann", 20 | "last_name": "Hesse" 21 | }, 22 | { 23 | "id": 2, 24 | "first_name": "Charles", 25 | "last_name": "Dickens" 26 | } 27 | ], 28 | "success": true, 29 | "status": 200 30 | } 31 | -------------------------------------------------------------------------------- /spec/lib/rambo/raml_models/security_scheme_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RamlModels::SecurityScheme do 2 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/secured_api.raml") } 3 | let(:raml) { Raml::Parser.parse_file(raml_file).security_schemes.first } 4 | 5 | subject { described_class.new(raml, { token: "foobarbaz" }) } 6 | 7 | it { is_expected.to respond_to :title } 8 | 9 | describe "#headers" do 10 | it "is a Headers object" do 11 | expect(subject.headers).to be_a(Rambo::RamlModels::Headers) 12 | end 13 | 14 | it "uses the API token from the options" do 15 | expect(subject.headers.headers).to eql({ "Api-Token" => "foobarbaz" }) 16 | end 17 | end 18 | 19 | describe "#api_token_header" do 20 | it "returns the key of a matching header" do 21 | expect(subject.api_token_header).to eql "Api-Token" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/rambo/raml_models/security_scheme.rb: -------------------------------------------------------------------------------- 1 | module Rambo 2 | module RamlModels 3 | class SecurityScheme 4 | attr_accessor :schema, :title 5 | private :schema 6 | 7 | def initialize(raml, options={}) 8 | @options = options 9 | @schema, @title = raml.last.fetch("describedBy", {}), raml.first 10 | use_token! 11 | end 12 | 13 | def use_token! 14 | if schema.fetch("headers", nil) 15 | schema.fetch("headers")[api_token_header] = @options[:token] 16 | end 17 | end 18 | 19 | def api_token_header 20 | return unless h = schema.fetch("headers", nil) 21 | 22 | h.find {|key, value| key.match(/(token|key)/i) }.first 23 | end 24 | 25 | def headers 26 | @headers ||= Rambo::RamlModels::Headers.new(schema.fetch("headers") || {}) 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/support/raml_with_media_type.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | mediaType: application/json 7 | 8 | /authors: 9 | get: 10 | description: Retrieve a list of authors 11 | responses: 12 | 200: 13 | body: 14 | application/json: 15 | example: | 16 | { 17 | "data": [ 18 | { 19 | "id": 1, 20 | "first_name": "Hermann", 21 | "last_name": "Hesse" 22 | }, 23 | { 24 | "id":2, 25 | "first_name": "Charles", 26 | "last_name": "Dickens" 27 | } 28 | ], 29 | "success": true, 30 | "status": 200 31 | } 32 | -------------------------------------------------------------------------------- /features/support/examples/rspec/simple_spec_file_with_example.rb.example: -------------------------------------------------------------------------------- 1 | require "rambo_helper" 2 | 3 | RSpec.describe "e-BookMobile API", type: :request do 4 | 5 | describe "/authors" do 6 | let(:route) { "/authors" } 7 | 8 | describe "GET" do 9 | let(:response_body) do 10 | File.read("spec/support/examples/authors_get_response_body.json") 11 | end 12 | 13 | let(:output_file) do 14 | "spec/contract/output/authors_get_response.json" 15 | end 16 | 17 | it "retrieve a list of authors" do 18 | get route 19 | 20 | File.open(output_file, "w+") {|file| file.puts JSON.pretty_generate(JSON.parse(response.body)) } 21 | 22 | expect(response.body).to eql response_body 23 | end 24 | 25 | it "returns status 200" do 26 | get route 27 | expect(response.status).to eql 200 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /features/support/examples/rspec/simple_spec_file_with_schema.rb.example: -------------------------------------------------------------------------------- 1 | require "rambo_helper" 2 | 3 | RSpec.describe "e-BookMobile API", type: :request do 4 | 5 | describe "/authors" do 6 | let(:route) { "/authors" } 7 | 8 | describe "GET" do 9 | let(:response_schema) do 10 | File.read("spec/support/examples/authors_response_schema.json") 11 | end 12 | 13 | let(:output_file) do 14 | "spec/contract/output/authors_get_response.json" 15 | end 16 | 17 | it "retrieve a list of authors" do 18 | get route 19 | 20 | File.open(output_file, "w+") {|file| file.puts JSON.pretty_generate(JSON.parse(response.body)) } 21 | 22 | expect(response.body).to match_schema response_schema 23 | end 24 | 25 | it "returns status 200" do 26 | get route 27 | expect(response.status).to eql 200 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/rambo/raml_models/method.rb: -------------------------------------------------------------------------------- 1 | module Rambo 2 | module RamlModels 3 | class Method 4 | attr_reader :schema 5 | 6 | def initialize(raml_method, headers=Headers.new({})) 7 | @schema = raml_method 8 | @headers = headers 9 | end 10 | 11 | def method 12 | schema.method 13 | end 14 | 15 | def request_body 16 | Rambo::RamlModels::Body.new(schema.bodies.first) if has_request_body? 17 | end 18 | 19 | def description 20 | @description ||= schema.description 21 | end 22 | 23 | def headers 24 | @headers.add(schema.headers) if schema.headers 25 | end 26 | 27 | def responses 28 | @responses ||= schema.responses.map {|resp| Rambo::RamlModels::Response.new(resp) } 29 | end 30 | 31 | private 32 | 33 | def has_request_body? 34 | !!schema.bodies.first 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /features/support/examples/rspec/simple_spec_file_with_post_route.rb.example: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "e-BookMobile API", type: :request do 4 | 5 | describe "/authors" do 6 | let(:route) { "/authors" } 7 | 8 | describe "POST" do 9 | let(:request_body) do 10 | File.read("spec/support/examples/authors_post_request_body.json") 11 | end 12 | 13 | let(:response_schema) do 14 | File.read("spec/support/examples/authors_post_response_schema.json") 15 | end 16 | 17 | let(:output_file) do 18 | "spec/contract/output/authors_post_response.json" 19 | end 20 | 21 | it "retrieve a list of authors" do 22 | get route 23 | 24 | File.open(output_file, "w+") {|file| file.puts(JSON.pretty_generate(JSON.parse(response.body))) } 25 | 26 | expect(response.body).to eql response_body 27 | end 28 | 29 | it "returns status 200" do 30 | get route 31 | expect(response.status).to eql 200 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /features/step_definitions/file_steps.rb: -------------------------------------------------------------------------------- 1 | Given(/^a file named "([^"]*)" like "([^"]*)"$/) do |file, example| 2 | text = read_example(example) 3 | step %(a file named "#{file}" with:), text 4 | end 5 | 6 | Given(/^a spec_helper\.rb file that requires rack\/test but not JSON$/) do 7 | step 'a file named "spec/rambo_helper.rb" like "spec_helper_only_rack_test.rb.example"' 8 | end 9 | 10 | Given(/^a spec_helper\.rb file that requires JSON but not rack\/test$/) do 11 | step 'a file named "spec/rambo_helper.rb" like "spec_helper_only_json.rb.example"' 12 | end 13 | 14 | Given(/a spec_helper.rb file that requires both JSON and rack\/test$/) do 15 | step 'a file named "spec/rambo_helper.rb" like "good_spec_helper.rb.example"' 16 | end 17 | 18 | Then(/^the file "([^"]*)" should be like "([^"]*)"$/) do |file, example| 19 | text = read_example(example) 20 | step %(a file "#{file}" should contain:), text 21 | end 22 | 23 | Then(/^the file "([^"]*)" should require "([^"]*)"$/) do |file, mod| 24 | steps %Q{the file "#{file}" should match /require "#{mod}"/} 25 | end 26 | -------------------------------------------------------------------------------- /spec/lib/rambo/raml_models/body_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RamlModels::Body do 2 | let(:raml) { Raml::Parser.parse_file(raml_file) } 3 | let(:body) { raml.resources.first.http_methods.first.responses.first.bodies.first } 4 | 5 | subject { described_class.new(body) } 6 | 7 | describe "#content_type" do 8 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/foobar.raml") } 9 | 10 | it "returns the content type" do 11 | expect(subject.content_type).to eql body.content_type 12 | end 13 | end 14 | 15 | describe "#example" do 16 | context "when an example is given" do 17 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/foobar.raml") } 18 | 19 | it "returns an example" do 20 | expect(subject.example).to eql body.example 21 | end 22 | end 23 | 24 | context "when a schema is given" do 25 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "../features/support/examples/raml/basic_raml_with_schema.raml") } 26 | 27 | it "returns generated JSON data" do 28 | expect(JSON.parse(subject.example)).to have_key "data" 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dana Scheider 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/lib/rambo/raml_models/resource_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RamlModels::Resource do 2 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/post_with_request_headers.raml") } 3 | let(:raml) { Raml::Parser.parse_file(raml_file) } 4 | let(:resource) { raml.resources.first } 5 | let(:headers) { Rambo::RamlModels::Headers.new(raml.resources.first.http_methods.first.headers) } 6 | 7 | subject { described_class.new(resource, headers) } 8 | 9 | describe "#to_s" do 10 | it "returns the URI partial" do 11 | expect(subject.to_s).to eql resource.uri_partial 12 | end 13 | end 14 | 15 | describe "#uri_partial" do 16 | it "returns the URI partial" do 17 | expect(subject.uri_partial).to eql resource.uri_partial 18 | end 19 | end 20 | 21 | describe "#http_methods" do 22 | it "returns the correct methods" do 23 | expect(subject.http_methods.count).to eql 1 24 | end 25 | 26 | it "returns an array of Method objects" do 27 | all_are_methods = subject.http_methods.all? {|method| method.is_a?(Rambo::RamlModels::Method) } 28 | expect(all_are_methods).to be true 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/lib/rambo/rspec/examples_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RSpec::Examples do 2 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/foobar.raml") } 3 | let(:raw_raml) { Raml::Parser.parse_file(raml_file) } 4 | let(:options) { { rails: true } } 5 | let(:raml) { Rambo::RamlModels::Api.new(raw_raml) } 6 | 7 | subject { Rambo::RSpec::Examples.new(raml, options) } 8 | 9 | before(:each) do 10 | FileUtils.mkdir_p(File.expand_path("spec/support/examples")) 11 | end 12 | 13 | after(:each) do 14 | FileUtils.rm_rf(File.expand_path("spec/support/examples")) 15 | end 16 | 17 | describe "#generate!" do 18 | it "calls render on each group" do 19 | expect_any_instance_of(Rambo::RSpec::ExampleGroup).to receive(:render) 20 | subject.generate! 21 | end 22 | 23 | it "returns an array of strings" do 24 | aggregate_failures do 25 | expect(subject.generate!).to be_a(Array) 26 | expect(subject.generate!.first).to be_a(String) 27 | end 28 | end 29 | end 30 | 31 | describe "#compose" do 32 | before(:each) { subject.generate! } 33 | 34 | it "returns a string" do 35 | expect(subject.compose).to be_a(String) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/lib/rambo/raml_models/api_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RamlModels::Api do 2 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/secured_api.raml") } 3 | let(:raml) { Raml::Parser.parse_file(raml_file) } 4 | 5 | subject { described_class.new(raml, { :token => "foobarbaz" }) } 6 | 7 | describe "#resources" do 8 | it "has the right number of resources" do 9 | expect(subject.resources.count).to eql 2 10 | end 11 | 12 | it "returns an array of Resource objects" do 13 | all_are_resources = subject.resources.all? {|resource| resource.is_a?(Rambo::RamlModels::Resource) } 14 | expect(all_are_resources).to be true 15 | end 16 | end 17 | 18 | describe "#title" do 19 | it "returns the API title from the RAML doc" do 20 | expect(subject.title).to eql raml.title 21 | end 22 | end 23 | 24 | describe "#security_schemes" do 25 | it "returns the security schemes" do 26 | expect(subject.security_schemes.all? {|scheme| scheme.is_a?(Rambo::RamlModels::SecurityScheme) }).to be true 27 | end 28 | end 29 | 30 | describe "headers" do 31 | it "incorporates API token headers" do 32 | expect(subject.headers.headers).to include({ "Api-Token" => "foobarbaz" }) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/lib/rambo/raml_models/method_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RamlModels::Method do 2 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/raml_with_media_type.raml") } 3 | let(:raml) { Raml::Parser.parse_file(raml_file) } 4 | let(:method) { raml.resources.first.http_methods.first } 5 | let(:headers) { Rambo::RamlModels::Headers.new({ "Accept" => "application/json" }) } 6 | 7 | subject { described_class.new(method, headers) } 8 | 9 | describe "#to_s" do 10 | it "returns the method name" do 11 | expect(subject.method).to eql method.method 12 | end 13 | end 14 | 15 | describe "#description" do 16 | it "returns the description" do 17 | expect(subject.description).to eql method.description 18 | end 19 | end 20 | 21 | describe "#request_body" do 22 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/post_with_request_headers.raml") } 23 | 24 | it "returns a request body" do 25 | expect(subject.request_body).to be_a Rambo::RamlModels::Body 26 | end 27 | end 28 | 29 | describe "#responses" do 30 | it "returns an array of Response objects" do 31 | all_are_responses = subject.responses.all? {|resp| resp.is_a?(Rambo::RamlModels::Response) } 32 | expect(all_are_responses).to be true 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/rambo/raml_models/api.rb: -------------------------------------------------------------------------------- 1 | module Rambo 2 | module RamlModels 3 | class Api 4 | attr_reader :schema, :options 5 | 6 | def initialize(parsed_raml, options={}) 7 | @schema, @options = parsed_raml, options 8 | end 9 | 10 | def resources 11 | @resources ||= schema.resources.map {|resource| Rambo::RamlModels::Resource.new(resource, headers) } 12 | end 13 | 14 | def title 15 | @title ||= schema.title 16 | end 17 | 18 | def security_schemes 19 | @security_schemes ||= schema.security_schemes.map {|scheme| Rambo::RamlModels::SecurityScheme.new(scheme, options) } 20 | end 21 | 22 | def headers 23 | @headers ||= Rambo::RamlModels::Headers.new({}) 24 | 25 | add_content_type_header!(@headers) 26 | add_security_headers!(@headers) 27 | 28 | @headers 29 | end 30 | 31 | private 32 | 33 | def add_content_type_header!(h) 34 | h.add({ "Content-Type" => schema.media_type }) if schema.media_type 35 | end 36 | 37 | def add_security_headers!(h) 38 | return unless schema.secured_by 39 | 40 | scheme = security_schemes.find {|sch| sch.title == schema.secured_by.first } 41 | 42 | h.merge!(scheme.headers) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /features/support/examples/rspec/spec_file_with_request_headers.rb.example: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "e-BookMobile API", type: :request do 4 | 5 | describe "/authors" do 6 | let(:route) { "/authors" } 7 | 8 | describe "POST" do 9 | let(:headers) do 10 | { 11 | "Content-Type" => "application/json" 12 | } 13 | end 14 | 15 | let(:request_body) do 16 | { 17 | "first_name" => "asgaakh", 18 | "last_name" => "sjdhhgu", 19 | "year_of_birth" => 3333 20 | }.to_json 21 | end 22 | 23 | let(:response_body) do 24 | { 25 | "author" => { 26 | "id" => 1, 27 | "first_name" => "asgaakh", 28 | "last_name" => "sjdhhgu", 29 | "year_of_birth" => 3333 30 | } 31 | }.to_json 32 | end 33 | 34 | let(:output_file) do 35 | "spec/contract/output/authors_get_response.json" 36 | end 37 | 38 | it "retrieve a list of authors" do 39 | get route 40 | 41 | File.open(output_file, "w+") {|file| file.puts JSON.pretty_generate(JSON.parse(response.body)) } 42 | 43 | expect(response.body).to eql response_body 44 | end 45 | 46 | it "returns status 200" do 47 | get route 48 | expect(response.status).to eql 200 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/lib/rambo/raml_models/headers_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RamlModels::Headers do 2 | let(:headers) { { "Content-Type" => "application/json" } } 3 | 4 | subject { described_class.new(headers) } 5 | 6 | describe "#pretty" do 7 | let(:pretty) do 8 | %Q[{\n "Content-Type" => "application/json"\n}] 9 | end 10 | 11 | it "makes it pretty" do 12 | expect(subject.pretty).to eql pretty 13 | end 14 | 15 | context "multiple headers" do 16 | let(:headers) { { "Content-Type" => "application/json", "Accept" => "application/json" } } 17 | 18 | let(:pretty) do 19 | %Q[{\n "Content-Type" => "application/json",\n "Accept" => "application/json"\n}] 20 | end 21 | 22 | it "formats the header hash" do 23 | expect(subject.pretty).to eql pretty 24 | end 25 | end 26 | end 27 | 28 | describe "#add" do 29 | it "adds an additional header" do 30 | subject.add("Accept" => "application/json") 31 | expect(subject.headers).to eql({ "Content-Type" => "application/json", "Accept" => "application/json" }) 32 | end 33 | 34 | it "returns self" do 35 | expect(subject.add("Accept" => "application/json")).to be subject 36 | end 37 | end 38 | 39 | describe "#merge!" do 40 | it "combines two sets of headers" do 41 | expect(subject.merge!(described_class.new({"Accept" => "application/json"}))).to be_a(described_class) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/support/foo.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | post: 9 | description: Retrieve a list of authors 10 | body: 11 | application/json: 12 | schema: | 13 | { 14 | "$schema": "http://json-schema.org/draft-04/schema#", 15 | "description": "schema to create an author", 16 | "type": "object", 17 | "required": [ "first_name", "last_name" ], 18 | "properties": { 19 | "first_name": { "type": "string" }, 20 | "last_name": { "type": "string" }, 21 | "year_of_birth": { "type": "integer" } 22 | } 23 | } 24 | responses: 25 | 200: 26 | body: 27 | application/json: 28 | schema: | 29 | { 30 | "$schema": "http://json-schema.org/draft-04/schema#", 31 | "description": "schema for a list of authors", 32 | "type": "object", 33 | "properties": { 34 | "author": { 35 | "type": "object", 36 | "properties": { 37 | "id": { "type": "integer" }, 38 | "first_name": { "type": "string" }, 39 | "last_name": { "type": "string" } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/rambo.rb: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | require "active_support/core_ext/hash" 3 | 4 | Dir["#{File.dirname(__FILE__)}/rambo/**/*.rb"].each {|file| require file } 5 | 6 | module Rambo 7 | class << self 8 | attr_reader :options, :file 9 | 10 | def generate_contract_tests!(file: nil, options: {}) 11 | @options = yaml_options.merge(options) 12 | @options[:framework] = :rails if @options.fetch(:framework, nil).nil? 13 | @file = file || @options.delete(:raml) || raml_file 14 | 15 | DocumentGenerator.generate!(@file, @options) 16 | end 17 | 18 | private 19 | 20 | def yaml_options 21 | opts = YAML.load(File.read(File.join(FileUtils.pwd, ".rambo.yml"))).symbolize_keys 22 | 23 | opts[:framework] = opts[:framework].to_sym if opts[:framework] 24 | 25 | if opts && opts.fetch(:raml, nil) 26 | opts[:raml] = File.join(FileUtils.pwd, opts.fetch(:raml)) 27 | end 28 | 29 | opts || {} 30 | rescue 31 | { framework: :rails } 32 | end 33 | 34 | # TODO: Permit use of multiple RAML files, since right now this only takes 35 | # the first one it finds in the "doc" directory. 36 | 37 | def raml_file 38 | return options.fetch(:raml) if options && options.fetch(:raml, nil) 39 | 40 | raml_path = File.join(FileUtils.pwd, "doc", "raml") 41 | Dir.foreach(raml_path) {|file| return File.join(raml_path, file) if file.match(/\.raml$/) } 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/support/basic_raml_with_post_route.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | post: 9 | description: Retrieve a list of authors 10 | body: 11 | application/json: 12 | schema: | 13 | { 14 | "$schema": "http://json-schema.org/draft-04/schema#", 15 | "description": "schema to create an author", 16 | "type": "object", 17 | "required": [ "first_name", "last_name" ], 18 | "properties": { 19 | "first_name": { "type": "string" }, 20 | "last_name": { "type": "string" }, 21 | "year_of_birth": { "type": "integer" } 22 | } 23 | } 24 | responses: 25 | 200: 26 | body: 27 | application/json: 28 | schema: | 29 | { 30 | "$schema": "http://json-schema.org/draft-04/schema#", 31 | "description": "schema for a list of authors", 32 | "type": "object", 33 | "properties": { 34 | "author": { 35 | "type": "object", 36 | "properties": { 37 | "id": { "type": "integer" }, 38 | "first_name": { "type": "string" }, 39 | "last_name": { "type": "string" } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /features/support/examples/raml/basic_raml_with_post_route.raml: -------------------------------------------------------------------------------- 1 | #%RAML 0.8 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | post: 9 | description: Retrieve a list of authors 10 | body: 11 | application/json: 12 | schema: | 13 | { 14 | "$schema": "http://json-schema.org/draft-04/schema#", 15 | "description": "schema to create an author", 16 | "type": "object", 17 | "required": [ "first_name", "last_name" ], 18 | "properties": { 19 | "first_name": { "type": "string" }, 20 | "last_name": { "type": "string" }, 21 | "year_of_birth": { "type": "integer" } 22 | } 23 | } 24 | responses: 25 | 200: 26 | body: 27 | application/json: 28 | schema: | 29 | { 30 | "$schema": "http://json-schema.org/draft-04/schema#", 31 | "description": "schema for a list of authors", 32 | "type": "object", 33 | "properties": { 34 | "author": { 35 | "type": "object", 36 | "properties": { 37 | "id": { "type": "integer" }, 38 | "first_name": { "type": "string" }, 39 | "last_name": { "type": "string" } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/cli.rb: -------------------------------------------------------------------------------- 1 | require "colorize" 2 | require "rambo" 3 | 4 | module Rambo 5 | class CLI 6 | def initialize(raml_file=nil, opts={}, stdout=STDOUT, stderr=STDERR) 7 | @stdout = stdout 8 | @stderr = stderr 9 | @file = raml_file 10 | @options = opts 11 | 12 | validate! 13 | end 14 | 15 | def run! 16 | print_logo 17 | 18 | begin 19 | Rambo.generate_contract_tests!(file: file, options: options) 20 | 21 | stdout.puts("Generating contract tests...") 22 | sleep 0.4 23 | 24 | stdout.puts("Done!".green) 25 | rescue NoMethodError => e 26 | stderr.puts("Error: #{e.message}".red) 27 | stderr.puts "\t#{e.backtrace.join("\n\t")}" 28 | end 29 | end 30 | 31 | def validate! 32 | exit_for_missing_file unless file 33 | exit_for_invalid_file_format unless file.match(/\.raml$/) 34 | end 35 | 36 | private 37 | 38 | attr_accessor :file, :stdout, :stderr, :generator, :options 39 | 40 | def print_logo 41 | stdout.puts logo.colorize(color: String.colors.sample) 42 | sleep 0.4 43 | end 44 | 45 | def exit_for_missing_file 46 | stdout.puts "USAGE: rambo [FILE]" 47 | exit 1 48 | end 49 | 50 | def exit_for_invalid_file_format 51 | stdout.puts "Unsupported file format. Please choose a RAML file." 52 | exit 1 53 | end 54 | 55 | def logo 56 | File.read(File.expand_path("../../assets/logo.txt", __FILE__)) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /rambo_ruby.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | require File.expand_path "../lib/version.rb", __FILE__ 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "rambo_ruby" 6 | s.version = Rambo.version 7 | s.authors = ["Dana Scheider"] 8 | s.description = "RAML in, RSpec out" 9 | s.summary = "rambo_ruby-#{s.version}" 10 | s.email = "dana.scheider@gmail.com" 11 | s.license = "MIT" 12 | s.platform = Gem::Platform::RUBY 13 | s.required_ruby_version = ">= 2.1.0" 14 | 15 | s.add_dependency "rspec", "~> 3.4" 16 | s.add_dependency "raml-rb", "~> 1.0" 17 | s.add_dependency "colorize", "~> 0.7" 18 | s.add_dependency "json_test_data", "~> 1.1", ">= 1.1.3" 19 | s.add_dependency "json-schema", "~> 2.6" 20 | s.add_dependency "rake", ">= 11.0" 21 | 22 | if RUBY_VERSION < "2.2.2" 23 | s.add_dependency "activesupport", "~> 4.0" 24 | else 25 | s.add_dependency "activesupport", ">= 4.0" 26 | end 27 | 28 | s.add_development_dependency "cucumber", "~> 2.1" 29 | s.add_development_dependency "json", ">= 1.7" 30 | s.add_development_dependency "coveralls", "~> 0.7" 31 | s.add_development_dependency "aruba", "~> 0.13" 32 | 33 | s.executables = "rambo" 34 | s.default_executable = "rambo" 35 | 36 | s.rubygems_version = ">= 1.6.1" 37 | s.files = `git ls-files`.split("\n").reject {|path| path =~ /\.gitignore$/ } 38 | s.test_files = `git ls-files -- {spec,features}/*`.split("\n") 39 | s.rdoc_options = ["--charset=UTF-8"] 40 | s.require_path = "lib" 41 | end 42 | -------------------------------------------------------------------------------- /spec/support/post_with_request_headers.raml: -------------------------------------------------------------------------------- 1 | #%RAML 1.0 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | post: 9 | description: Retrieve a list of authors 10 | headers: 11 | Content-Type: application/json 12 | body: 13 | application/json: 14 | schema: | 15 | { 16 | "$schema": "http://json-schema.org/draft-04/schema#", 17 | "description": "schema to create an author", 18 | "type": "object", 19 | "required": [ "first_name", "last_name" ], 20 | "properties": { 21 | "first_name": { "type": "string" }, 22 | "last_name": { "type": "string" }, 23 | "year_of_birth": { "type": "integer" } 24 | } 25 | } 26 | responses: 27 | 200: 28 | body: 29 | application/json: 30 | schema: | 31 | { 32 | "$schema": "http://json-schema.org/draft-04/schema#", 33 | "description": "schema for a list of authors", 34 | "type": "object", 35 | "properties": { 36 | "author": { 37 | "type": "object", 38 | "properties": { 39 | "id": { "type": "integer" }, 40 | "first_name": { "type": "string" }, 41 | "last_name": { "type": "string" } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /features/support/examples/raml/post_with_request_headers.raml: -------------------------------------------------------------------------------- 1 | #%RAML 1.0 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | post: 9 | description: Retrieve a list of authors 10 | headers: 11 | Content-Type: application/json 12 | body: 13 | application/json: 14 | schema: | 15 | { 16 | "$schema": "http://json-schema.org/draft-04/schema#", 17 | "description": "schema to create an author", 18 | "type": "object", 19 | "required": [ "first_name", "last_name" ], 20 | "properties": { 21 | "first_name": { "type": "string" }, 22 | "last_name": { "type": "string" }, 23 | "year_of_birth": { "type": "integer" } 24 | } 25 | } 26 | responses: 27 | 200: 28 | body: 29 | application/json: 30 | schema: | 31 | { 32 | "$schema": "http://json-schema.org/draft-04/schema#", 33 | "description": "schema for a list of authors", 34 | "type": "object", 35 | "properties": { 36 | "author": { 37 | "type": "object", 38 | "properties": { 39 | "id": { "type": "integer" }, 40 | "first_name": { "type": "string" }, 41 | "last_name": { "type": "string" } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spec/support/multiple_headers.raml: -------------------------------------------------------------------------------- 1 | #%RAML 1.0 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | post: 9 | description: Retrieve a list of authors 10 | headers: 11 | Content-Type: application/json 12 | Accept: application/json 13 | body: 14 | application/json: 15 | schema: | 16 | { 17 | "$schema": "http://json-schema.org/draft-04/schema#", 18 | "description": "schema to create an author", 19 | "type": "object", 20 | "required": [ "first_name", "last_name" ], 21 | "properties": { 22 | "first_name": { "type": "string" }, 23 | "last_name": { "type": "string" }, 24 | "year_of_birth": { "type": "integer" } 25 | } 26 | } 27 | responses: 28 | 200: 29 | body: 30 | application/json: 31 | schema: | 32 | { 33 | "$schema": "http://json-schema.org/draft-04/schema#", 34 | "description": "schema for a list of authors", 35 | "type": "object", 36 | "properties": { 37 | "author": { 38 | "type": "object", 39 | "properties": { 40 | "id": { "type": "integer" }, 41 | "first_name": { "type": "string" }, 42 | "last_name": { "type": "string" } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /spec/support/multiple_resources.raml: -------------------------------------------------------------------------------- 1 | #%RAML 1.0 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | 7 | /authors: 8 | get: 9 | description: Retrieve a list of authors 10 | responses: 11 | 200: 12 | body: 13 | application/json: 14 | example: | 15 | { 16 | "data": [ 17 | { 18 | "id": 1, 19 | "first_name": "Hermann", 20 | "last_name": "Hesse" 21 | }, 22 | { 23 | "id":2, 24 | "first_name": "Charles", 25 | "last_name": "Dickens" 26 | } 27 | ], 28 | "success": true, 29 | "status": 200 30 | } 31 | /genres: 32 | get: 33 | description: Retrieve a list of available genres 34 | responses: 35 | 200: 36 | body: 37 | application/json: 38 | example: | 39 | { 40 | "genres": [ 41 | { 42 | "id": 1, 43 | "description": "Science fiction" 44 | }, 45 | { 46 | "id": 2, 47 | "description": "Young adult" 48 | }, 49 | { 50 | "id": 3, 51 | "description": "Romance" 52 | } 53 | ], 54 | "success": true, 55 | "status": 200 56 | } 57 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 4 | 5 | 6 | 7 | ## Expected Behavior 8 | 9 | 10 | 11 | 12 | ## Current Behavior 13 | 14 | 15 | 16 | 17 | 18 | ~~~ 19 | ~~~ 20 | 21 | ## Possible Solution 22 | 23 | 24 | 25 | 26 | ## Steps to Reproduce (for bugs) 27 | 28 | 29 | 30 | 1. 31 | 2. 32 | 3. 33 | 4. 34 | 35 | ## Context 36 | 37 | 38 | 39 | 40 | ## Your Environment 41 | 42 | 43 | * Version used: 44 | * Operating System and version: 45 | * Link to your project: 46 | -------------------------------------------------------------------------------- /lib/rambo/rspec/helper_file.rb: -------------------------------------------------------------------------------- 1 | require "erb" 2 | require "raml" 3 | require "rambo/raml_models" 4 | require "rambo/rspec/examples" 5 | 6 | module Rambo 7 | module RSpec 8 | class HelperFile 9 | attr_reader :options 10 | 11 | def initialize(template_path:, file_path:, raml: nil, options: nil) 12 | @template_path = template_path 13 | @file_path = file_path 14 | @options = options || { framework: :rails } 15 | @raml = raml ? Rambo::RamlModels::Api.new(raml) : nil 16 | end 17 | 18 | def app_classes 19 | { 20 | :rails => "Rails.application", 21 | :"sinatra:classic" => "Sinatra::Application", 22 | :"sinatra:modular" => "Sinatra::Base.descendants.find {|klass| klass != Sinatra::Application } || Sinatra::Application", 23 | :grape => "Grape::API.descendants.first", 24 | :rory => "Rory.application" 25 | } 26 | end 27 | 28 | def generate 29 | write_to_file(render) unless file_already_exists? 30 | end 31 | 32 | def render 33 | b = binding 34 | ERB.new(template, 0, "-", "@result").result(raml.instance_eval { b }) 35 | @result 36 | end 37 | 38 | private 39 | 40 | attr_reader :template_path, :file_path, :raml 41 | 42 | def file_already_exists? 43 | File.exist?(file_path) 44 | end 45 | 46 | def write_to_file(template) 47 | File.write(file_path, template) 48 | end 49 | 50 | def template 51 | @template ||= File.read(template_path) 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/lib/rambo/rspec/spec_file_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RSpec::SpecFile do 2 | let(:raw_raml) { Raml::Parser.parse_file(raml_file) } 3 | let(:options) { { rails: true } } 4 | let(:raml) { Rambo::RamlModels::Api.new(raw_raml) } 5 | let(:spec_file) { Rambo::RSpec::SpecFile.new(raw_raml, options) } 6 | 7 | before(:each) do 8 | FileUtils.mkdir_p(File.expand_path("spec/support/examples")) 9 | end 10 | 11 | after(:each) do 12 | FileUtils.rm_rf(File.expand_path("spec/support/examples")) 13 | end 14 | 15 | context "file with examples" do 16 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/foobar.raml") } 17 | 18 | describe "#initialize" do 19 | it "assigns @raml" do 20 | expect(spec_file.raml).not_to be_nil 21 | end 22 | 23 | it "uses the correct schema" do 24 | expect(spec_file.raml.schema).to eq raw_raml 25 | end 26 | end 27 | 28 | describe "#template" do 29 | it "is a string" do 30 | expect(spec_file.template.is_a?(String)).to be true 31 | end 32 | end 33 | 34 | describe "#render" do 35 | it "interpolates the correct values" do 36 | expect(spec_file.render).to include("e-BookMobile API") 37 | end 38 | end 39 | end 40 | 41 | context "file with schema" do 42 | let(:raml_file) do 43 | File.join(RAMBO_ROOT, "../features/support/examples/raml/basic_raml_with_schema.raml") 44 | end 45 | 46 | describe "#initialize" do 47 | it "assigns @raml" do 48 | expect(spec_file.raml).not_to be_nil 49 | end 50 | end 51 | 52 | describe "#template" do 53 | it "is a string" do 54 | expect(spec_file.template.is_a?(String)).to be true 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/support/secured_api.raml: -------------------------------------------------------------------------------- 1 | #%RAML 1.0 2 | --- 3 | title: e-BookMobile API 4 | baseUri: http://api.e-bookmobile.com/{version} 5 | version: v1 6 | securitySchemes: 7 | auth_header: 8 | describedBy: 9 | headers: 10 | Api-Token: 11 | type: string 12 | securedBy: [auth_header] 13 | 14 | /authors: 15 | get: 16 | description: Retrieve a list of authors 17 | responses: 18 | 200: 19 | body: 20 | application/json: 21 | example: | 22 | { 23 | "data": [ 24 | { 25 | "id": 1, 26 | "first_name": "Hermann", 27 | "last_name": "Hesse" 28 | }, 29 | { 30 | "id":2, 31 | "first_name": "Charles", 32 | "last_name": "Dickens" 33 | } 34 | ], 35 | "success": true, 36 | "status": 200 37 | } 38 | /genres: 39 | get: 40 | description: Retrieve a list of available genres 41 | responses: 42 | 200: 43 | body: 44 | application/json: 45 | example: | 46 | { 47 | "genres": [ 48 | { 49 | "id": 1, 50 | "description": "Science fiction" 51 | }, 52 | { 53 | "id": 2, 54 | "description": "Young adult" 55 | }, 56 | { 57 | "id": 3, 58 | "description": "Romance" 59 | } 60 | ], 61 | "success": true, 62 | "status": 200 63 | } 64 | -------------------------------------------------------------------------------- /lib/rambo/rspec/example_group.rb: -------------------------------------------------------------------------------- 1 | module Rambo 2 | module RSpec 3 | class ExampleGroup 4 | 5 | TEMPLATE_PATH = File.expand_path("../templates/example_group_template.erb", __FILE__) 6 | 7 | attr_reader :resource 8 | 9 | def initialize(resource, options={}) 10 | @resource = resource 11 | @options = options || { framework: :rails } 12 | end 13 | 14 | def template 15 | @template ||= File.read(TEMPLATE_PATH) 16 | end 17 | 18 | def create_fixture_files 19 | resource.http_methods.each do |method| 20 | if method.request_body 21 | path = File.expand_path("spec/support/examples/#{@resource.to_s.gsub(/\//, "")}_#{method.method}_request_body.json") 22 | File.write(path, method.request_body.example) 23 | end 24 | 25 | method.responses.each do |resp| 26 | resp.bodies.each do |body| 27 | path = body.schema ? response_schema_fixture_path(method) : response_body_fixture_path(method) 28 | contents = body.schema ? body.schema : body.example 29 | File.write(path, contents) 30 | end 31 | end 32 | end 33 | end 34 | 35 | def render 36 | create_fixture_files 37 | b = binding 38 | ERB.new(template, 0, "-", "@result").result(resource.instance_eval { b }) 39 | @result 40 | end 41 | 42 | private 43 | 44 | def response_schema_fixture_path(method) 45 | File.expand_path("spec/support/examples/#{@resource.to_s.gsub(/\//, "")}_#{method.method}_response_schema.json") 46 | end 47 | 48 | def response_body_fixture_path(method) 49 | File.expand_path("spec/support/examples/#{@resource.to_s.gsub(/\//, "")}_#{method.method}_response_body.json") 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/rambo/document_generator.rb: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | require "raml-rb" 3 | 4 | require "rambo/rspec/spec_file" 5 | require "rambo/rspec/helper_file" 6 | 7 | module Rambo 8 | class DocumentGenerator 9 | attr_accessor :file, :raml, :options 10 | 11 | class << self 12 | def generate!(file, options={}) 13 | generator = new(file, options) 14 | generator.generate_spec_dir! 15 | generator.generate_matcher_dir! 16 | generator.generate_examples! 17 | generator.generate_spec_file! 18 | generator.generate_rambo_helper! 19 | generator.generate_matchers! 20 | end 21 | end 22 | 23 | def initialize(file, options={}) 24 | @file = file 25 | @raml = Raml::Parser.parse_file(file) 26 | @options = options 27 | end 28 | 29 | def generate_spec_dir! 30 | FileUtils.mkdir_p("spec/contract/output") 31 | end 32 | 33 | def generate_examples! 34 | FileUtils.mkdir_p("spec/support/examples") 35 | end 36 | 37 | def generate_spec_file! 38 | spec_file_name = file.match(/[^\/]*\.raml$/).to_s.gsub(/\.raml$/, "_spec.rb") 39 | contents = Rambo::RSpec::SpecFile.new(raml, options).render 40 | File.write("spec/contract/#{spec_file_name}", contents) 41 | end 42 | 43 | def generate_rambo_helper! 44 | Rambo::RSpec::HelperFile.new( 45 | template_path: File.expand_path("../rspec/templates/rambo_helper_file_template.erb", __FILE__), 46 | file_path: "spec/rambo_helper.rb", 47 | raml: raml, 48 | options: options 49 | ).generate 50 | end 51 | 52 | def generate_matcher_dir! 53 | FileUtils.mkdir_p("spec/support/matchers") 54 | end 55 | 56 | def generate_matchers! 57 | Rambo::RSpec::HelperFile.new( 58 | template_path: File.expand_path("../rspec/templates/matcher_file_template.erb", __FILE__), 59 | file_path: "spec/support/matchers/rambo_matchers.rb", raml: nil 60 | ).generate 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/rambo/rspec/templates/example_group_template.erb: -------------------------------------------------------------------------------- 1 | <% rails, rory = @options.fetch(:framework, :rails) == :rails, @options.fetch(:framework, :rails) == :rory %> describe "<%= @resource.to_s %>" do 2 | let(:route) { "<%= @resource.to_s %>" } 3 | <%- @resource.http_methods.each do |method| %> 4 | describe "<%= method.method.upcase %>" do 5 | <% if method.headers %>let(:headers) do <% headers = method.headers.pretty.split("\n")[1..-2] %> 6 | { 7 | <%= headers.join(",\n ") %> 8 | } 9 | end<% end %> 10 | <% if method.request_body %> 11 | let(:request_body) do 12 | <% if rails || rory %>JSON.parse( 13 | File.read("<%= "spec/support/examples/#{@resource.to_s.gsub("/", "")}_#{method.method}_request_body.json" %>"), 14 | symbolize_names: true 15 | )<% else %>File.read("<%= "spec/support/examples/#{@resource.to_s.gsub("/", "")}_#{method.method}_request_body.json" %>")<% end %> 16 | end<% end %><% if has_schema = method.responses.first.bodies.first.schema %> 17 | 18 | let(:response_schema) do 19 | File.read("<%= "spec/support/examples/#{@resource.to_s.gsub("/", "")}_#{method.method}_response_schema.json" %>") 20 | end<% else %> 21 | let(:response_body) do<% body = method.responses.first.bodies.first.example.split("\n") %> 22 | File.read("<%= "spec/support/examples/#{@resource.to_s.gsub("/", "")}_#{method.method}_response_body.json" %>") 23 | end<% end %> 24 | 25 | let(:output_file) do 26 | "<%= "spec/contract/output/#{@resource.to_s.gsub("/", "")}_#{method.method}_response.json" %>" 27 | end 28 | 29 | it "<%= method.description && method.description.downcase || "#{method.method}s the resource" %>" do 30 | <%= method.method %> route<% if method.request_body %>, request_body<% end %><% if method.headers %>, headers<% end %> 31 | 32 | File.open(output_file, "w+") {|file| file.puts JSON.pretty_generate(JSON.parse(last_response.body)) } 33 | 34 | expect(last_response.body).to <%= has_schema ? "match_schema response_schema" : "eql response_body" %> 35 | end 36 | 37 | it "returns status <%= method.responses.first.status_code %>" do 38 | <%= method.method %> route<% if method.request_body %>, request_body<% end %><% if method.headers %>, headers<% end %> 39 | expect(last_response.status).to eql <%= method.responses.first.status_code %> 40 | end 41 | end<%- end %> 42 | end 43 | -------------------------------------------------------------------------------- /spec/lib/rambo/cli_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::CLI do 2 | let(:io) { StringIO.new } 3 | let(:stderr) { StringIO.new } 4 | let(:opts) { { framework: :rails } } 5 | let(:valid_file) { File.join(SPEC_DIR_ROOT, 'support/foobar.raml',) } 6 | 7 | describe "run!" do 8 | let(:cli) { Rambo::CLI.new(valid_file, opts, io, stderr) } 9 | 10 | before(:each) do 11 | allow_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_rambo_helper!) 12 | allow_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_spec_file!) 13 | allow_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_matchers!) 14 | allow_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_matcher_dir!) 15 | allow_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_examples!) 16 | allow_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_spec_dir!) 17 | end 18 | 19 | it "creates a spec/contract directory" do 20 | expect_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_spec_dir!) 21 | cli.run! 22 | end 23 | 24 | it "creates a spec/support/matchers directory" do 25 | expect_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_matchers!) 26 | cli.run! 27 | end 28 | 29 | it "creates a rambo_helper file" do 30 | allow(File).to receive(:exist?).with('spec/rambo_helper.rb').and_return(true) 31 | expect_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_rambo_helper!) 32 | cli.run! 33 | end 34 | 35 | it "creates foobar_spec.rb" do 36 | expect_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_spec_file!) 37 | cli.run! 38 | end 39 | 40 | it "creates the spec/support/examples directory" do 41 | expect_any_instance_of(Rambo::DocumentGenerator).to receive(:generate_examples!) 42 | cli.run! 43 | end 44 | 45 | context "when there is an error" do 46 | it "prints the error messaage" do 47 | allow_any_instance_of(Rambo::DocumentGenerator) 48 | .to receive(:generate_spec_file!) 49 | .and_raise NoMethodError, "Undefined method generate_spec_file!" 50 | 51 | expect{ cli.run! }.to rescue(NoMethodError) 52 | end 53 | end 54 | end 55 | 56 | describe "validate!" do 57 | context "with no file given" do 58 | it "exits" do 59 | expect{ Rambo::CLI.new(nil, io) }.to raise_error(SystemExit) 60 | end 61 | end 62 | 63 | context "with the wrong file format" do 64 | let(:invalid_file) { File.expand_path('../support/foobar.yml', __FILE__) } 65 | 66 | it "exits" do 67 | expect{ Rambo::CLI.new(invalid_file, io) }.to raise_error(SystemExit) 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/lib/rambo/rspec/example_group_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::RSpec::ExampleGroup do 2 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "/support/foobar.raml") } 3 | let(:raml) { Raml::Parser.parse_file(raml_file) } 4 | let(:resource) { Rambo::RamlModels::Resource.new(raml.resources.first) } 5 | let(:options) { { rails: true } } 6 | subject { Rambo::RSpec::ExampleGroup.new(resource, options) } 7 | 8 | before(:each) do 9 | FileUtils.mkdir_p(File.expand_path("spec/support/examples")) 10 | end 11 | 12 | after(:each) do 13 | FileUtils.rm_rf(File.expand_path("spec/support/examples")) 14 | end 15 | 16 | describe "#render" do 17 | it "interpolates the correct values" do 18 | aggregate_failures do 19 | expect(subject.render).to include("let(:output_file) do") 20 | expect(subject.render).to include("describe \"#{raml.resources.first.http_methods.first.method.upcase}\" do") 21 | expect(subject.render).to include('describe "GET" do') 22 | end 23 | end 24 | 25 | it "does not include a request body" do 26 | expect(subject.render).not_to include("let(:request_body) do") 27 | end 28 | 29 | it "does not include headers" do 30 | expect(subject.render).not_to include("let(:headers) do") 31 | end 32 | 33 | context "when the route has a response schema" do 34 | let(:raml_file) {File.join(SPEC_DIR_ROOT, "support/basic_raml_with_post_route.raml") } 35 | 36 | it "creates a response schema object" do 37 | aggregate_failures do 38 | expect(subject.render).to include("let(:response_schema) do") 39 | expect(subject.render).to include("expect(last_response.body).to match_schema response_schema") 40 | end 41 | end 42 | end 43 | 44 | context "when there is a response example but not a response schema" do 45 | it "creates a response_body object" do 46 | aggregate_failures do 47 | expect(subject.render).to include("let(:response_body) do") 48 | expect(subject.render).to include("expect(last_response.body).to eql response_body") 49 | end 50 | end 51 | end 52 | 53 | context "when the route has a request body" do 54 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/basic_raml_with_post_route.raml") } 55 | 56 | it "adds a request body" do 57 | expect(subject.render).to include("let(:request_body) do") 58 | end 59 | 60 | it "does not include headers" do 61 | expect(subject.render).not_to include("let(:headers) do") 62 | end 63 | end 64 | 65 | context "when the route has request headers" do 66 | let(:raml_file) { File.join(SPEC_DIR_ROOT, "support/post_with_request_headers.raml") } 67 | 68 | it "adds headers" do 69 | expect(subject.render).to include("let(:headers) do") 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /spec/lib/rambo/document_generator_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo::DocumentGenerator do 2 | let(:valid_file) { File.join(SPEC_DIR_ROOT, "support/foobar.raml") } 3 | let(:options) { { framework: :rails } } 4 | 5 | subject { described_class.new(valid_file, options) } 6 | 7 | before(:each) do 8 | allow_any_instance_of(Rambo::DocumentGenerator).to receive(:extract_raml) 9 | end 10 | 11 | describe ".generate!" do 12 | before(:each) do 13 | allow_any_instance_of(described_class).to receive(:generate_matchers!) 14 | allow_any_instance_of(described_class).to receive(:generate_spec_dir!) 15 | allow_any_instance_of(described_class).to receive(:generate_spec_file!) 16 | allow_any_instance_of(described_class).to receive(:generate_matcher_dir!) 17 | allow_any_instance_of(described_class).to receive(:generate_rambo_helper!) 18 | allow_any_instance_of(described_class).to receive(:generate_examples!) 19 | end 20 | 21 | it "generates a spec directory" do 22 | expect_any_instance_of(described_class).to receive(:generate_spec_dir!) 23 | described_class.generate!(valid_file, options) 24 | end 25 | 26 | it "generates the Rambo helper" do 27 | expect_any_instance_of(described_class).to receive(:generate_rambo_helper!) 28 | described_class.generate!(valid_file, options) 29 | end 30 | 31 | it "generates the matcher directory" do 32 | expect_any_instance_of(described_class).to receive(:generate_matcher_dir!) 33 | described_class.generate!(valid_file, options) 34 | end 35 | 36 | it "generates the matchers" do 37 | expect_any_instance_of(described_class).to receive(:generate_matchers!) 38 | described_class.generate!(valid_file, options) 39 | end 40 | 41 | it "generates the examples" do 42 | expect_any_instance_of(described_class).to receive(:generate_examples!) 43 | described_class.generate!(valid_file, options) 44 | end 45 | 46 | it "generates a spec file" do 47 | expect_any_instance_of(described_class).to receive(:generate_spec_file!) 48 | described_class.generate!(valid_file, options) 49 | end 50 | end 51 | 52 | describe "#generate_spec_dir!" do 53 | it "generates the spec/contract directory" do 54 | expect(FileUtils).to receive(:mkdir_p).with("spec/contract/output") 55 | subject.generate_spec_dir! 56 | end 57 | end 58 | 59 | describe "#generate_rambo_helper!" do 60 | 61 | it "generates the rambo_helper file" do 62 | aggregate_failures do 63 | expect_any_instance_of(Rambo::RSpec::HelperFile) 64 | .to receive(:initialize) 65 | .with(hash_including( 66 | :template_path => File.join(RAMBO_ROOT, "rambo/rspec/templates/rambo_helper_file_template.erb"), 67 | :file_path => "spec/rambo_helper.rb", 68 | :options => { framework: :rails } 69 | )) 70 | expect_any_instance_of(Rambo::RSpec::HelperFile).to receive(:generate) 71 | end 72 | 73 | subject.generate_rambo_helper! 74 | end 75 | end 76 | 77 | describe "#generate_spec_file!" do 78 | before(:each) do 79 | allow(File).to receive(:open) 80 | end 81 | 82 | it "generates foobar_spec.rb" do 83 | allow_any_instance_of(Rambo::RSpec::SpecFile).to receive(:render).and_return("foo") 84 | expect(File).to receive(:write).with("spec/contract/foobar_spec.rb", "foo") 85 | subject.generate_spec_file! 86 | end 87 | end 88 | 89 | describe "#generate_matcher_dir!" do 90 | it "creates a spec/support/matchers directory" do 91 | expect(FileUtils).to receive(:mkdir_p).with("spec/support/matchers") 92 | subject.generate_matcher_dir! 93 | end 94 | end 95 | 96 | describe "#generate_matchers!" do 97 | it "adds a matcher file" do 98 | aggregate_failures do 99 | expect_any_instance_of(Rambo::RSpec::HelperFile) 100 | .to receive(:initialize) 101 | .with(hash_including( 102 | :template_path => File.join(RAMBO_ROOT, "rambo/rspec/templates/matcher_file_template.erb"), 103 | :file_path => "spec/support/matchers/rambo_matchers.rb" 104 | )) 105 | expect_any_instance_of(Rambo::RSpec::HelperFile).to receive(:generate) 106 | end 107 | 108 | subject.generate_matchers! 109 | end 110 | end 111 | 112 | describe "#generate_examples!" do 113 | it "creates the directory" do 114 | expect(FileUtils).to receive(:mkdir_p).with("spec/support/examples") 115 | subject.generate_examples! 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /spec/lib/rambo_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Rambo do 2 | let(:file_contents) do 3 | <<-EOF 4 | raml: doc/raml/foobar.raml 5 | framework: rails 6 | EOF 7 | end 8 | 9 | describe ".generate_contract_tests!" do 10 | let(:valid_file) { "foobar.raml" } 11 | let(:default_options) { { framework: :rails } } 12 | 13 | before(:each) do 14 | allow(Dir).to receive(:foreach).and_return("/Users/dscheider/rambo/doc/raml/#{valid_file}") 15 | end 16 | 17 | context "in all cases" do 18 | it "generates documents" do 19 | expect(Rambo::DocumentGenerator).to receive(:generate!).with(valid_file, default_options) 20 | Rambo.generate_contract_tests!(file: valid_file, options: default_options) 21 | end 22 | end 23 | 24 | context "when there is a .rambo.yml file" do 25 | before(:each) do 26 | allow(Rambo::DocumentGenerator).to receive(:generate!) 27 | 28 | allow(File) 29 | .to receive(:read) 30 | .with(File.expand_path(".rambo.yml")) 31 | .and_return(file_contents) 32 | end 33 | 34 | it "reads from the file" do 35 | expect(File).to receive(:read).with(File.expand_path(".rambo.yml")) 36 | Rambo.generate_contract_tests! 37 | end 38 | 39 | it "sets the RAML file to the one from the file" do 40 | expect(Rambo::DocumentGenerator) 41 | .to receive(:generate!) 42 | .with(File.expand_path("doc/raml/foobar.raml"), { framework: :rails }) 43 | 44 | Rambo.generate_contract_tests! 45 | end 46 | 47 | context "framework set to sinatra:classic" do 48 | it "sets framework to sinatra:classic" do 49 | allow(Rambo).to receive(:yaml_options).and_return({ framework: :"sinatra:classic" }) 50 | expect(Rambo::DocumentGenerator) 51 | .to receive(:generate!) 52 | .with("/Users/dscheider/rambo/doc/raml/#{valid_file}", { framework: :"sinatra:classic" }) 53 | Rambo.generate_contract_tests! 54 | end 55 | end 56 | 57 | context "framework set to sinatra:modular" do 58 | it "sets framework to sinatra:modular" do 59 | allow(Rambo).to receive(:yaml_options).and_return({ framework: :"sinatra:modular" }) 60 | expect(Rambo::DocumentGenerator) 61 | .to receive(:generate!) 62 | .with("/Users/dscheider/rambo/doc/raml/#{valid_file}", { framework: :"sinatra:modular" }) 63 | Rambo.generate_contract_tests! 64 | end 65 | end 66 | 67 | context "framework set to Rory" do 68 | it "sets the framework to Rory" do 69 | allow(Rambo).to receive(:yaml_options).and_return({ framework: :rory }) 70 | expect(Rambo::DocumentGenerator) 71 | .to receive(:generate!) 72 | .with("/Users/dscheider/rambo/doc/raml/#{valid_file}", { framework: :rory }) 73 | Rambo.generate_contract_tests! 74 | end 75 | end 76 | 77 | context "framework set to Grape" do 78 | it "sets framework to Grape" do 79 | allow(Rambo).to receive(:yaml_options).and_return({ framework: :grape }) 80 | expect(Rambo::DocumentGenerator) 81 | .to receive(:generate!) 82 | .with("/Users/dscheider/rambo/doc/raml/#{valid_file}", { framework: :grape }) 83 | Rambo.generate_contract_tests! 84 | end 85 | end 86 | 87 | context "framework set to Rails" do 88 | it "sets framework to Rails" do 89 | allow(Rambo).to receive(:yaml_options).and_return({ framework: :rails }) 90 | expect(Rambo::DocumentGenerator) 91 | .to receive(:generate!) 92 | .with("/Users/dscheider/rambo/doc/raml/#{valid_file}", { framework: :rails }) 93 | Rambo.generate_contract_tests! 94 | end 95 | end 96 | 97 | context "framework option not set in file" do 98 | it "sets framework to Rails" do 99 | allow(Rambo).to receive(:yaml_options).and_return({}) 100 | expect(Rambo::DocumentGenerator) 101 | .to receive(:generate!) 102 | .with(File.expand_path("/Users/dscheider/rambo/doc/raml/#{valid_file}"), { framework: :rails }) 103 | Rambo.generate_contract_tests! 104 | end 105 | end 106 | end 107 | 108 | context "when there is no .rambo.yml file" do 109 | it "uses default options" do 110 | expect(Rambo::DocumentGenerator) 111 | .to receive(:generate!) 112 | .with(File.expand_path("/Users/dscheider/rambo/doc/raml/#{valid_file}"), default_options) 113 | 114 | Rambo.generate_contract_tests! 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rambo 2 | [![Gem Version](https://badge.fury.io/rb/rambo_ruby.svg)](https://badge.fury.io/rb/rambo_ruby) [![Build Status](https://travis-ci.org/danascheider/rambo.svg?branch=master)](https://travis-ci.org/danascheider/rambo) [![Code Climate](https://codeclimate.com/github/danascheider/rambo/badges/gpa.svg)](https://codeclimate.com/github/danascheider/rambo) [![Dependency Status](https://gemnasium.com/badges/github.com/danascheider/rambo.svg)](https://gemnasium.com/github.com/danascheider/rambo) 3 | [![Coverage Status](https://coveralls.io/repos/github/danascheider/rambo/badge.svg?branch=dev)](https://coveralls.io/github/danascheider/rambo?branch=master) 4 | 5 | Rambo is a gem that generates API contract tests from API docs in [RAML](http://raml.org/). Rambo is being developed to test APIs complying with standard REST practices. Mileage may vary with other architectures, but I'm happy to consider pull requests. 6 | 7 | #### Rambo is still in development. It is unstable and has a limited feature set. Use at your own risk and please file issue reports if they come up! 8 | 9 | ## Usage 10 | You can install Rambo using: 11 | ```ruby 12 | gem install rambo_ruby 13 | ``` 14 | You can also add it to your project's Gemfile: 15 | ```ruby 16 | group :development, :test do 17 | gem 'rambo_ruby', '~> 0.7' 18 | end 19 | ``` 20 | There are three options for generating tests from Rambo: The command line tool, the rake task, and the Ruby API. In all cases, Rambo will look for a `.rambo.yml` file in the root directory of your project for configuration options. Options may also be passed in through the command line as arguments or the Ruby API as a hash. There is currently no option to pass arguments to the Rake task, but Rambo comes pre-loaded with sensible defaults, and the `.rambo.yml` file is always an option. 21 | 22 | Rambo will create `spec/contract` directory and a `spec/rambo_helper.rb` file if they don't exist, and will create a `spec/contract/foobar_spec.rb` file. The latter will overwrite any existing spec file by the same name. This is intentional behavior and will not change in future versions. 23 | 24 | The Rack::Test API uses different syntax for Rails and non-Rails Rack apps. By default, Rambo assumes it is dealing with a Rails app, but this is easily modified by passing options or using a .rambo.yml file. 25 | 26 | To run the RSpec examples Rambo generates, you will need to have `require`s in your `spec_helper.rb` or `rambo_helper.rb` file: 27 | 28 | - `require "rack/test"` 29 | - `require "json"` 30 | - `require "json-schema"` 31 | 32 | ### The Command Line Tool 33 | To use the command line tool, simply `cd` into the root directory of your project and run 34 | ``` 35 | $ rambo foobar.raml 36 | ``` 37 | Replace `foobar.raml` with the path of the actual RAML file from which you want to generate tests. 38 | 39 | #### Options 40 | By default, Rambo assumes you are testing a Rails app and generates tests using syntax that will work for Rails apps. If you are testing a non-Rails app, you can use the `--framework` flag to indicate a `sinatra:classic`, `sinatra:modular`, `grape`, or `rory` app. If you are using a different framework, please open an issue to let us know which, or submit a PR adding support for the framework you are using. 41 | 42 | If your app uses an API token header, you can also pass in the token to be used as an option using the `-T` or `--token` flag: 43 | ``` 44 | $ rambo foobar.raml -T sometoken 45 | ``` 46 | Rambo will automatically use this value for any header whose name matches "token" or "key" (not case-sensitive). 47 | 48 | ### The Rake Task 49 | After adding `rambo_ruby` to your Gemfile or gemspec, you will need to add the following to your Rakefile: 50 | ```ruby 51 | require "rambo" 52 | 53 | Rambo::Rake::Task.new 54 | ``` 55 | This will create a Rake task called `rambo`. Now, you can generate tests by running: 56 | ``` 57 | rake rambo 58 | ``` 59 | 60 | ### The Ruby API 61 | You can generate Rambo tests from a Ruby script using: 62 | ```ruby 63 | require "rambo" 64 | 65 | Rambo.generate_contract_tests!(File.expand_path("doc/foobar.raml"), {}) 66 | ``` 67 | You can pass any options in as a hash. Currently, the available options are `:framework` and `:token`. Valid values for the `:framework` option are `:grape`, `:"sinatra:classic"`, `:"sinatra:modular"`, `:rory`, and `:rails`, with `:rails` being the default. The `:token` option takes an API token as a string. 68 | 69 | ## The .rambo.yml File 70 | By default, Rambo will always check for a `.rambo.yml` file in the root directory of your projects and load options from there. If there is no `.rambo.yml` file, default values will be used (see below). 71 | 72 | A sample `.rambo.yml` file could look like this: 73 | ```yaml 74 | raml: docs/contracts/foobar.raml 75 | framework: sinatra:modular 76 | token: foobarbaz 77 | ``` 78 | The three possible keys are: 79 | - `raml` - specifies the RAML file to use to generate the tests. The default, relative 80 | to the root of your project directory, is `doc/raml/foobar.raml`, where `foobar.raml` is the first RAML file found in the `doc/raml` directory. 81 | - `framework` - specifies the framework you are using. The default value is `rails`; other available 82 | frameworks are `sinatra:classic`, `sinatra:modular`, `grape`, and `rory`. 83 | - `token` - the API key or token to be included in the security headers. This value will be 84 | used for any header whose name matches either "token" or "key" (not case-sensitive). 85 | 86 | If a `.rambo.yml` file is present and additional options are passed in through the command line or Ruby API, the option values that are passed in will override those in the `.rambo.yml` file. 87 | 88 | ## Default Behavior 89 | In order to provide the best user experience to a majority of users, Rambo comes with some sensible defaults that are easily overridden in an optional `.rambo.yml` file, or by using command line flags or a Ruby option hash (see above). 90 | 91 | ### RAML File 92 | In the present version, Rambo only generates tests from a single RAML file. If you're using the command line tool, the name of this file is passed in as an argument. If you're not using the command line tool and don't specify by another means (Ruby hash, `.rambo.yml` file) which RAML file to use, Rambo will look in `your_project/doc/raml` and use the first RAML file it finds. 93 | 94 | As noted above, Rambo currently supports only [Rails](https://github.com/rails/rails), [Sinatra](https://github.com/sinatra/sinatra), [Grape](https://github.com/ruby-grape/grape), and [Rory](https://github.com/screamingmuse/rory) apps. Since Rails is the most popular Ruby framework, it assumes your app is a Rails app unless specified otherwise. Since Rack::Test syntax differs when testing Rails and non-Rails apps, you will need to tell Rambo if your app is not a Rails app using the `--framework` flag on the command line, the `:framework` option for the Ruby API, or specifying `framework: ` in your `.rambo.yml` file. Note that for Sinatra apps, you must choose either `sinatra:classic` or `sinatra:modular`. 95 | 96 | ## Using Rambo with Grape or Sinatra 97 | Rambo is able to generate tests for apps written in Rails, Grape, or Sinatra. However, it has one important limitation when working with non-Rails frameworks. Specifically, Rambo does not support multiple subclasses of `Sinatra::Base`, `Sinatra::Application`, or `Grape::API`. In order to identify the class of your app (which is required to configure Rack::Test), the Rambo-generated test configuration will use the first subclass of one of these classes that it finds. There is currently no option to override this behavior. (If you are building a classic Sinatra app instead of the modular type, `Sinatra::Application` will be used.) 98 | 99 | ## About the Project 100 | I started Rambo in March of 2016 as part of my work at [Renew Financial](http://renewfinancial.com). For this reason, our primary focus has been on adding the features and functionality that are most important for testing RF's back-end services. Since my contract with Renew Financial has ended, I now have more latitude to do with the project what I want, but also less time to do it. 101 | 102 | Rambo, therefore, considers RAML 1.0 and Rails 4 the default, and support for other frameworks and for RAML 0.8 is currently lower priority. We would be delighted to merge pull requests adding such support, as long as they don't adversely affect the features we need most. 103 | 104 | ## Contributing 105 | Rambo is a new project and any contributions are much appreciated. All pull requests should include comprehensive test coverage and, where appropriate, documentation. If you're not sure where to get started, contact me [through Github](https://github.com/danascheider) and I'll be glad to chat. 106 | 107 | Additional information for contributors is available in the wiki. Beginning or first-time contributors are welcome and encouraged! 108 | 109 | ## More Information 110 | * [RAML homepage](https://raml.org) 111 | * [Roy Fielding's dissertation](https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf) describing Representational State Transfer (REST) architecture 112 | * [RESTful Web Services](http://www.crummy.com/writing/RESTful-Web-Services/RESTful_Web_Services.pdf), by Leonard Richardson & Sam Ruby 113 | * [Toby Clemson](http://martinfowler.com/articles/microservice-testing/) on testing microservices 114 | --------------------------------------------------------------------------------