require 'swagger_helper' require 'rswag/specs/swagger_formatter' # This spec file validates OpenAPI output generated by spec metadata. # Specifically here, we look at OpenApi 3 as documented at # https://swagger.io/docs/specification/about/ RSpec.describe 'Generated OpenApi', type: :request, swagger_doc: 'v3/openapi.json' do before do |example| output = double('output').as_null_object swagger_root = File.expand_path('tmp/swagger', __dir__) config = double('config', swagger_root: swagger_root, get_swagger_doc: swagger_doc ) formatter = Rswag::Specs::SwaggerFormatter.new(output, config) example_group = OpenStruct.new(group: OpenStruct.new(metadata: example.metadata)) formatter.example_group_finished(example_group) end # Framework definition, to be overridden for contexts let(:swagger_doc) do { # That which would be defined in swagger_helper.rb openapi: api_openapi, info: {}, servers: api_servers, produces: api_produces, components: api_components } end let(:api_openapi) { '3.0.3' } let(:api_servers) {[{ url: "https://api.example.com/foo" }]} let(:api_produces) { ['application/json'] } let(:api_components) { {} } describe 'Basic Structure' describe 'API Server and Base Path' do path '/stubs' do get 'a summary' do tags 'Server and Path' response '200', 'OK' do run_test! it 'lists server' do tree = swagger_doc.dig(:servers) expect(tree).to eq([ { url: "https://api.example.com/foo" } ]) end context "multiple" do let(:api_servers) {[ { url: "https://api.example.com/foo" }, { url: "http://api.example.com/foo" }, ]} it 'lists servers' do tree = swagger_doc.dig(:servers) expect(tree).to eq([ { url: "https://api.example.com/foo" }, { url: "http://api.example.com/foo" } ]) end end context "with variables" do let(:api_servers) {[{ url: "https://{defaultHost}/foo", variables: { defaultHost: { default: "api.example.com" } } }]} it 'lists server and variables' do tree = swagger_doc.dig(:servers) expect(tree).to eq([{ url: "https://{defaultHost}/foo", variables: { defaultHost: { default: "api.example.com" } } }]) end end # TODO: Enum variables, defaults, override at path/operation end end end end describe 'Media Types' do path '/stubs' do get 'a summary' do tags 'Media Types' response '200', 'OK' do run_test! it 'declares output as application/json' do pending "Not yet implemented?" tree = swagger_doc.dig(:paths, "/stubs", :get, :responses, '200', :content) expect(tree).to have_key('application/json') end end end end end describe 'Paths and Operations' describe 'Parameter Serialization' do describe 'Path Parameters' do path '/stubs/{a_param}' do get 'a summary' do tags 'Parameter Serialization: Query String' produces 'application/json' parameter( name: 'a_param', in: :path, ) let(:a_param) { "42" } response '200', 'OK' do run_test! it 'declares parameter in path' do tree = swagger_doc.dig(:paths, "/stubs/{a_param}", :get, :parameters) expect(tree.first[:name]).to eq('a_param') expect(tree.first[:in]).to eq(:path) end it 'declares path parameters as required' do tree = swagger_doc.dig(:paths, "/stubs/{a_param}", :get, :parameters) expect(tree.first[:required]).to eq(true) end end end end end describe 'Query Parameters' do path '/stubs' do get 'a summary' do tags 'Parameter Serialization: Query String' produces 'application/json' parameter( name: 'a_param', in: :query, ) let(:a_param) { "a foo" } response '200', 'OK' do run_test! it 'declares parameter in query string' do tree = swagger_doc.dig(:paths, "/stubs", :get, :parameters) expect(tree.first[:name]).to eq('a_param') expect(tree.first[:in]).to eq(:query) end end # TODO: Serialization (form/spaceDelimited/pipeDelimited/deepObject) end end end # TODO: Header # TODO: Cookie # TODO: Default values # TODO: Enum # TODO: Constant # TODO: Empty/Nullable # TODO: Examples # TODO: Deprecated # TODO: Common Parameters end describe 'Request Body' do path '/stubs' do post 'body is required' do tags 'Media Types' consumes 'multipart/form-data' parameter name: :file, :in => :formData, :type => :file, required: true let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) } response '200', 'OK' do run_test! it 'declares requestBody is required' do pending "This output is massaged in SwaggerFormatter#stop, and isn't quite ready here to assert" tree = swagger_doc.dig(:paths, "/stubs", :post, :requestBody) expect(tree[:required]).to eq(true) end end end end end describe 'Responses' describe 'Data Models (Schemas)' describe 'Examples' describe 'Authentication' describe 'Links' describe 'Callbacks' describe 'Components Section' describe 'Using $ref' describe 'Grouping Operations with Tags' # path '/blogs' do # post 'Creates a blog' do # tags 'Blogs' # description 'Creates a new blog from provided data' # operationId 'createBlog' # consumes 'application/json' # produces 'application/json' # parameter name: :blog, in: :body, schema: { '$ref' => '#/definitions/blog' } # let(:blog) { { title: 'foo', content: 'bar' } } # response '201', 'blog created' do # # schema '$ref' => '#/definitions/blog' # run_test! # end # response '422', 'invalid request' do # schema '$ref' => '#/definitions/errors_object' # let(:blog) { { title: 'foo' } } # run_test! do |response| # expect(response.body).to include("can't be blank") # end # it 'outputs parameters' do # pp swagger_doc # params = swagger_doc.dig(:paths, "/blogs", :post, :parameters) # expect(params[0][:name]).to eq(:blog) # end # end # end # get 'Searches blogs' do # tags 'Blogs' # description 'Searches blogs by keywords' # operationId 'searchBlogs' # produces 'application/json' # parameter name: :keywords, in: :query, type: 'string' # let(:keywords) { 'foo bar' } # response '200', 'success' do # schema type: 'array', items: { '$ref' => '#/definitions/blog' } # end # response '406', 'unsupported accept header' do # let(:'Accept') { 'application/foo' } # run_test! # end # end # end # path '/blogs/flexible' do # post 'Creates a blog flexible body' do # tags 'Blogs' # description 'Creates a flexible blog from provided data' # operationId 'createFlexibleBlog' # consumes 'application/json' # produces 'application/json' # parameter name: :flexible_blog, in: :body, schema: { # oneOf: [ # { '$ref' => '#/definitions/blog' }, # { '$ref' => '#/definitions/flexible_blog' } # ] # } # let(:flexible_blog) { { blog: { headline: 'my headline', text: 'my text' } } } # response '201', 'flexible blog created' do # schema oneOf: [{ '$ref' => '#/definitions/blog' }, { '$ref' => '#/definitions/flexible_blog' }] # run_test! # end # end # end # path '/blogs/{id}' do # parameter name: :id, in: :path, type: :string # let(:id) { blog.id } # let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') } # get 'Retrieves a blog' do # tags 'Blogs' # description 'Retrieves a specific blog by id' # operationId 'getBlog' # produces 'application/json' # response '200', 'blog found' do # header 'ETag', type: :string # header 'Last-Modified', type: :string # header 'Cache-Control', type: :string # schema '$ref' => '#/definitions/blog' # examples 'application/json' => { # id: 1, # title: 'Hello world!', # content: 'Hello world and hello universe. Thank you all very much!!!', # thumbnail: 'thumbnail.png' # } # let(:id) { blog.id } # run_test! # end # response '404', 'blog not found' do # let(:id) { 'invalid' } # run_test! # end # end # end # 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 'Uploads 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 # end end