From 730197d07f42d438ffea28c0465b696753a30719 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 25 Feb 2018 13:01:41 -0800 Subject: [PATCH 01/24] Initial commit for Postman writer with no variable support --- .gitignore | 1 + features/postman.feature | 127 ++++++++++++++++++ lib/rspec_api_documentation.rb | 1 + .../writers/postman_writer.rb | 96 +++++++++++++ 4 files changed, 225 insertions(+) create mode 100644 features/postman.feature create mode 100644 lib/rspec_api_documentation/writers/postman_writer.rb diff --git a/.gitignore b/.gitignore index a0190963..06664625 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ example/public/docs *.gem *.swp /html/ +.idea \ No newline at end of file diff --git a/features/postman.feature b/features/postman.feature new file mode 100644 index 00000000..537819d0 --- /dev/null +++ b/features/postman.feature @@ -0,0 +1,127 @@ +Feature: Postman + + Background: + Given a file named "app.rb" with: + """ + class App + def self.call(env) + request = Rack::Request.new(env) + response = Rack::Response.new + response["Content-Type"] = "text/plain" + response.write("Hello, #{request.params["target"]}!") + response.finish + end + end + """ + And a file named "app_spec.rb" with: + """ + require "rspec_api_documentation" + require "rspec_api_documentation/dsl" + + RspecApiDocumentation.configure do |config| + config.app = App + config.api_name = "app" + config.api_explanation = "desc" + config.format = :postman + config.io_docs_protocol = "https" + end + + resource "Greetings" do + explanation "Greetings API methods" + + get "/greetings" do + parameter :target, "The thing you want to greet" + + example "Greeting your favorite gem" do + do_request :target => "rspec_api_documentation" + + expect(response_headers["Content-Type"]).to eq("text/plain") + expect(status).to eq(200) + expect(response_body).to eq('Hello, rspec_api_documentation!') + end + + example "Greeting your favorite developers of your favorite gem" do + do_request :target => "Sam & Eric" + + expect(response_headers["Content-Type"]).to eq("text/plain") + expect(status).to eq(200) + expect(response_body).to eq('Hello, Sam & Eric!') + end + end + end + """ + When I run `rspec app_spec.rb --require ./app.rb --format RspecApiDocumentation::ApiFormatter` + + Scenario: Output helpful progress to the console + Then the output should contain: + """ + Generating API Docs + Greetings + GET /greetings + * Greeting your favorite gem + * Greeting your favorite developers of your favorite gem + """ + And the output should contain "2 examples, 0 failures" + And the exit status should be 0 + + Scenario: File should look like we expect + Then the file "doc/api/app.postman_collection.json" should contain JSON exactly like: + """ + { + "info": { + "name": "app", + "description": "desc", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Greetings", + "description": "Greetings API methods", + "item": [ + { + "name": "Greeting your favorite developers of your favorite gem", + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{application_url}}/greetings?target=Sam+%26+Eric", + "host": [ + "{{application_url}}" + ], + "path": [ + "greetings" + ], + "variable": [] + }, + "description": null + }, + "response": [] + }, + { + "name": "Greeting your favorite gem", + "request": { + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{application_url}}/greetings?target=rspec_api_documentation", + "host": [ + "{{application_url}}" + ], + "path": [ + "greetings" + ], + "variable": [] + }, + "description": null + }, + "response": [] + } + ] + } + ] + } + """ + + diff --git a/lib/rspec_api_documentation.rb b/lib/rspec_api_documentation.rb index 6d6afdc2..7fb56092 100644 --- a/lib/rspec_api_documentation.rb +++ b/lib/rspec_api_documentation.rb @@ -45,6 +45,7 @@ module Writers autoload :CombinedJsonWriter autoload :SlateWriter autoload :ApiBlueprintWriter + autoload :PostmanWriter end module Views diff --git a/lib/rspec_api_documentation/writers/postman_writer.rb b/lib/rspec_api_documentation/writers/postman_writer.rb new file mode 100644 index 00000000..1c805d33 --- /dev/null +++ b/lib/rspec_api_documentation/writers/postman_writer.rb @@ -0,0 +1,96 @@ +require 'rspec_api_documentation/writers/formatter' + +module RspecApiDocumentation + module Writers + class PostmanWriter < Writer + attr_accessor :api_name + delegate :docs_dir, :to => :configuration + + def initialize(index, configuration) + super + self.api_name = configuration.api_name.parameterize + end + + def write + File.open(docs_dir.join("#{api_name}.postman_collection.json"), "w+") do |file| + file.write Formatter.to_json(PostmanIndex.new(index, configuration)) + end + end + end + + class PostmanIndex + POSTMAN_SCHEMA = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'.freeze + + def initialize(index, configuration) + @index = index + @configuration = configuration + end + + def sections + IndexHelper.sections(examples, @configuration) + end + + def examples + @index.examples.map do |example| + PostmanRequestExample.new(example) + end + end + + def as_json(opts = nil) + collections = { :info => { :name => @configuration.api_name, + :description => @configuration.api_explanation, + :schema => POSTMAN_SCHEMA }, + :item => [] + } + + sections.each do |section| + folder = { :name => section[:resource_name], + :description => section[:resource_explanation], + :item => section[:examples].map do |example| + example.as_json(opts) + end + } + collections[:item] << folder + end + + collections + end + end + + class PostmanRequestExample + attr_reader :metadata + + def initialize(example) + @example = example + @metadata = requests.first + end + + def method_missing(method, *args, &block) + @example.send(method, *args, &block) + end + + def path_without_query(request_path) + URI::parse(request_path).path.split('/').reject { |p| p.empty? } + end + + def as_json(ots = nil) + { + :name => description, + :request => { + :method => http_method, + :header => [], + :body => {}, + :url => { + :raw => ('{{application_url}}' + metadata[:request_path] rescue ''), + :host => ['{{application_url}}'], + :path => path_without_query(metadata[:request_path]), + :variable => [] + }, + :description => explanation + }, + :response => [] + } + end + end + end +end \ No newline at end of file From d127e9e59e9f97f24c6ffe26227bb129f0ae5a9c Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 25 Feb 2018 13:39:57 -0800 Subject: [PATCH 02/24] Getting rid of raw, adding query placeholder, and using route --- features/postman.feature | 4 ++-- lib/rspec_api_documentation/writers/postman_writer.rb | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/features/postman.feature b/features/postman.feature index 537819d0..68f51c9f 100644 --- a/features/postman.feature +++ b/features/postman.feature @@ -85,13 +85,13 @@ Feature: Postman "header": [], "body": {}, "url": { - "raw": "{{application_url}}/greetings?target=Sam+%26+Eric", "host": [ "{{application_url}}" ], "path": [ "greetings" ], + "query" : [], "variable": [] }, "description": null @@ -105,13 +105,13 @@ Feature: Postman "header": [], "body": {}, "url": { - "raw": "{{application_url}}/greetings?target=rspec_api_documentation", "host": [ "{{application_url}}" ], "path": [ "greetings" ], + "query" : [], "variable": [] }, "description": null diff --git a/lib/rspec_api_documentation/writers/postman_writer.rb b/lib/rspec_api_documentation/writers/postman_writer.rb index 1c805d33..b7d39420 100644 --- a/lib/rspec_api_documentation/writers/postman_writer.rb +++ b/lib/rspec_api_documentation/writers/postman_writer.rb @@ -69,10 +69,6 @@ def method_missing(method, *args, &block) @example.send(method, *args, &block) end - def path_without_query(request_path) - URI::parse(request_path).path.split('/').reject { |p| p.empty? } - end - def as_json(ots = nil) { :name => description, @@ -81,9 +77,9 @@ def as_json(ots = nil) :header => [], :body => {}, :url => { - :raw => ('{{application_url}}' + metadata[:request_path] rescue ''), :host => ['{{application_url}}'], - :path => path_without_query(metadata[:request_path]), + :path => route.split('/').reject { |p| p.empty? }, + :query => [], :variable => [] }, :description => explanation From cb66b5971cef6c74409685dee201a2e0d8ea2e00 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 25 Feb 2018 14:47:19 -0800 Subject: [PATCH 03/24] support for query and description --- features/postman.feature | 16 ++++++++++++++-- .../writers/postman_writer.rb | 16 +++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/features/postman.feature b/features/postman.feature index 68f51c9f..6fb06d97 100644 --- a/features/postman.feature +++ b/features/postman.feature @@ -91,7 +91,13 @@ Feature: Postman "path": [ "greetings" ], - "query" : [], + "query" : [ + { + "key": "target", + "equals": true, + "description": "The thing you want to greet" + } + ], "variable": [] }, "description": null @@ -111,7 +117,13 @@ Feature: Postman "path": [ "greetings" ], - "query" : [], + "query" : [ + { + "key": "target", + "equals": true, + "description": "The thing you want to greet" + } + ], "variable": [] }, "description": null diff --git a/lib/rspec_api_documentation/writers/postman_writer.rb b/lib/rspec_api_documentation/writers/postman_writer.rb index b7d39420..3d97ab4c 100644 --- a/lib/rspec_api_documentation/writers/postman_writer.rb +++ b/lib/rspec_api_documentation/writers/postman_writer.rb @@ -69,6 +69,20 @@ def method_missing(method, *args, &block) @example.send(method, *args, &block) end + def populate_query + query_params = [] + if @example.respond_to?(:parameters) + @example.parameters.map do |param| + query_params << { + :key => param[:name], + :equals => true, + :description => (param[:required] ? "Required" : "") + param[:description] + } + end + end + query_params + end + def as_json(ots = nil) { :name => description, @@ -79,7 +93,7 @@ def as_json(ots = nil) :url => { :host => ['{{application_url}}'], :path => route.split('/').reject { |p| p.empty? }, - :query => [], + :query => populate_query, :variable => [] }, :description => explanation From 6d8da3f5729b7cd93db5f8a71b1291733a39670e Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 25 Feb 2018 15:05:55 -0800 Subject: [PATCH 04/24] adding preliminary Postman writer test --- spec/writers/postman_writer_spec.rb | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 spec/writers/postman_writer_spec.rb diff --git a/spec/writers/postman_writer_spec.rb b/spec/writers/postman_writer_spec.rb new file mode 100644 index 00000000..22c368ba --- /dev/null +++ b/spec/writers/postman_writer_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe RspecApiDocumentation::Writers::PostmanWriter do + let(:index) { RspecApiDocumentation::Index.new } + let(:configuration) { RspecApiDocumentation::Configuration.new } + + describe ".write" do + let(:writer) { double(:writer) } + + it "should build a new writer and write the docs" do + allow(described_class).to receive(:new).with(index, configuration).and_return(writer) + expect(writer).to receive(:write) + described_class.write(index, configuration) + end + end + + describe "#write" do + let(:writer) { described_class.new(index, configuration) } + + before do + allow(configuration.api_name).to receive(:parameterize).and_return("Name") + FileUtils.mkdir_p(configuration.docs_dir) + end + + it "should write the collection" do + writer.write + collection_file = File.join(configuration.docs_dir, "Name.postman_collection.json") + expect(File.exists?(collection_file)).to be_truthy + end + end +end \ No newline at end of file From b07ed5f5f89d9cc4cc0e18fb355cc28855483a06 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 25 Feb 2018 15:07:52 -0800 Subject: [PATCH 05/24] Initial update for README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 332f3bd6..a8805b08 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ RspecApiDocumentation.configure do |config| # An array of output format(s). # Possible values are :json, :html, :combined_text, :combined_json, # :json_iodocs, :textile, :markdown, :append_json, :slate, - # :api_blueprint + # :api_blueprint, :postman config.format = [:html] # Location of templates @@ -172,6 +172,7 @@ end * **markdown**: Generates an index file and example files in Markdown. * **api_blueprint**: Generates an index file and example files in [APIBlueprint](https://apiblueprint.org). * **append_json**: Lets you selectively run specs without destroying current documentation. See section below. +* **postman**: Generates a Postman collection JSON file that you can import ### append_json From 681c72b3913bdf9aaa7f3cd6be648ca7a649710b Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Wed, 28 Feb 2018 17:13:30 -0800 Subject: [PATCH 06/24] Initial content type support --- features/postman.feature | 15 +++++++++++++-- .../writers/postman_writer.rb | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/features/postman.feature b/features/postman.feature index 6fb06d97..f4d6b0b2 100644 --- a/features/postman.feature +++ b/features/postman.feature @@ -28,6 +28,7 @@ Feature: Postman resource "Greetings" do explanation "Greetings API methods" + header "Content-Type", "application/json" get "/greetings" do parameter :target, "The thing you want to greet" @@ -82,7 +83,12 @@ Feature: Postman "name": "Greeting your favorite developers of your favorite gem", "request": { "method": "GET", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], "body": {}, "url": { "host": [ @@ -108,7 +114,12 @@ Feature: Postman "name": "Greeting your favorite gem", "request": { "method": "GET", - "header": [], + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], "body": {}, "url": { "host": [ diff --git a/lib/rspec_api_documentation/writers/postman_writer.rb b/lib/rspec_api_documentation/writers/postman_writer.rb index 3d97ab4c..1d1e7f83 100644 --- a/lib/rspec_api_documentation/writers/postman_writer.rb +++ b/lib/rspec_api_documentation/writers/postman_writer.rb @@ -88,7 +88,7 @@ def as_json(ots = nil) :name => description, :request => { :method => http_method, - :header => [], + :header => [ { :key => "Content-Type", :value => @metadata[:request_headers]["Content-Type"] } ], :body => {}, :url => { :host => ['{{application_url}}'], From cc88cbff9a8d04c0e6b8db5f261760502b17b834 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sat, 3 Mar 2018 15:18:41 -0800 Subject: [PATCH 07/24] Initial content type support in header --- .../writers/postman_writer.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/rspec_api_documentation/writers/postman_writer.rb b/lib/rspec_api_documentation/writers/postman_writer.rb index 1d1e7f83..ef7c7c67 100644 --- a/lib/rspec_api_documentation/writers/postman_writer.rb +++ b/lib/rspec_api_documentation/writers/postman_writer.rb @@ -83,13 +83,28 @@ def populate_query query_params end + def content_type + { :key => 'Content-Type', :value => @metadata[:request_headers]['Content-Type'] } + end + + def body + case content_type[:value] + when 'application/json' + @metadata[:request_body] ? { :mode => 'raw', :raw => @metadata[:request_body] } : {} + when 'w-www-form-urlencoded' + @metadata[:request_body] ? { :mode => 'urlencoded', :urlencoded => @metadata[:request_body] } : {} + else + {} + end + end + def as_json(ots = nil) { :name => description, :request => { :method => http_method, - :header => [ { :key => "Content-Type", :value => @metadata[:request_headers]["Content-Type"] } ], - :body => {}, + :header => [content_type], + :body => body, :url => { :host => ['{{application_url}}'], :path => route.split('/').reject { |p| p.empty? }, From d8117d1c14f9f878ef38b5ab75309faf3af3614f Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sat, 3 Mar 2018 17:14:27 -0800 Subject: [PATCH 08/24] Moving Postman related classes into individual files --- lib/rspec_api_documentation.rb | 2 + .../views/postman_index.rb | 42 ++++++++ .../views/postman_request_example.rb | 64 +++++++++++ .../writers/postman_writer.rb | 102 +----------------- 4 files changed, 109 insertions(+), 101 deletions(-) create mode 100644 lib/rspec_api_documentation/views/postman_index.rb create mode 100644 lib/rspec_api_documentation/views/postman_request_example.rb diff --git a/lib/rspec_api_documentation.rb b/lib/rspec_api_documentation.rb index 7fb56092..2690b43e 100644 --- a/lib/rspec_api_documentation.rb +++ b/lib/rspec_api_documentation.rb @@ -63,6 +63,8 @@ module Views autoload :SlateExample autoload :ApiBlueprintIndex autoload :ApiBlueprintExample + autoload :PostmanIndex + autoload :PostmanRequestExample end def self.configuration diff --git a/lib/rspec_api_documentation/views/postman_index.rb b/lib/rspec_api_documentation/views/postman_index.rb new file mode 100644 index 00000000..237e63e5 --- /dev/null +++ b/lib/rspec_api_documentation/views/postman_index.rb @@ -0,0 +1,42 @@ +module RspecApiDocumentation + module Views + class PostmanIndex + POSTMAN_SCHEMA = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'.freeze + + def initialize(index, configuration) + @index = index + @configuration = configuration + end + + def sections + Writers::IndexHelper.sections(examples, @configuration) + end + + def examples + @index.examples.map do |example| + Views::PostmanRequestExample.new(example) + end + end + + def as_json(opts = nil) + collections = { :info => { :name => @configuration.api_name, + :description => @configuration.api_explanation, + :schema => POSTMAN_SCHEMA }, + :item => [] + } + + sections.each do |section| + folder = { :name => section[:resource_name], + :description => section[:resource_explanation], + :item => section[:examples].map do |example| + example.as_json(opts) + end + } + collections[:item] << folder + end + + collections + end + end + end +end \ No newline at end of file diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb new file mode 100644 index 00000000..f8630d81 --- /dev/null +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -0,0 +1,64 @@ +module RspecApiDocumentation + module Views + class PostmanRequestExample + attr_reader :metadata + + def initialize(example) + @example = example + @metadata = requests.first + end + + def method_missing(method, *args, &block) + @example.send(method, *args, &block) + end + + def populate_query + query_params = [] + if @example.respond_to?(:parameters) + @example.parameters.map do |param| + query_params << { + :key => param[:name], + :equals => true, + :description => (param[:required] ? "Required" : "") + param[:description] + } + end + end + query_params + end + + def content_type + { :key => 'Content-Type', :value => @metadata[:request_headers]['Content-Type'] } + end + + def body + case content_type[:value] + when 'application/json' + @metadata[:request_body] ? { :mode => 'raw', :raw => @metadata[:request_body] } : {} + when 'w-www-form-urlencoded' + @metadata[:request_body] ? { :mode => 'urlencoded', :urlencoded => @metadata[:request_body] } : {} + else + @metadata[:request_body] + end + end + + def as_json(ots = nil) + { + :name => description, + :request => { + :method => http_method, + :header => [content_type], + :body => body, + :url => { + :host => ['{{application_url}}'], + :path => route.split('/').reject { |p| p.empty? }, + :query => populate_query, + :variable => [] + }, + :description => explanation + }, + :response => [] + } + end + end + end +end \ No newline at end of file diff --git a/lib/rspec_api_documentation/writers/postman_writer.rb b/lib/rspec_api_documentation/writers/postman_writer.rb index ef7c7c67..6caac416 100644 --- a/lib/rspec_api_documentation/writers/postman_writer.rb +++ b/lib/rspec_api_documentation/writers/postman_writer.rb @@ -13,109 +13,9 @@ def initialize(index, configuration) def write File.open(docs_dir.join("#{api_name}.postman_collection.json"), "w+") do |file| - file.write Formatter.to_json(PostmanIndex.new(index, configuration)) + file.write Formatter.to_json(Views::PostmanIndex.new(index, configuration)) end end end - - class PostmanIndex - POSTMAN_SCHEMA = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'.freeze - - def initialize(index, configuration) - @index = index - @configuration = configuration - end - - def sections - IndexHelper.sections(examples, @configuration) - end - - def examples - @index.examples.map do |example| - PostmanRequestExample.new(example) - end - end - - def as_json(opts = nil) - collections = { :info => { :name => @configuration.api_name, - :description => @configuration.api_explanation, - :schema => POSTMAN_SCHEMA }, - :item => [] - } - - sections.each do |section| - folder = { :name => section[:resource_name], - :description => section[:resource_explanation], - :item => section[:examples].map do |example| - example.as_json(opts) - end - } - collections[:item] << folder - end - - collections - end - end - - class PostmanRequestExample - attr_reader :metadata - - def initialize(example) - @example = example - @metadata = requests.first - end - - def method_missing(method, *args, &block) - @example.send(method, *args, &block) - end - - def populate_query - query_params = [] - if @example.respond_to?(:parameters) - @example.parameters.map do |param| - query_params << { - :key => param[:name], - :equals => true, - :description => (param[:required] ? "Required" : "") + param[:description] - } - end - end - query_params - end - - def content_type - { :key => 'Content-Type', :value => @metadata[:request_headers]['Content-Type'] } - end - - def body - case content_type[:value] - when 'application/json' - @metadata[:request_body] ? { :mode => 'raw', :raw => @metadata[:request_body] } : {} - when 'w-www-form-urlencoded' - @metadata[:request_body] ? { :mode => 'urlencoded', :urlencoded => @metadata[:request_body] } : {} - else - {} - end - end - - def as_json(ots = nil) - { - :name => description, - :request => { - :method => http_method, - :header => [content_type], - :body => body, - :url => { - :host => ['{{application_url}}'], - :path => route.split('/').reject { |p| p.empty? }, - :query => populate_query, - :variable => [] - }, - :description => explanation - }, - :response => [] - } - end - end end end \ No newline at end of file From 1725480fd03579a7fb6df0490054e804eb57db7a Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sat, 3 Mar 2018 17:21:45 -0800 Subject: [PATCH 09/24] Updating hash syntax --- .../views/postman_index.rb | 14 ++++---- .../views/postman_request_example.rb | 36 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/rspec_api_documentation/views/postman_index.rb b/lib/rspec_api_documentation/views/postman_index.rb index 237e63e5..e5b571b3 100644 --- a/lib/rspec_api_documentation/views/postman_index.rb +++ b/lib/rspec_api_documentation/views/postman_index.rb @@ -19,16 +19,16 @@ def examples end def as_json(opts = nil) - collections = { :info => { :name => @configuration.api_name, - :description => @configuration.api_explanation, - :schema => POSTMAN_SCHEMA }, - :item => [] + collections = { info: { name: @configuration.api_name, + description: @configuration.api_explanation, + schema: POSTMAN_SCHEMA }, + item: [] } sections.each do |section| - folder = { :name => section[:resource_name], - :description => section[:resource_explanation], - :item => section[:examples].map do |example| + folder = { name: section[:resource_name], + description: section[:resource_explanation], + item: section[:examples].map do |example| example.as_json(opts) end } diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb index f8630d81..58c67ece 100644 --- a/lib/rspec_api_documentation/views/postman_request_example.rb +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -17,9 +17,9 @@ def populate_query if @example.respond_to?(:parameters) @example.parameters.map do |param| query_params << { - :key => param[:name], - :equals => true, - :description => (param[:required] ? "Required" : "") + param[:description] + key: param[:name], + equals: true, + description: (param[:required] ? "Required" : "") + param[:description] } end end @@ -27,15 +27,15 @@ def populate_query end def content_type - { :key => 'Content-Type', :value => @metadata[:request_headers]['Content-Type'] } + { key: 'Content-Type', value: @metadata[:request_headers]['Content-Type'] } end def body case content_type[:value] when 'application/json' - @metadata[:request_body] ? { :mode => 'raw', :raw => @metadata[:request_body] } : {} + @metadata[:request_body] ? { mode: 'raw', raw: @metadata[:request_body] } : {} when 'w-www-form-urlencoded' - @metadata[:request_body] ? { :mode => 'urlencoded', :urlencoded => @metadata[:request_body] } : {} + @metadata[:request_body] ? { mode: 'urlencoded', urlencoded: @metadata[:request_body] } : {} else @metadata[:request_body] end @@ -43,20 +43,20 @@ def body def as_json(ots = nil) { - :name => description, - :request => { - :method => http_method, - :header => [content_type], - :body => body, - :url => { - :host => ['{{application_url}}'], - :path => route.split('/').reject { |p| p.empty? }, - :query => populate_query, - :variable => [] + name: description, + request: { + method: http_method, + header: [content_type], + body: body, + url: { + host: ['{{application_url}}'], + path: route.split('/').reject { |p| p.empty? }, + query: populate_query, + variable: [] }, - :description => explanation + description: explanation }, - :response => [] + response: [] } end end From 05801d3b896eb9c41380809e7c7d27774a073923 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sat, 3 Mar 2018 17:34:05 -0800 Subject: [PATCH 10/24] Initial commit for PostmanRequestExample spec --- spec/views/postman_example_spec.rb | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 spec/views/postman_example_spec.rb diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb new file mode 100644 index 00000000..b5271327 --- /dev/null +++ b/spec/views/postman_example_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + + +describe RspecApiDocumentation::Views::PostmanRequestExample do + let(:metadata) { { resource_name: 'Orders' } } + let(:group) { RSpec::Core::ExampleGroup.describe('', metadata) } + let(:rspec_example) { group.example('Ordering a cup of coffee') {} } + let(:rad_example) do + RspecApiDocumentation::Example.new(rspec_example, configuration) + end + let(:configuration) { RspecApiDocumentation::Configuration.new } + let(:postman_example) { described_class.new(rad_example) } + + let(:content_type) { "application/json" } + let(:requests) do + [{ + request_body: "{}", + request_headers: { + "Content-Type" => content_type + }, + request_content_type: "", + }] + end + + before do + rspec_example.metadata[:requests] = requests + end + + describe '#populate_query' do + it 'just does something' do + puts rspec_example.inspect + puts rad_example.inspect + expect(postman_example).to eq true + end + end +end \ No newline at end of file From 6ac335d3e550a3471b2a4ccbcf281ee3c5732cf6 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sat, 3 Mar 2018 17:49:43 -0800 Subject: [PATCH 11/24] Test for parameters --- .../views/postman_request_example.rb | 2 +- spec/views/postman_example_spec.rb | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb index 58c67ece..e28b449b 100644 --- a/lib/rspec_api_documentation/views/postman_request_example.rb +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -19,7 +19,7 @@ def populate_query query_params << { key: param[:name], equals: true, - description: (param[:required] ? "Required" : "") + param[:description] + description: (param[:required] ? "Required. " : "") + param[:description] } end end diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index b5271327..8b213b52 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -2,7 +2,15 @@ describe RspecApiDocumentation::Views::PostmanRequestExample do - let(:metadata) { { resource_name: 'Orders' } } + let(:metadata) do + { resource_name: 'Orders', + parameters: + [ + {name: 'type', required: false, description: 'decaf or regular'}, + {name: 'size', required: true, description: 'cup size' } + ] + } + end let(:group) { RSpec::Core::ExampleGroup.describe('', metadata) } let(:rspec_example) { group.example('Ordering a cup of coffee') {} } let(:rad_example) do @@ -27,10 +35,18 @@ end describe '#populate_query' do - it 'just does something' do - puts rspec_example.inspect - puts rad_example.inspect - expect(postman_example).to eq true + it 'populates parameteres' do + expect(postman_example.populate_query).to eq [{ + key: 'type', + equals: true, + description: 'decaf or regular' + }, + { + key: 'size', + equals: true, + description: 'Required. cup size' + } + ] end end end \ No newline at end of file From 4f74878812d1441744c78bb2225301acfafa9f0b Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sat, 3 Mar 2018 18:03:12 -0800 Subject: [PATCH 12/24] initial content type test --- spec/views/postman_example_spec.rb | 32 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index 8b213b52..9fa00ce8 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -26,7 +26,7 @@ request_headers: { "Content-Type" => content_type }, - request_content_type: "", + request_content_type: "" }] end @@ -34,19 +34,25 @@ rspec_example.metadata[:requests] = requests end + subject(:view) { postman_example } + describe '#populate_query' do - it 'populates parameteres' do - expect(postman_example.populate_query).to eq [{ - key: 'type', - equals: true, - description: 'decaf or regular' - }, - { - key: 'size', - equals: true, - description: 'Required. cup size' - } - ] + it 'populates parameters' do + expect(subject.populate_query).to eq [{ key: 'type', + equals: true, + description: 'decaf or regular' + }, + { + key: 'size', + equals: true, + description: 'Required. cup size' + }] + end + end + + describe '#content_type' do + it 'parses content type' do + expect(subject.content_type).to eq({key: 'Content-Type', value: 'application/json'}) end end end \ No newline at end of file From f31c6193d40299d6813e493a1471cbb6a3ba3701 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 08:34:06 -0800 Subject: [PATCH 13/24] initial test for #as_json --- spec/views/postman_example_spec.rb | 36 ++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index 9fa00ce8..22642b58 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -8,7 +8,9 @@ [ {name: 'type', required: false, description: 'decaf or regular'}, {name: 'size', required: true, description: 'cup size' } - ] + ], + route: '/orders', + method: 'get' } end let(:group) { RSpec::Core::ExampleGroup.describe('', metadata) } @@ -52,7 +54,37 @@ describe '#content_type' do it 'parses content type' do - expect(subject.content_type).to eq({key: 'Content-Type', value: 'application/json'}) + expect(subject.content_type).to eq({ key: 'Content-Type', value: 'application/json' }) + end + end + + describe '#as_json' do + it 'something' do + expected_hash = { + name: 'Ordering a cup of coffee', + request: { + method: 'GET', + header: [{ key: 'Content-Type', value: content_type }], + body: { mode: 'raw', raw: '{}'}, + url: { + host: ['{{application_url}}'], + path: ['orders'], + query: [{ key: 'type', + equals: true, + description: 'decaf or regular' + }, + { + key: 'size', + equals: true, + description: 'Required. cup size' + }], + variable: [] + }, + description: nil, + }, + response: [] + } + expect(subject.as_json).to eq expected_hash end end end \ No newline at end of file From fc578c8ddf8060dfeac4fe2d9d19af5583828c73 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 08:44:47 -0800 Subject: [PATCH 14/24] Refactoring tests --- spec/views/postman_example_spec.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index 22642b58..93a16171 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -1,6 +1,5 @@ require 'spec_helper' - describe RspecApiDocumentation::Views::PostmanRequestExample do let(:metadata) do { resource_name: 'Orders', @@ -10,21 +9,23 @@ {name: 'size', required: true, description: 'cup size' } ], route: '/orders', - method: 'get' + method: 'post' } end let(:group) { RSpec::Core::ExampleGroup.describe('', metadata) } - let(:rspec_example) { group.example('Ordering a cup of coffee') {} } + let(:description) { 'Ordering a cup of coffee' } + let(:rspec_example) { group.example(description) {} } let(:rad_example) do RspecApiDocumentation::Example.new(rspec_example, configuration) end let(:configuration) { RspecApiDocumentation::Configuration.new } let(:postman_example) { described_class.new(rad_example) } - let(:content_type) { "application/json" } + let(:content_type) { 'application/json' } + let(:body_content) { "{ \"customer_name\": \"FooBar\" }" } let(:requests) do [{ - request_body: "{}", + request_body: body_content, request_headers: { "Content-Type" => content_type }, @@ -61,11 +62,11 @@ describe '#as_json' do it 'something' do expected_hash = { - name: 'Ordering a cup of coffee', + name: description, request: { - method: 'GET', + method: 'POST', header: [{ key: 'Content-Type', value: content_type }], - body: { mode: 'raw', raw: '{}'}, + body: { mode: 'raw', raw: body_content }, url: { host: ['{{application_url}}'], path: ['orders'], From e206cf304998363fc5fe2170773a57e89545922a Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 08:57:12 -0800 Subject: [PATCH 15/24] Making distinctions in test for POST and GET for #as_json --- spec/views/postman_example_spec.rb | 88 ++++++++++++++++++++---------- 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index 93a16171..d6e791dd 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -9,7 +9,7 @@ {name: 'size', required: true, description: 'cup size' } ], route: '/orders', - method: 'post' + method: 'get' } end let(:group) { RSpec::Core::ExampleGroup.describe('', metadata) } @@ -22,7 +22,7 @@ let(:postman_example) { described_class.new(rad_example) } let(:content_type) { 'application/json' } - let(:body_content) { "{ \"customer_name\": \"FooBar\" }" } + let(:body_content) { '{}' } let(:requests) do [{ request_body: body_content, @@ -60,32 +60,64 @@ end describe '#as_json' do - it 'something' do - expected_hash = { - name: description, - request: { - method: 'POST', - header: [{ key: 'Content-Type', value: content_type }], - body: { mode: 'raw', raw: body_content }, - url: { - host: ['{{application_url}}'], - path: ['orders'], - query: [{ key: 'type', - equals: true, - description: 'decaf or regular' - }, - { - key: 'size', - equals: true, - description: 'Required. cup size' - }], - variable: [] - }, - description: nil, - }, - response: [] - } - expect(subject.as_json).to eq expected_hash + context 'when the example is for POST' do + let(:metadata) do + { resource_name: 'Orders', + route: '/orders', + method: 'post' + } + end + let(:body_content) { "{ \"customer_name\": \"FooBar\" }" } + + it 'returns expected hash with correct data' do + expected_hash = { + name: description, + request: { + method: 'POST', + header: [{ key: 'Content-Type', value: content_type }], + body: { mode: 'raw', raw: body_content }, + url: { + host: ['{{application_url}}'], + path: ['orders'], + query: [], + variable: [] + }, + description: nil, + }, + response: [] + } + expect(subject.as_json).to eq expected_hash + end + end + + context 'when the example is for GET' do + it 'returns expected hash with correct data' do + expected_hash = { + name: description, + request: { + method: 'GET', + header: [{ key: 'Content-Type', value: content_type }], + body: { mode: 'raw', raw: body_content }, + url: { + host: ['{{application_url}}'], + path: ['orders'], + query: [{ key: 'type', + equals: true, + description: 'decaf or regular' + }, + { + key: 'size', + equals: true, + description: 'Required. cup size' + }], + variable: [] + }, + description: nil, + }, + response: [] + } + expect(subject.as_json).to eq expected_hash + end end end end \ No newline at end of file From 31d8f2fdebe033bd80cb6d750431adbb338dc5e4 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 13:46:59 -0800 Subject: [PATCH 16/24] Body support for urlencoded type --- .../views/postman_request_example.rb | 43 +++++++++++++++---- spec/views/postman_example_spec.rb | 38 +++++++++++++++- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb index e28b449b..779d50d3 100644 --- a/lib/rspec_api_documentation/views/postman_request_example.rb +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -19,7 +19,7 @@ def populate_query query_params << { key: param[:name], equals: true, - description: (param[:required] ? "Required. " : "") + param[:description] + description: format_description(param[:description], param[:required]) } end end @@ -27,17 +27,18 @@ def populate_query end def content_type - { key: 'Content-Type', value: @metadata[:request_headers]['Content-Type'] } + { key: 'Content-Type', value: metadata[:request_headers]['Content-Type'] } end def body - case content_type[:value] - when 'application/json' - @metadata[:request_body] ? { mode: 'raw', raw: @metadata[:request_body] } : {} - when 'w-www-form-urlencoded' - @metadata[:request_body] ? { mode: 'urlencoded', urlencoded: @metadata[:request_body] } : {} - else - @metadata[:request_body] + return {} unless metadata[:request_body] + + if content_type[:value].include?('application/json') + { mode: 'raw', raw: metadata[:request_body] } + elsif content_type[:value] == 'application/w-www-form-urlencoded' + { mode: 'urlencoded', urlencoded: build_urlencoded_body } + elsif content_type[:value] == 'binary' + metadata[:request_body] end end @@ -59,6 +60,30 @@ def as_json(ots = nil) response: [] } end + + private + + def build_urlencoded_body + urlencoded_params = [] + params = CGI::parse(metadata[:request_body]) + params.each do |p| + param_from_example = @example.parameters.select{ |e| e[:name] == p[0] }.first + urlencoded_param = { + key: p[0], + value: '', + description: format_description(param_from_example[:description], + param_from_example[:required]), + type: 'text', + disabled: !param_from_example[:required] + } + urlencoded_params << urlencoded_param + end + urlencoded_params + end + + def format_description(description, required = false) + required ? "Required. #{description}" : description + end end end end \ No newline at end of file diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index d6e791dd..d5c9624b 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -41,7 +41,8 @@ describe '#populate_query' do it 'populates parameters' do - expect(subject.populate_query).to eq [{ key: 'type', + expect(subject.populate_query).to eq [{ + key: 'type', equals: true, description: 'decaf or regular' }, @@ -59,6 +60,41 @@ end end + describe '#body' do + context 'when content type includes application/json' do + let(:body_content) { "{ \"customer_name\": \"FooBar\" }" } + + it 'returns raw mode hash' do + expect(subject.body).to eq({ mode: 'raw', raw: body_content }) + end + end + + context 'when content type is w-www-form-urlencoded' do + let(:content_type) { 'application/w-www-form-urlencoded' } + let(:body_content) { "type=decaf&size=regular"} + + it 'returns urlencoded hash' do + expect(subject.body).to eq({ mode: 'urlencoded', + urlencoded: [ + { + key: 'type', + value: '', + description: 'decaf or regular', + type: 'text', + disabled: true + }, + { + key: 'size', + value: '', + description: 'Required. cup size', + type: 'text', + disabled: false + }] + }) + end + end + end + describe '#as_json' do context 'when the example is for POST' do let(:metadata) do From 61c7617890e6aa5f4c023e6c8b9a84a9a86c247f Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 14:33:29 -0800 Subject: [PATCH 17/24] Modifying conditionals for body and refactoring --- .../views/postman_request_example.rb | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb index 779d50d3..9bcd8d47 100644 --- a/lib/rspec_api_documentation/views/postman_request_example.rb +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -33,12 +33,12 @@ def content_type def body return {} unless metadata[:request_body] - if content_type[:value].include?('application/json') - { mode: 'raw', raw: metadata[:request_body] } - elsif content_type[:value] == 'application/w-www-form-urlencoded' + if content_type[:value] == 'application/w-www-form-urlencoded' { mode: 'urlencoded', urlencoded: build_urlencoded_body } - elsif content_type[:value] == 'binary' - metadata[:request_body] + elsif content_type[:value] == 'application/octet-stream' + { mode: 'file', file: {} } + else + { mode: 'raw', raw: metadata[:request_body] } end end @@ -67,16 +67,18 @@ def build_urlencoded_body urlencoded_params = [] params = CGI::parse(metadata[:request_body]) params.each do |p| - param_from_example = @example.parameters.select{ |e| e[:name] == p[0] }.first - urlencoded_param = { - key: p[0], - value: '', - description: format_description(param_from_example[:description], - param_from_example[:required]), - type: 'text', - disabled: !param_from_example[:required] - } - urlencoded_params << urlencoded_param + param_from_example = @example.parameters.select{ |e| e[:name] == p.first }.try(:first) + if param_from_example + urlencoded_param = { + key: p.first, + value: '', + description: format_description(param_from_example[:description], + param_from_example[:required]), + type: 'text', + disabled: !param_from_example[:required] + } + urlencoded_params << urlencoded_param + end end urlencoded_params end From 7398060867bceb2cc95f788d248c1dc2bf4b6dbd Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 14:37:49 -0800 Subject: [PATCH 18/24] Introducing 'disabled' field for optional params --- .../views/postman_request_example.rb | 3 ++- spec/views/postman_example_spec.rb | 27 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb index 9bcd8d47..b506c9fa 100644 --- a/lib/rspec_api_documentation/views/postman_request_example.rb +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -19,7 +19,8 @@ def populate_query query_params << { key: param[:name], equals: true, - description: format_description(param[:description], param[:required]) + description: format_description(param[:description], param[:required]), + disabled: param[:required] ? !param[:required] : true } end end diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index d5c9624b..b2fb6cd5 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -44,12 +44,14 @@ expect(subject.populate_query).to eq [{ key: 'type', equals: true, - description: 'decaf or regular' + description: 'decaf or regular', + disabled: true }, { key: 'size', equals: true, - description: 'Required. cup size' + description: 'Required. cup size', + disabled: false }] end end @@ -137,15 +139,18 @@ url: { host: ['{{application_url}}'], path: ['orders'], - query: [{ key: 'type', - equals: true, - description: 'decaf or regular' - }, - { - key: 'size', - equals: true, - description: 'Required. cup size' - }], + query: [ + { key: 'type', + equals: true, + description: 'decaf or regular', + disabled: true + }, + { + key: 'size', + equals: true, + description: 'Required. cup size', + disabled: false + }], variable: [] }, description: nil, From 994ccc16286ef265dd7dde808dd9b44c64e64643 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 16:18:52 -0800 Subject: [PATCH 19/24] Renaming methods and implemented a method to fill variable array --- .../views/postman_request_example.rb | 50 +++++++++++++++---- spec/views/postman_example_spec.rb | 32 ++++++++++-- 2 files changed, 68 insertions(+), 14 deletions(-) diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb index b506c9fa..9492a899 100644 --- a/lib/rspec_api_documentation/views/postman_request_example.rb +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -12,16 +12,18 @@ def method_missing(method, *args, &block) @example.send(method, *args, &block) end - def populate_query + def query_in_url query_params = [] if @example.respond_to?(:parameters) @example.parameters.map do |param| - query_params << { - key: param[:name], - equals: true, - description: format_description(param[:description], param[:required]), - disabled: param[:required] ? !param[:required] : true - } + unless tokenized_path.include?(":#{param[:name]}") + query_params << { + key: param[:name], + equals: true, + description: format_description(param[:description], param[:required]), + disabled: !param[:required] + } + end end end query_params @@ -43,6 +45,30 @@ def body end end + def variables_for_url + route_variables = tokenized_path.select { |r| r.start_with?(':') } + return [] unless route_variables + + variables = [] + route_variables.each do |rv| + param_name = rv.split(':')[1] + param_from_example = @example.parameters.select { |e| e[:name] == param_name }.try(:first) + if param_from_example + variable = { + key: param_name, + value: '', + description: format_description(param_from_example[:description], + param_from_example[:required]), + disabled: !param_from_example[:required] + + } + variables << variable + end + end + + variables + end + def as_json(ots = nil) { name: description, @@ -52,9 +78,9 @@ def as_json(ots = nil) body: body, url: { host: ['{{application_url}}'], - path: route.split('/').reject { |p| p.empty? }, - query: populate_query, - variable: [] + path: tokenized_path, + query: query_in_url, + variable: variables_for_url }, description: explanation }, @@ -64,6 +90,10 @@ def as_json(ots = nil) private + def tokenized_path + route.split('/').reject { |p| p.empty? } + end + def build_urlencoded_body urlencoded_params = [] params = CGI::parse(metadata[:request_body]) diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index b2fb6cd5..96bdaba7 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -5,8 +5,8 @@ { resource_name: 'Orders', parameters: [ - {name: 'type', required: false, description: 'decaf or regular'}, - {name: 'size', required: true, description: 'cup size' } + { name: 'type', required: false, description: 'decaf or regular' }, + { name: 'size', required: true, description: 'cup size' } ], route: '/orders', method: 'get' @@ -39,9 +39,9 @@ subject(:view) { postman_example } - describe '#populate_query' do + describe '#query_in_url' do it 'populates parameters' do - expect(subject.populate_query).to eq [{ + expect(subject.query_in_url).to eq [{ key: 'type', equals: true, description: 'decaf or regular', @@ -97,6 +97,30 @@ end end + describe '#variables_for_url' do + context 'when route has a variable' do + let(:metadata) do + { resource_name: 'Orders', + parameters: + [ + { name: 'id', required: true, description: 'Order ID' }, + { name: 'type', required: false, description: 'decaf or regular' }, + { name: 'size', required: true, description: 'cup size' } + ], + route: '/orders/:id', + method: 'get' + } + end + + it 'can populate variable for url' do + expect(subject.variables_for_url).to eq([{ key: 'id', + value: '', + description: 'Required. Order ID', + disabled: false }]) + end + end + end + describe '#as_json' do context 'when the example is for POST' do let(:metadata) do From 10410d50c2b8ebbb4d077c4fa6b38f511d93efdb Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 16:23:08 -0800 Subject: [PATCH 20/24] Updating README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a8805b08..49168c0a 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ end * **markdown**: Generates an index file and example files in Markdown. * **api_blueprint**: Generates an index file and example files in [APIBlueprint](https://apiblueprint.org). * **append_json**: Lets you selectively run specs without destroying current documentation. See section below. -* **postman**: Generates a Postman collection JSON file that you can import +* **postman**: (In Alpha) Generates a Postman collection JSON file that you can import ### append_json From 5e851959b72fed6ff0c0b29023b97a1f281bb1c2 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 16:26:03 -0800 Subject: [PATCH 21/24] Updating test --- features/postman.feature | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/features/postman.feature b/features/postman.feature index f4d6b0b2..531c9fef 100644 --- a/features/postman.feature +++ b/features/postman.feature @@ -101,7 +101,8 @@ Feature: Postman { "key": "target", "equals": true, - "description": "The thing you want to greet" + "description": "The thing you want to greet", + "disabled": true } ], "variable": [] @@ -132,7 +133,8 @@ Feature: Postman { "key": "target", "equals": true, - "description": "The thing you want to greet" + "description": "The thing you want to greet", + "disabled": true } ], "variable": [] From 7748afed22cac0e74328a446c0e3704f6cb0439b Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 19:46:40 -0800 Subject: [PATCH 22/24] Separating out URL query params from request body params --- features/postman.feature | 12 +++++++- .../views/postman_request_example.rb | 24 ++++++++------- spec/views/postman_example_spec.rb | 29 +++++++++++-------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/features/postman.feature b/features/postman.feature index 531c9fef..cbf164b2 100644 --- a/features/postman.feature +++ b/features/postman.feature @@ -32,9 +32,10 @@ Feature: Postman get "/greetings" do parameter :target, "The thing you want to greet" + parameter :type, "foo" example "Greeting your favorite gem" do - do_request :target => "rspec_api_documentation" + do_request({:target => "rspec_api_documentation", :type => "foo"}) expect(response_headers["Content-Type"]).to eq("text/plain") expect(status).to eq(200) @@ -100,6 +101,7 @@ Feature: Postman "query" : [ { "key": "target", + "value": "", "equals": true, "description": "The thing you want to greet", "disabled": true @@ -132,9 +134,17 @@ Feature: Postman "query" : [ { "key": "target", + "value": "", "equals": true, "description": "The thing you want to greet", "disabled": true + }, + { + "key": "type", + "value": "", + "equals": true, + "description": "foo", + "disabled": true } ], "variable": [] diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb index 9492a899..752deb9b 100644 --- a/lib/rspec_api_documentation/views/postman_request_example.rb +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -14,18 +14,22 @@ def method_missing(method, *args, &block) def query_in_url query_params = [] - if @example.respond_to?(:parameters) - @example.parameters.map do |param| - unless tokenized_path.include?(":#{param[:name]}") - query_params << { - key: param[:name], - equals: true, - description: format_description(param[:description], param[:required]), - disabled: !param[:required] - } + + if metadata[:request_query_parameters] && @example.respond_to?(:parameters) + metadata[:request_query_parameters].map do |k, v| + documented_param = @example.parameters.select { |p| p[:name] == k.to_s }.try(:first) + if documented_param + query_params << { key: k.to_s, + value: '', + equals: true, + description: format_description(documented_param[:description], + documented_param[:required]), + disabled: !documented_param[:required] + } end end end + query_params end @@ -69,7 +73,7 @@ def variables_for_url variables end - def as_json(ots = nil) + def as_json(options = nil) { name: description, request: { diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index 96bdaba7..54d12eea 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -29,7 +29,8 @@ request_headers: { "Content-Type" => content_type }, - request_content_type: "" + request_content_type: "", + request_query_parameters: { type: 'decaf', size: 'tall' } }] end @@ -42,17 +43,19 @@ describe '#query_in_url' do it 'populates parameters' do expect(subject.query_in_url).to eq [{ - key: 'type', - equals: true, - description: 'decaf or regular', - disabled: true - }, - { - key: 'size', - equals: true, - description: 'Required. cup size', - disabled: false - }] + key: 'type', + value: '', + equals: true, + description: 'decaf or regular', + disabled: true + }, + { + key: 'size', + value: '', + equals: true, + description: 'Required. cup size', + disabled: false + }] end end @@ -165,12 +168,14 @@ path: ['orders'], query: [ { key: 'type', + value: '', equals: true, description: 'decaf or regular', disabled: true }, { key: 'size', + value: '', equals: true, description: 'Required. cup size', disabled: false From a6fda45d23816a512565fe4babc8704fbdc50452 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 20:13:10 -0800 Subject: [PATCH 23/24] Listing out params descriptions in request description with Markdown --- features/postman.feature | 4 ++-- .../views/postman_request_example.rb | 12 +++++++++++- spec/views/postman_example_spec.rb | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/features/postman.feature b/features/postman.feature index cbf164b2..0e360a27 100644 --- a/features/postman.feature +++ b/features/postman.feature @@ -109,7 +109,7 @@ Feature: Postman ], "variable": [] }, - "description": null + "description": "\n * `target`: The thing you want to greet\n * `type`: foo" }, "response": [] }, @@ -149,7 +149,7 @@ Feature: Postman ], "variable": [] }, - "description": null + "description": "\n * `target`: The thing you want to greet\n * `type`: foo" }, "response": [] } diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb index 752deb9b..2344447c 100644 --- a/lib/rspec_api_documentation/views/postman_request_example.rb +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -86,7 +86,7 @@ def as_json(options = nil) query: query_in_url, variable: variables_for_url }, - description: explanation + description: request_description }, response: [] } @@ -121,6 +121,16 @@ def build_urlencoded_body def format_description(description, required = false) required ? "Required. #{description}" : description end + + def request_description + text = explanation ? explanation : "" + if @example.respond_to?(:parameters) + @example.parameters.each do |param| + text = text + "\n * `#{param[:name]}`: #{param[:description]}" + end + end + text + end end end end \ No newline at end of file diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index 54d12eea..39908629 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -147,7 +147,7 @@ query: [], variable: [] }, - description: nil, + description: "", }, response: [] } @@ -182,7 +182,7 @@ }], variable: [] }, - description: nil, + description: "\n * `type`: decaf or regular\n * `size`: cup size", }, response: [] } From 4e8d152f9e7519ee4f3304f5fd460e75a41e6bd0 Mon Sep 17 00:00:00 2001 From: Makoto Furuya <> Date: Sun, 4 Mar 2018 20:50:34 -0800 Subject: [PATCH 24/24] Cleaning up PostmanRequestExample and moving methods. Added tests --- lib/rspec_api_documentation.rb | 1 + .../views/postman_request_example.rb | 119 ++--------------- .../views/postman_request_metadata.rb | 117 ++++++++++++++++ spec/views/postman_example_spec.rb | 84 ------------ spec/views/postman_request_metadata_spec.rb | 126 ++++++++++++++++++ 5 files changed, 253 insertions(+), 194 deletions(-) create mode 100644 lib/rspec_api_documentation/views/postman_request_metadata.rb create mode 100644 spec/views/postman_request_metadata_spec.rb diff --git a/lib/rspec_api_documentation.rb b/lib/rspec_api_documentation.rb index 2690b43e..f1b4412b 100644 --- a/lib/rspec_api_documentation.rb +++ b/lib/rspec_api_documentation.rb @@ -65,6 +65,7 @@ module Views autoload :ApiBlueprintExample autoload :PostmanIndex autoload :PostmanRequestExample + autoload :PostmanRequestMetadata end def self.configuration diff --git a/lib/rspec_api_documentation/views/postman_request_example.rb b/lib/rspec_api_documentation/views/postman_request_example.rb index 2344447c..1fdf1077 100644 --- a/lib/rspec_api_documentation/views/postman_request_example.rb +++ b/lib/rspec_api_documentation/views/postman_request_example.rb @@ -1,76 +1,15 @@ module RspecApiDocumentation module Views class PostmanRequestExample - attr_reader :metadata + attr_reader :example, :metadata def initialize(example) @example = example - @metadata = requests.first + @metadata = Views::PostmanRequestMetadata.new(example) end def method_missing(method, *args, &block) - @example.send(method, *args, &block) - end - - def query_in_url - query_params = [] - - if metadata[:request_query_parameters] && @example.respond_to?(:parameters) - metadata[:request_query_parameters].map do |k, v| - documented_param = @example.parameters.select { |p| p[:name] == k.to_s }.try(:first) - if documented_param - query_params << { key: k.to_s, - value: '', - equals: true, - description: format_description(documented_param[:description], - documented_param[:required]), - disabled: !documented_param[:required] - } - end - end - end - - query_params - end - - def content_type - { key: 'Content-Type', value: metadata[:request_headers]['Content-Type'] } - end - - def body - return {} unless metadata[:request_body] - - if content_type[:value] == 'application/w-www-form-urlencoded' - { mode: 'urlencoded', urlencoded: build_urlencoded_body } - elsif content_type[:value] == 'application/octet-stream' - { mode: 'file', file: {} } - else - { mode: 'raw', raw: metadata[:request_body] } - end - end - - def variables_for_url - route_variables = tokenized_path.select { |r| r.start_with?(':') } - return [] unless route_variables - - variables = [] - route_variables.each do |rv| - param_name = rv.split(':')[1] - param_from_example = @example.parameters.select { |e| e[:name] == param_name }.try(:first) - if param_from_example - variable = { - key: param_name, - value: '', - description: format_description(param_from_example[:description], - param_from_example[:required]), - disabled: !param_from_example[:required] - - } - variables << variable - end - end - - variables + example.send(method, *args, &block) end def as_json(options = nil) @@ -78,59 +17,19 @@ def as_json(options = nil) name: description, request: { method: http_method, - header: [content_type], - body: body, + header: [metadata.content_type], + body: metadata.body, url: { host: ['{{application_url}}'], - path: tokenized_path, - query: query_in_url, - variable: variables_for_url + path: metadata.tokenized_path, + query: metadata.query_in_url, + variable: metadata.variables_for_url }, - description: request_description + description: metadata.request_description }, response: [] } end - - private - - def tokenized_path - route.split('/').reject { |p| p.empty? } - end - - def build_urlencoded_body - urlencoded_params = [] - params = CGI::parse(metadata[:request_body]) - params.each do |p| - param_from_example = @example.parameters.select{ |e| e[:name] == p.first }.try(:first) - if param_from_example - urlencoded_param = { - key: p.first, - value: '', - description: format_description(param_from_example[:description], - param_from_example[:required]), - type: 'text', - disabled: !param_from_example[:required] - } - urlencoded_params << urlencoded_param - end - end - urlencoded_params - end - - def format_description(description, required = false) - required ? "Required. #{description}" : description - end - - def request_description - text = explanation ? explanation : "" - if @example.respond_to?(:parameters) - @example.parameters.each do |param| - text = text + "\n * `#{param[:name]}`: #{param[:description]}" - end - end - text - end end end end \ No newline at end of file diff --git a/lib/rspec_api_documentation/views/postman_request_metadata.rb b/lib/rspec_api_documentation/views/postman_request_metadata.rb new file mode 100644 index 00000000..a93022a2 --- /dev/null +++ b/lib/rspec_api_documentation/views/postman_request_metadata.rb @@ -0,0 +1,117 @@ +module RspecApiDocumentation + module Views + class PostmanRequestMetadata + attr_reader :example, :metadata + + def initialize(example) + @example = example + @metadata = requests.first + end + + def method_missing(method, *args, &block) + example.send(method, *args, &block) + end + + def query_in_url + query_params = [] + + if metadata[:request_query_parameters] && example.respond_to?(:parameters) + metadata[:request_query_parameters].map do |k, v| + documented_param = example.parameters.select { |p| p[:name] == k.to_s }.try(:first) + if documented_param + query_params << { key: k.to_s, + value: '', + equals: true, + description: format_description(documented_param[:description], + documented_param[:required]), + disabled: !documented_param[:required] + } + end + end + end + + query_params + end + + def content_type + { key: 'Content-Type', value: metadata[:request_headers]['Content-Type'] } + end + + def body + return {} unless metadata[:request_body] + + if content_type[:value] == 'application/w-www-form-urlencoded' + { mode: 'urlencoded', urlencoded: build_urlencoded_body } + elsif content_type[:value] == 'application/octet-stream' + { mode: 'file', file: {} } + else + { mode: 'raw', raw: metadata[:request_body] } + end + end + + def variables_for_url + route_variables = tokenized_path.select { |r| r.start_with?(':') } + return [] unless route_variables + + variables = [] + route_variables.each do |rv| + param_name = rv.split(':')[1] + param_from_example = example.parameters.select { |e| e[:name] == param_name }.try(:first) + if param_from_example + variable = { + key: param_name, + value: '', + description: format_description(param_from_example[:description], + param_from_example[:required]), + disabled: !param_from_example[:required] + + } + variables << variable + end + end + + variables + end + + def request_description + text = explanation ? explanation : "" + if example.respond_to?(:parameters) + example.parameters.each do |param| + text = text + "\n * `#{param[:name]}`: #{param[:description]}" + end + end + text + end + + def tokenized_path + route.split('/').reject { |p| p.empty? } + end + + private + + def build_urlencoded_body + urlencoded_params = [] + params = CGI::parse(metadata[:request_body]) + params.each do |p| + param_from_example = example.parameters.select{ |e| e[:name] == p.first }.try(:first) + if param_from_example + urlencoded_param = { + key: p.first, + value: '', + description: format_description(param_from_example[:description], + param_from_example[:required]), + type: 'text', + disabled: !param_from_example[:required] + } + urlencoded_params << urlencoded_param + end + end + urlencoded_params + end + + def format_description(description, required = false) + required ? "Required. #{description}" : description + end + end + end +end diff --git a/spec/views/postman_example_spec.rb b/spec/views/postman_example_spec.rb index 39908629..6ed54cd9 100644 --- a/spec/views/postman_example_spec.rb +++ b/spec/views/postman_example_spec.rb @@ -40,90 +40,6 @@ subject(:view) { postman_example } - describe '#query_in_url' do - it 'populates parameters' do - expect(subject.query_in_url).to eq [{ - key: 'type', - value: '', - equals: true, - description: 'decaf or regular', - disabled: true - }, - { - key: 'size', - value: '', - equals: true, - description: 'Required. cup size', - disabled: false - }] - end - end - - describe '#content_type' do - it 'parses content type' do - expect(subject.content_type).to eq({ key: 'Content-Type', value: 'application/json' }) - end - end - - describe '#body' do - context 'when content type includes application/json' do - let(:body_content) { "{ \"customer_name\": \"FooBar\" }" } - - it 'returns raw mode hash' do - expect(subject.body).to eq({ mode: 'raw', raw: body_content }) - end - end - - context 'when content type is w-www-form-urlencoded' do - let(:content_type) { 'application/w-www-form-urlencoded' } - let(:body_content) { "type=decaf&size=regular"} - - it 'returns urlencoded hash' do - expect(subject.body).to eq({ mode: 'urlencoded', - urlencoded: [ - { - key: 'type', - value: '', - description: 'decaf or regular', - type: 'text', - disabled: true - }, - { - key: 'size', - value: '', - description: 'Required. cup size', - type: 'text', - disabled: false - }] - }) - end - end - end - - describe '#variables_for_url' do - context 'when route has a variable' do - let(:metadata) do - { resource_name: 'Orders', - parameters: - [ - { name: 'id', required: true, description: 'Order ID' }, - { name: 'type', required: false, description: 'decaf or regular' }, - { name: 'size', required: true, description: 'cup size' } - ], - route: '/orders/:id', - method: 'get' - } - end - - it 'can populate variable for url' do - expect(subject.variables_for_url).to eq([{ key: 'id', - value: '', - description: 'Required. Order ID', - disabled: false }]) - end - end - end - describe '#as_json' do context 'when the example is for POST' do let(:metadata) do diff --git a/spec/views/postman_request_metadata_spec.rb b/spec/views/postman_request_metadata_spec.rb new file mode 100644 index 00000000..d196a0a0 --- /dev/null +++ b/spec/views/postman_request_metadata_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +describe RspecApiDocumentation::Views::PostmanRequestMetadata do + let(:metadata) do + { resource_name: 'Orders', + parameters: + [ + { name: 'type', required: false, description: 'decaf or regular' }, + { name: 'size', required: true, description: 'cup size' } + ], + route: '/orders', + method: 'get' + } + end + let(:group) { RSpec::Core::ExampleGroup.describe('', metadata) } + let(:description) { 'Ordering a cup of coffee' } + let(:rspec_example) { group.example(description) {} } + let(:rad_example) do + RspecApiDocumentation::Example.new(rspec_example, configuration) + end + let(:configuration) { RspecApiDocumentation::Configuration.new } + let(:postman_metadata) { described_class.new(rad_example) } + + let(:content_type) { 'application/json' } + let(:body_content) { '{}' } + let(:requests) do + [{ + request_body: body_content, + request_headers: { + "Content-Type" => content_type + }, + request_content_type: "", + request_query_parameters: { type: 'decaf', size: 'tall' } + }] + end + + before do + rspec_example.metadata[:requests] = requests + end + + subject(:view_helper) { postman_metadata } + + describe '#query_in_url' do + it 'populates parameters' do + expect(subject.query_in_url).to eq [{ + key: 'type', + value: '', + equals: true, + description: 'decaf or regular', + disabled: true + }, + { + key: 'size', + value: '', + equals: true, + description: 'Required. cup size', + disabled: false + }] + end + end + + describe '#content_type' do + it 'parses content type' do + expect(subject.content_type).to eq({ key: 'Content-Type', value: 'application/json' }) + end + end + + describe '#body' do + context 'when content type includes application/json' do + let(:body_content) { "{ \"customer_name\": \"FooBar\" }" } + + it 'returns raw mode hash' do + expect(subject.body).to eq({ mode: 'raw', raw: body_content }) + end + end + + context 'when content type is w-www-form-urlencoded' do + let(:content_type) { 'application/w-www-form-urlencoded' } + let(:body_content) { "type=decaf&size=regular"} + + it 'returns urlencoded hash' do + expect(subject.body).to eq({ mode: 'urlencoded', + urlencoded: [ + { + key: 'type', + value: '', + description: 'decaf or regular', + type: 'text', + disabled: true + }, + { + key: 'size', + value: '', + description: 'Required. cup size', + type: 'text', + disabled: false + }] + }) + end + end + end + + describe '#variables_for_url' do + context 'when route has a variable' do + let(:metadata) do + { resource_name: 'Orders', + parameters: + [ + { name: 'id', required: true, description: 'Order ID' }, + { name: 'type', required: false, description: 'decaf or regular' }, + { name: 'size', required: true, description: 'cup size' } + ], + route: '/orders/:id', + method: 'get' + } + end + + it 'can populate variable for url' do + expect(subject.variables_for_url).to eq([{ key: 'id', + value: '', + description: 'Required. Order ID', + disabled: false }]) + end + end + end +end