diff --git a/rswag-specs/lib/rswag/specs/request_factory.rb b/rswag-specs/lib/rswag/specs/request_factory.rb index dcb18db..20e499f 100644 --- a/rswag-specs/lib/rswag/specs/request_factory.rb +++ b/rswag-specs/lib/rswag/specs/request_factory.rb @@ -138,15 +138,21 @@ module Rswag end end + def build_form_payload(parameters, example) + #See http://seejohncode.com/2012/04/29/quick-tip-testing-multipart-uploads-with-rspec/ + # Rather that serializing with the appropriate encoding (e.g. multipart/form-data), + # Rails test infrastructure allows us to send the values directly as a hash + # PROS: simple to implement, CONS: serialization/deserialization is bypassed in test + tuples = parameters + .select { |p| p[:in] == :formData } + .map { |p| [ p[:name], example.send(p[:name]) ] } + Hash[ tuples ] + end + def build_json_payload(parameters, example) body_param = parameters.select { |p| p[:in] == :body }.first body_param ? example.send(body_param[:name]).to_json : nil end - - def build_form_payload(parameters, example) - nil - # TODO: - end end end end diff --git a/rswag-specs/spec/rswag/specs/request_factory_spec.rb b/rswag-specs/spec/rswag/specs/request_factory_spec.rb index a22b0d4..d906644 100644 --- a/rswag-specs/spec/rswag/specs/request_factory_spec.rb +++ b/rswag-specs/spec/rswag/specs/request_factory_spec.rb @@ -158,6 +158,25 @@ module Rswag expect(request[:payload]).to eq("{\"text\":\"Some comment\"}") end end + + context 'form payload' do + before do + metadata[:operation][:consumes] = [ 'multipart/form-data' ] + metadata[:operation][:parameters] = [ + { name: 'f1', in: :formData, type: :string }, + { name: 'f2', in: :formData, type: :string } + ] + allow(example).to receive(:f1).and_return('foo blah') + allow(example).to receive(:f2).and_return('bar blah') + end + + it 'sets payload to hash of names and example values' do + expect(request[:payload]).to eq( + 'f1' => 'foo blah', + 'f2' => 'bar blah' + ) + end + end end context 'produces content' do diff --git a/test-app/app/controllers/blogs_controller.rb b/test-app/app/controllers/blogs_controller.rb index 765576b..dc732ed 100644 --- a/test-app/app/controllers/blogs_controller.rb +++ b/test-app/app/controllers/blogs_controller.rb @@ -1,3 +1,5 @@ +require 'fileutils' + class BlogsController < ApplicationController # POST /blogs @@ -6,6 +8,14 @@ class BlogsController < ApplicationController respond_with @blog end + # Put /blogs/1 + def upload + @blog = Blog.find_by_id(params[:id]) + return head :not_found if @blog.nil? + @blog.thumbnail = save_uploaded_file params[:file] + head @blog.save ? :ok : :unprocsessible_entity + end + # GET /blogs def index @blogs = Blog.all @@ -22,4 +32,13 @@ class BlogsController < ApplicationController respond_with @blog, status: :not_found and return unless @blog respond_with @blog end + + private + + def save_uploaded_file(field) + return if field.nil? + file = File.join('tmp', field.original_filename) + FileUtils.cp field.tempfile.path, file + field.original_filename + end end diff --git a/test-app/app/models/blog.rb b/test-app/app/models/blog.rb index e913dba..9fb7070 100644 --- a/test-app/app/models/blog.rb +++ b/test-app/app/models/blog.rb @@ -5,7 +5,8 @@ class Blog < ActiveRecord::Base { id: id, title: title, - content: nil + content: nil, + thumbnail: thumbnail } end end diff --git a/test-app/config/routes.rb b/test-app/config/routes.rb index 2974a48..dff0779 100644 --- a/test-app/config/routes.rb +++ b/test-app/config/routes.rb @@ -1,5 +1,6 @@ TestApp::Application.routes.draw do resources :blogs + put '/blogs/:id/upload', to: 'blogs#upload' post 'auth-tests/basic', to: 'auth_tests#basic' diff --git a/test-app/db/migrate/20160218212104_create_blogs.rb b/test-app/db/migrate/20160218212104_create_blogs.rb index 03e1463..9dd862b 100644 --- a/test-app/db/migrate/20160218212104_create_blogs.rb +++ b/test-app/db/migrate/20160218212104_create_blogs.rb @@ -9,6 +9,7 @@ class CreateBlogs < migration_class create_table :blogs do |t| t.string :title t.text :content + t.string :thumbnail t.timestamps end diff --git a/test-app/db/schema.rb b/test-app/db/schema.rb index 4e72efd..8accba1 100644 --- a/test-app/db/schema.rb +++ b/test-app/db/schema.rb @@ -15,6 +15,7 @@ ActiveRecord::Schema.define(version: 20160218212104) do create_table "blogs", force: :cascade do |t| t.string "title" t.text "content" + t.string "thumbnail" t.datetime "created_at" t.datetime "updated_at" end diff --git a/test-app/spec/fixtures/thumbnail.png b/test-app/spec/fixtures/thumbnail.png new file mode 100644 index 0000000..5790d6c Binary files /dev/null and b/test-app/spec/fixtures/thumbnail.png differ diff --git a/test-app/spec/integration/blogs_spec.rb b/test-app/spec/integration/blogs_spec.rb index 1949fa9..143dc39 100644 --- a/test-app/spec/integration/blogs_spec.rb +++ b/test-app/spec/integration/blogs_spec.rb @@ -10,7 +10,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do operationId 'createBlog' consumes 'application/json' produces 'application/json' - parameter name: :blog, :in => :body, schema: { '$ref' => '#/definitions/blog' } + parameter name: :blog, in: :body, schema: { '$ref' => '#/definitions/blog' } let(:blog) { { title: 'foo', content: 'bar' } } @@ -24,11 +24,6 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do let(:blog) { { title: 'foo' } } run_test! end - - response '406', 'unsupported accept header' do - let(:'Accept') { 'application/foo' } - run_test! - end end get 'Searches blogs' do @@ -53,10 +48,10 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do end path '/blogs/{id}' do - parameter name: :id, :in => :path, :type => :string + parameter name: :id, in: :path, type: :string let(:id) { blog.id } - let(:blog) { Blog.create(title: 'foo', content: 'bar') } + let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') } get 'Retrieves a blog' do tags 'Blogs' @@ -74,9 +69,11 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do examples 'application/json' => { id: 1, title: 'Hello world!', - content: 'Hello world and hello universe. Thank you all very much!!!' + content: 'Hello world and hello universe. Thank you all very much!!!', + thumbnail: "thumbnail.png" } + let(:id) { blog.id } run_test! end @@ -84,9 +81,24 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do let(:id) { 'invalid' } run_test! end + end + end - response '406', 'unsupported accept header' do - let(:'Accept') { 'application/foo' } + path '/blogs/{id}/upload' do + parameter name: :id, in: :path, type: :string + + let(:id) { blog.id } + let(:blog) { Blog.create(title: 'foo', content: 'bar') } + + put 'upload a blog thumbnail' do + tags 'Blogs' + description 'Upload a thumbnail for specific blog by id' + operationId 'uploadThumbnailBlog' + consumes 'multipart/form-data' + parameter name: :file, :in => :formData, :type => :file, required: true + + response '200', 'blog updated' do + let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) } run_test! end end diff --git a/test-app/spec/spec_helper.rb b/test-app/spec/spec_helper.rb index 8f698be..d20f071 100644 --- a/test-app/spec/spec_helper.rb +++ b/test-app/spec/spec_helper.rb @@ -47,6 +47,10 @@ RSpec.configure do |config| # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups + config.after(:suite) do + File.delete("#{Rails.root}/tmp/thumbnail.png") if File.file?("#{Rails.root}/tmp/thumbnail.png") + end + # The settings below are suggested to provide a good initial experience # with RSpec, but feel free to customize to your heart's content. =begin diff --git a/test-app/spec/swagger_helper.rb b/test-app/spec/swagger_helper.rb index 91d506c..1575a58 100644 --- a/test-app/spec/swagger_helper.rb +++ b/test-app/spec/swagger_helper.rb @@ -39,9 +39,10 @@ RSpec.configure do |config| properties: { id: { type: 'integer' }, title: { type: 'string' }, - content: { type: 'string', 'x-nullable': true } + content: { type: 'string', 'x-nullable': true }, + thumbnail: { type: 'string'} }, - required: [ 'id', 'title', 'content' ] + required: [ 'id', 'title', 'content', 'thumbnail' ] } }, securityDefinitions: { diff --git a/test-app/swagger/v1/swagger.json b/test-app/swagger/v1/swagger.json index 60f3ca8..27703bf 100644 --- a/test-app/swagger/v1/swagger.json +++ b/test-app/swagger/v1/swagger.json @@ -61,9 +61,6 @@ "schema": { "$ref": "#/definitions/errors_object" } - }, - "406": { - "description": "unsupported accept header" } } }, @@ -140,15 +137,47 @@ "application/json": { "id": 1, "title": "Hello world!", - "content": "Hello world and hello universe. Thank you all very much!!!" + "content": "Hello world and hello universe. Thank you all very much!!!", + "thumbnail": "thumbnail.png" } } }, "404": { "description": "blog not found" - }, - "406": { - "description": "unsupported accept header" + } + } + } + }, + "/blogs/{id}/upload": { + "parameters": [ + { + "name": "id", + "in": "path", + "type": "string", + "required": true + } + ], + "put": { + "summary": "upload a blog thumbnail", + "tags": [ + "Blogs" + ], + "description": "Upload a thumbnail for specific blog by id", + "operationId": "uploadThumbnailBlog", + "consumes": [ + "multipart/form-data" + ], + "parameters": [ + { + "name": "file", + "in": "formData", + "type": "file", + "required": true + } + ], + "responses": { + "200": { + "description": "blog updated" } } } @@ -184,12 +213,16 @@ "content": { "type": "string", "x-nullable": true + }, + "thumbnail": { + "type": "string" } }, "required": [ "id", "title", - "content" + "content", + "thumbnail" ] } },