Skip to content

Commit

Permalink
Add support for multiple OpenAPI documents
Browse files Browse the repository at this point in the history
  • Loading branch information
skryukov committed Jan 12, 2024
1 parent def1e34 commit fa6618a
Show file tree
Hide file tree
Showing 21 changed files with 324 additions and 129 deletions.
14 changes: 10 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:

pull_request:

env:
CI: 1

jobs:
lint:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -50,10 +53,13 @@ jobs:
- name: Run RSpec
run: bundle exec rspec
- name: Run RSpec examples
run: CI=1 ruby examples/rspec.rb
run: ruby examples/rspec.rb
- name: Fix RubyGems activating older versions of gems
run: gem install timeout net-protocol stringio psych date
- name: Run RSpec Rails examples
run: CI=1 ruby examples/rspec_rails.rb
- name: Run minitest example
run: CI=1 ruby examples/minitest.rb
run: ruby examples/minitest.rb
- name: Run RSpec Rails examples
working-directory: ./examples/rails_app
run: |
bundle install
bundle exec rspec
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning].

## [Unreleased]

### Added

- Add support for multiple OpenAPI documents. ([@skryukov])

### Fixed

- Fix `Skooma::Error: Missing name key /request` by setting `content` and `required` keyword dependencies. ([@skryukov])
Expand Down
2 changes: 1 addition & 1 deletion examples/minitest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
include Skooma::Minitest[File.join(__dir__, "openapi.yml")]

def app
TestApp
TestApp["bar"]
end

it "is valid OpenAPI document" do
Expand Down
3 changes: 3 additions & 0 deletions examples/rails_app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/tmp/

Gemfile.lock
1 change: 1 addition & 0 deletions examples/rails_app/.rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--require rails_helper
9 changes: 9 additions & 0 deletions examples/rails_app/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

source "https://rubygems.org"

gem "rails"
gem "rspec"
gem "rspec-rails"
gem "skooma", (ENV["CI"] == "1") ? {path: File.join(__dir__, "..", "..")} : {}
gem "sinatra"
46 changes: 46 additions & 0 deletions examples/rails_app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# RSpec Rails Example

This is an example Rails app that uses Skooma with multiple openapi documents.

First, we need to define the OpenAPI documents we want to use:

```ruby
# rails_helper.rb
RSpec.configure do |config|
# You can use different RSpec filters if you want to test different API descriptions.
# Check RSpec's config.define_derived_metadata for better UX.
config.include Skooma::RSpec[bar_openapi, path_prefix: "/bar"], :bar_api
config.include Skooma::RSpec[baz_openapi, path_prefix: "/baz"], :baz_api
end
```

Next, we can write our specs and mark them with the appropriate RSpec filter:

```ruby
# spec/requests/bar/bar_spec.rb
describe "Bar API", :bar_api, type: :request do
describe "GET /bar" do
subject { get "/bar" }

it { is_expected.to conform_schema(200) }
end
end
```

To avoid having to specify the RSpec filter on every spec, you can use RSpec's `config.define_derived_metadata`:

```ruby
# rails_helper.rb
RSpec.configure do |config|
config.define_derived_metadata(file_path: %r{/spec/requests/bar}) do |metadata|
metadata[:bar_api] = true
end
end
```

## Running the example

```bash
bundle install
bundle exec rspec
```
20 changes: 20 additions & 0 deletions examples/rails_app/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

require "rails"
require "action_controller/railtie"
require "rails/test_unit/railtie"

require_relative "../test_app"

class RailsApp < Rails::Application
config.load_defaults Rails::VERSION::STRING.to_f
config.eager_load = false
config.logger = Logger.new(nil)

routes.append do
mount TestApp["bar"], at: "/bar", as: :bar_api
mount TestApp["baz"], at: "/baz", as: :baz_api
end
end

RailsApp.initialize!
5 changes: 5 additions & 0 deletions examples/rails_app/config.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This file is used by Rack-based servers to start the application.

require_relative "app"

run Rails.application
53 changes: 53 additions & 0 deletions examples/rails_app/docs/bar_openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
openapi: 3.1.0
info:
title: OpenAPI Sample
version: 1.0.0

paths:
"/":
get:
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Item"

post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
responses:
"201":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"

components:
schemas:
Item:
type: object
unevaluatedProperties: false
required: [foo]
properties:
foo:
type: string
enum: [bar]
Error:
type: object
unevaluatedProperties: false
required: [message]
properties:
message:
type: string
53 changes: 53 additions & 0 deletions examples/rails_app/docs/baz_openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
openapi: 3.1.0
info:
title: OpenAPI Sample
version: 1.0.0

paths:
"/":
get:
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Item"

post:
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
responses:
"201":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
"400":
description: Bad Request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"

components:
schemas:
Item:
type: object
unevaluatedProperties: false
required: [foo]
properties:
foo:
type: string
enum: [baz]
Error:
type: object
unevaluatedProperties: false
required: [message]
properties:
message:
type: string
21 changes: 21 additions & 0 deletions examples/rails_app/spec/rails_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
ENV["RAILS_ENV"] = "test"

require_relative "../app"

require "rspec/rails"
require "skooma"

RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups

bar_openapi = File.join(__dir__, "..", "docs", "bar_openapi.yml")
baz_openapi = File.join(__dir__, "..", "docs", "baz_openapi.yml")

# You can use different RSpec filters if you want to test different API descriptions.
# Check RSpec's config.define_derived_metadata for better UX.
config.include Skooma::RSpec[bar_openapi, path_prefix: "/bar"], :bar_api
config.include Skooma::RSpec[baz_openapi, path_prefix: "/baz"], :baz_api
end
28 changes: 28 additions & 0 deletions examples/rails_app/spec/requests/bar/bar_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require "rails_helper"

describe "Bar API", :bar_api, type: :request do
describe "GET /bar" do
subject { get "/bar" }

it { is_expected.to conform_schema(200) }

it "returns correct response" do
subject
expect(response.parsed_body).to eq({"foo" => "bar"})
end
end

describe "POST /bar" do
subject { post("/bar", params: body, as: :json) }

let(:body) { {foo: "bar"} }

it { is_expected.to conform_schema(201) }

context "with invalid params" do
let(:body) { {foo: "baz"} }

it { is_expected.to conform_response_schema(400) }
end
end
end
28 changes: 28 additions & 0 deletions examples/rails_app/spec/requests/baz/baz_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require "rails_helper"

describe "Baz API", :baz_api, type: :request do
describe "GET prefixed /baz" do
subject { get "/baz" }

it { is_expected.to conform_schema(200) }

it "returns correct response" do
subject
expect(response.parsed_body).to eq({"foo" => "baz"})
end
end

describe "POST prefixed /baz" do
subject { post("/baz", params: body, as: :json) }

let(:body) { {foo: "baz"} }

it { is_expected.to conform_schema(201) }

context "with invalid params" do
let(:body) { {foo: "bar"} }

it { is_expected.to conform_response_schema(400) }
end
end
end
15 changes: 15 additions & 0 deletions examples/rails_app/spec/requests/openapi_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require "rails_helper"

describe "OpenAPI documents", type: :request do
describe "Bar API", :bar_api do
subject(:schema) { skooma_openapi_schema }

it { is_expected.to be_valid_document }
end

describe "Baz API", :baz_api do
subject(:schema) { skooma_openapi_schema }

it { is_expected.to be_valid_document }
end
end
2 changes: 1 addition & 1 deletion examples/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

describe TestApp, type: :request do
def app
TestApp
TestApp["bar"]
end

describe "OpenAPI document", type: :request do
Expand Down

0 comments on commit fa6618a

Please sign in to comment.