+
+ 404
+The page you were looking for doesn’t exist.
+From c426320e6716312d2dab8e360b24a6d318fc6b61 Mon Sep 17 00:00:00 2001 From: Alexey Matskevich <45947106+AlexeyMatskevich@users.noreply.github.com> Date: Mon, 1 Apr 2024 19:11:23 +0500 Subject: [PATCH] Hanami support (#202) --- .rubocop.yml | 2 +- Gemfile | 9 + lib/rspec/openapi/record_builder.rb | 5 +- lib/rspec/openapi/schema_merger.rb | 9 +- rspec-openapi.gemspec | 1 + spec/apps/hanami/.gitignore | 2 + spec/apps/hanami/Procfile.dev | 1 + spec/apps/hanami/Rakefile | 3 + spec/apps/hanami/app/action.rb | 8 + .../images => apps/hanami/app/actions}/.keep | 0 .../hanami/app/actions/extensions/create.rb | 13 + .../hanami/app/actions/extensions/index.rb | 13 + spec/apps/hanami/app/actions/images/index.rb | 22 + spec/apps/hanami/app/actions/images/show.rb | 23 + spec/apps/hanami/app/actions/images/upload.rb | 25 + .../app/actions/images/upload_multiple.rb | 25 + .../actions/images/upload_multiple_nested.rb | 23 + .../app/actions/images/upload_nested.rb | 23 + .../hanami/app/actions/secret_items/index.rb | 15 + spec/apps/hanami/app/actions/tables/create.rb | 21 + .../apps/hanami/app/actions/tables/destroy.rb | 18 + spec/apps/hanami/app/actions/tables/index.rb | 21 + spec/apps/hanami/app/actions/tables/show.rb | 15 + .../hanami/app/actions/tables/table_action.rb | 30 + .../app/actions/tables/table_repository.rb | 51 + spec/apps/hanami/app/actions/tables/update.rb | 14 + spec/apps/hanami/app/actions/users/active.rb | 15 + spec/apps/hanami/app/actions/users/create.rb | 26 + spec/apps/hanami/app/actions/users/show.rb | 15 + .../hanami/app/actions/users/user_action.rb | 22 + .../app/actions/users/user_repository.rb | 32 + spec/apps/hanami/bin/dev | 8 + spec/apps/hanami/config.ru | 5 + spec/apps/hanami/config/app.rb | 9 + spec/apps/hanami/config/puma.rb | 47 + spec/apps/hanami/config/routes.rb | 39 + spec/apps/hanami/config/settings.rb | 9 + spec/apps/hanami/doc/openapi.json | 1007 +++++++++++++++++ spec/apps/hanami/doc/openapi.yaml | 665 +++++++++++ spec/apps/hanami/lib/hanami_test/types.rb | 11 + .../concerns => apps/hanami/lib/tasks}/.keep | 0 spec/apps/hanami/public/404.html | 82 ++ spec/apps/hanami/public/500.html | 82 ++ spec/apps/hanami/slices/my_engine/action.rb | 7 + .../hanami/slices/my_engine/actions}/.keep | 0 .../slices/my_engine/actions/eng/example.rb | 14 + spec/apps/hanami/test.png | Bin 0 -> 71 bytes spec/{ => apps}/rails/.gitignore | 2 +- spec/{ => apps}/rails/Rakefile | 0 .../rails/app/assets/config/manifest.js | 0 .../rails/app/assets/images}/.keep | 0 .../app/assets/stylesheets/application.css | 0 .../app/channels/application_cable/channel.rb | 0 .../channels/application_cable/connection.rb | 0 .../additional_properties_controller.rb | 0 .../app/controllers/application_controller.rb | 0 .../rails/app/controllers/concerns}/.keep | 0 .../app/controllers/images_controller.rb | 0 .../masters/extensions_controller.rb | 0 .../rails/app/controllers/pages_controller.rb | 0 .../controllers/secret_items_controller.rb | 0 .../app/controllers/tables_controller.rb | 0 .../rails/app/controllers/users_controller.rb | 3 +- .../rails/app/helpers/application_helper.rb | 0 .../rails/app/javascript/channels/consumer.js | 0 .../rails/app/javascript/channels/index.js | 0 .../rails/app/javascript/packs/application.js | 0 .../rails/app/jobs/application_job.rb | 0 .../rails/app/mailers/application_mailer.rb | 0 .../rails/app/models/application_record.rb | 0 .../rails/app/models/concerns}/.keep | 0 .../app/views/layouts/application.html.erb | 0 .../rails/app/views/layouts/mailer.html.erb | 0 .../rails/app/views/layouts/mailer.text.erb | 0 spec/{ => apps}/rails/bin/rails | 0 spec/{ => apps}/rails/bin/rake | 0 spec/{ => apps}/rails/bin/setup | 0 spec/{ => apps}/rails/bin/yarn | 0 spec/{ => apps}/rails/config.ru | 0 spec/{ => apps}/rails/config/application.rb | 0 spec/{ => apps}/rails/config/boot.rb | 0 spec/{ => apps}/rails/config/cable.yml | 0 .../rails/config/credentials.yml.enc | 0 spec/{ => apps}/rails/config/database.yml | 0 spec/{ => apps}/rails/config/environment.rb | 0 .../rails/config/environments/development.rb | 0 .../rails/config/environments/production.rb | 2 +- .../rails/config/environments/test.rb | 0 .../application_controller_renderer.rb | 0 .../initializers/backtrace_silencers.rb | 0 .../initializers/content_security_policy.rb | 0 .../config/initializers/cookies_serializer.rb | 0 .../initializers/filter_parameter_logging.rb | 0 .../rails/config/initializers/inflections.rb | 0 .../rails/config/initializers/mime_types.rb | 0 .../config/initializers/wrap_parameters.rb | 0 spec/{ => apps}/rails/config/locales/en.yml | 0 spec/{ => apps}/rails/config/puma.rb | 0 spec/{ => apps}/rails/config/routes.rb | 2 +- spec/{ => apps}/rails/config/spring.rb | 0 spec/{ => apps}/rails/config/storage.yml | 0 spec/{ => apps}/rails/db/seeds.rb | 0 spec/{ => apps}/rails/doc/openapi.json | 0 spec/{ => apps}/rails/doc/openapi.yaml | 0 spec/{ => apps}/rails/doc/screenshot.png | Bin spec/{ => apps}/rails/doc/smart/expected.yaml | 0 spec/{ => apps}/rails/doc/smart/openapi.yaml | 0 .../storage => apps/rails/lib/assets}/.keep | 0 .../{rails/tmp => apps/rails/lib/tasks}/.keep | 0 spec/{rails/tmp/pids => apps/rails/log}/.keep | 0 spec/{ => apps}/rails/package.json | 0 spec/{ => apps}/rails/public/404.html | 0 spec/{ => apps}/rails/public/422.html | 0 spec/{ => apps}/rails/public/500.html | 0 .../public/apple-touch-icon-precomposed.png | 0 .../rails/public/apple-touch-icon.png | 0 spec/{ => apps}/rails/public/favicon.ico | 0 spec/{ => apps}/rails/public/robots.txt | 0 .../vendor => apps/rails/storage}/.keep | 0 spec/apps/rails/tmp/.keep | 0 spec/apps/rails/tmp/pids/.keep | 0 spec/apps/rails/vendor/.keep | 0 .../rails/vendor/my_engine/config/routes.rb | 0 .../vendor/my_engine/lib/my_engine/engine.rb | 0 spec/{ => apps}/roda/doc/openapi.json | 0 spec/{ => apps}/roda/doc/openapi.yaml | 0 spec/{ => apps}/roda/doc/rspec_openapi.rb | 0 spec/{ => apps}/roda/roda_app.rb | 0 spec/integration_tests/rails_test.rb | 4 +- spec/integration_tests/roda_test.rb | 4 +- spec/minitest/rack_test_spec.rb | 8 +- spec/minitest/rails_spec.rb | 8 +- spec/requests/hanami_spec.rb | 284 +++++ spec/requests/rails_smart_merge_spec.rb | 4 +- spec/requests/rails_spec.rb | 4 +- spec/requests/roda_spec.rb | 4 +- spec/rspec/hanami_spec.rb | 38 + spec/rspec/rack_test_spec.rb | 8 +- spec/rspec/rails_spec.rb | 14 +- 139 files changed, 2839 insertions(+), 37 deletions(-) create mode 100644 spec/apps/hanami/.gitignore create mode 100644 spec/apps/hanami/Procfile.dev create mode 100644 spec/apps/hanami/Rakefile create mode 100644 spec/apps/hanami/app/action.rb rename spec/{rails/app/assets/images => apps/hanami/app/actions}/.keep (100%) create mode 100644 spec/apps/hanami/app/actions/extensions/create.rb create mode 100644 spec/apps/hanami/app/actions/extensions/index.rb create mode 100644 spec/apps/hanami/app/actions/images/index.rb create mode 100644 spec/apps/hanami/app/actions/images/show.rb create mode 100644 spec/apps/hanami/app/actions/images/upload.rb create mode 100644 spec/apps/hanami/app/actions/images/upload_multiple.rb create mode 100644 spec/apps/hanami/app/actions/images/upload_multiple_nested.rb create mode 100644 spec/apps/hanami/app/actions/images/upload_nested.rb create mode 100644 spec/apps/hanami/app/actions/secret_items/index.rb create mode 100644 spec/apps/hanami/app/actions/tables/create.rb create mode 100644 spec/apps/hanami/app/actions/tables/destroy.rb create mode 100644 spec/apps/hanami/app/actions/tables/index.rb create mode 100644 spec/apps/hanami/app/actions/tables/show.rb create mode 100644 spec/apps/hanami/app/actions/tables/table_action.rb create mode 100644 spec/apps/hanami/app/actions/tables/table_repository.rb create mode 100644 spec/apps/hanami/app/actions/tables/update.rb create mode 100644 spec/apps/hanami/app/actions/users/active.rb create mode 100644 spec/apps/hanami/app/actions/users/create.rb create mode 100644 spec/apps/hanami/app/actions/users/show.rb create mode 100644 spec/apps/hanami/app/actions/users/user_action.rb create mode 100644 spec/apps/hanami/app/actions/users/user_repository.rb create mode 100755 spec/apps/hanami/bin/dev create mode 100644 spec/apps/hanami/config.ru create mode 100644 spec/apps/hanami/config/app.rb create mode 100644 spec/apps/hanami/config/puma.rb create mode 100644 spec/apps/hanami/config/routes.rb create mode 100644 spec/apps/hanami/config/settings.rb create mode 100644 spec/apps/hanami/doc/openapi.json create mode 100644 spec/apps/hanami/doc/openapi.yaml create mode 100644 spec/apps/hanami/lib/hanami_test/types.rb rename spec/{rails/app/controllers/concerns => apps/hanami/lib/tasks}/.keep (100%) create mode 100644 spec/apps/hanami/public/404.html create mode 100644 spec/apps/hanami/public/500.html create mode 100644 spec/apps/hanami/slices/my_engine/action.rb rename spec/{rails/app/models/concerns => apps/hanami/slices/my_engine/actions}/.keep (100%) create mode 100644 spec/apps/hanami/slices/my_engine/actions/eng/example.rb create mode 100644 spec/apps/hanami/test.png rename spec/{ => apps}/rails/.gitignore (98%) rename spec/{ => apps}/rails/Rakefile (100%) rename spec/{ => apps}/rails/app/assets/config/manifest.js (100%) rename spec/{rails/lib/assets => apps/rails/app/assets/images}/.keep (100%) rename spec/{ => apps}/rails/app/assets/stylesheets/application.css (100%) rename spec/{ => apps}/rails/app/channels/application_cable/channel.rb (100%) rename spec/{ => apps}/rails/app/channels/application_cable/connection.rb (100%) rename spec/{ => apps}/rails/app/controllers/additional_properties_controller.rb (100%) rename spec/{ => apps}/rails/app/controllers/application_controller.rb (100%) rename spec/{rails/lib/tasks => apps/rails/app/controllers/concerns}/.keep (100%) rename spec/{ => apps}/rails/app/controllers/images_controller.rb (100%) rename spec/{ => apps}/rails/app/controllers/masters/extensions_controller.rb (100%) rename spec/{ => apps}/rails/app/controllers/pages_controller.rb (100%) rename spec/{ => apps}/rails/app/controllers/secret_items_controller.rb (100%) rename spec/{ => apps}/rails/app/controllers/tables_controller.rb (100%) rename spec/{ => apps}/rails/app/controllers/users_controller.rb (98%) rename spec/{ => apps}/rails/app/helpers/application_helper.rb (100%) rename spec/{ => apps}/rails/app/javascript/channels/consumer.js (100%) rename spec/{ => apps}/rails/app/javascript/channels/index.js (100%) rename spec/{ => apps}/rails/app/javascript/packs/application.js (100%) rename spec/{ => apps}/rails/app/jobs/application_job.rb (100%) rename spec/{ => apps}/rails/app/mailers/application_mailer.rb (100%) rename spec/{ => apps}/rails/app/models/application_record.rb (100%) rename spec/{rails/log => apps/rails/app/models/concerns}/.keep (100%) rename spec/{ => apps}/rails/app/views/layouts/application.html.erb (100%) rename spec/{ => apps}/rails/app/views/layouts/mailer.html.erb (100%) rename spec/{ => apps}/rails/app/views/layouts/mailer.text.erb (100%) rename spec/{ => apps}/rails/bin/rails (100%) rename spec/{ => apps}/rails/bin/rake (100%) rename spec/{ => apps}/rails/bin/setup (100%) rename spec/{ => apps}/rails/bin/yarn (100%) rename spec/{ => apps}/rails/config.ru (100%) rename spec/{ => apps}/rails/config/application.rb (100%) rename spec/{ => apps}/rails/config/boot.rb (100%) rename spec/{ => apps}/rails/config/cable.yml (100%) rename spec/{ => apps}/rails/config/credentials.yml.enc (100%) rename spec/{ => apps}/rails/config/database.yml (100%) rename spec/{ => apps}/rails/config/environment.rb (100%) rename spec/{ => apps}/rails/config/environments/development.rb (100%) rename spec/{ => apps}/rails/config/environments/production.rb (99%) rename spec/{ => apps}/rails/config/environments/test.rb (100%) rename spec/{ => apps}/rails/config/initializers/application_controller_renderer.rb (100%) rename spec/{ => apps}/rails/config/initializers/backtrace_silencers.rb (100%) rename spec/{ => apps}/rails/config/initializers/content_security_policy.rb (100%) rename spec/{ => apps}/rails/config/initializers/cookies_serializer.rb (100%) rename spec/{ => apps}/rails/config/initializers/filter_parameter_logging.rb (100%) rename spec/{ => apps}/rails/config/initializers/inflections.rb (100%) rename spec/{ => apps}/rails/config/initializers/mime_types.rb (100%) rename spec/{ => apps}/rails/config/initializers/wrap_parameters.rb (100%) rename spec/{ => apps}/rails/config/locales/en.yml (100%) rename spec/{ => apps}/rails/config/puma.rb (100%) rename spec/{ => apps}/rails/config/routes.rb (94%) rename spec/{ => apps}/rails/config/spring.rb (100%) rename spec/{ => apps}/rails/config/storage.yml (100%) rename spec/{ => apps}/rails/db/seeds.rb (100%) rename spec/{ => apps}/rails/doc/openapi.json (100%) rename spec/{ => apps}/rails/doc/openapi.yaml (100%) rename spec/{ => apps}/rails/doc/screenshot.png (100%) rename spec/{ => apps}/rails/doc/smart/expected.yaml (100%) rename spec/{ => apps}/rails/doc/smart/openapi.yaml (100%) rename spec/{rails/storage => apps/rails/lib/assets}/.keep (100%) rename spec/{rails/tmp => apps/rails/lib/tasks}/.keep (100%) rename spec/{rails/tmp/pids => apps/rails/log}/.keep (100%) rename spec/{ => apps}/rails/package.json (100%) rename spec/{ => apps}/rails/public/404.html (100%) rename spec/{ => apps}/rails/public/422.html (100%) rename spec/{ => apps}/rails/public/500.html (100%) rename spec/{ => apps}/rails/public/apple-touch-icon-precomposed.png (100%) rename spec/{ => apps}/rails/public/apple-touch-icon.png (100%) rename spec/{ => apps}/rails/public/favicon.ico (100%) rename spec/{ => apps}/rails/public/robots.txt (100%) rename spec/{rails/vendor => apps/rails/storage}/.keep (100%) create mode 100644 spec/apps/rails/tmp/.keep create mode 100644 spec/apps/rails/tmp/pids/.keep create mode 100644 spec/apps/rails/vendor/.keep rename spec/{ => apps}/rails/vendor/my_engine/config/routes.rb (100%) rename spec/{ => apps}/rails/vendor/my_engine/lib/my_engine/engine.rb (100%) rename spec/{ => apps}/roda/doc/openapi.json (100%) rename spec/{ => apps}/roda/doc/openapi.yaml (100%) rename spec/{ => apps}/roda/doc/rspec_openapi.rb (100%) rename spec/{ => apps}/roda/roda_app.rb (100%) create mode 100644 spec/requests/hanami_spec.rb create mode 100644 spec/rspec/hanami_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index f88aedf2..b17a921b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -5,7 +5,7 @@ AllCops: SuggestExtensions: false TargetRubyVersion: 2.7 Exclude: - - 'spec/rails/**/*' + - 'spec/apps/**/*' - 'vendor/**/*' Style/TrailingCommaInHashLiteral: diff --git a/Gemfile b/Gemfile index edcdb401..4fad7e7f 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,16 @@ source 'https://rubygems.org' gemspec gem 'rails', ENV['RAILS_VERSION'] || '6.0.3.7' + +if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.0.0') + gem 'hanami', ENV['HANAMI_VERSION'] || '2.1.0' + gem 'hanami-controller', ENV['HANAMI_VERSION'] || '2.1.0' + gem 'hanami-router', ENV['HANAMI_VERSION'] || '2.1.0' +end + gem 'roda' + +gem 'rails-dom-testing', '~> 2.2' gem 'rspec-rails' group :test do diff --git a/lib/rspec/openapi/record_builder.rb b/lib/rspec/openapi/record_builder.rb index 81e11ef1..31e7adff 100644 --- a/lib/rspec/openapi/record_builder.rb +++ b/lib/rspec/openapi/record_builder.rb @@ -55,7 +55,10 @@ def safe_parse_body(response, media_type) def extract_headers(request, response) request_headers = RSpec::OpenAPI.request_headers.each_with_object([]) do |header, headers_arr| header_key = header.gsub('-', '_').upcase.to_sym - header_value = request.get_header(['HTTP', header_key].join('_')) || request.get_header(header_key) + + header_value = request.get_header(['HTTP', header_key].join('_')) || + request.get_header(header_key) || + request.get_header(header_key.to_s) headers_arr << [header, header_value] if header_value end response_headers = RSpec::OpenAPI.response_headers.each_with_object([]) do |header, headers_arr| diff --git a/lib/rspec/openapi/schema_merger.rb b/lib/rspec/openapi/schema_merger.rb index 1dcfaeb2..b4119553 100644 --- a/lib/rspec/openapi/schema_merger.rb +++ b/lib/rspec/openapi/schema_merger.rb @@ -53,7 +53,8 @@ def merge_arrays(base, key, value) def merge_parameters(base, key, value) all_parameters = value | base[key] - unique_base_parameters = base[key].index_by { |parameter| [parameter[:name], parameter[:in]] } + unique_base_parameters = build_unique_params(base, key) + all_parameters = all_parameters.map do |parameter| base_parameter = unique_base_parameters[[parameter[:name], parameter[:in]]] || {} base_parameter ? base_parameter.merge(parameter) : parameter @@ -63,6 +64,12 @@ def merge_parameters(base, key, value) base[key] = all_parameters end + def build_unique_params(base, key) + base[key].each_with_object({}) do |parameter, hash| + hash[[parameter[:name], parameter[:in]]] = parameter + end + end + SIMILARITY_THRESHOLD = 0.5 def merge_closest_match!(options, spec) diff --git a/rspec-openapi.gemspec b/rspec-openapi.gemspec index f51d9278..05a03a6c 100644 --- a/rspec-openapi.gemspec +++ b/rspec-openapi.gemspec @@ -29,6 +29,7 @@ Gem::Specification.new do |spec| spec.require_paths = ['lib'] spec.add_dependency 'actionpack', '>= 5.2.0' + spec.add_dependency 'rails-dom-testing' spec.add_dependency 'rspec-core' spec.metadata['rubygems_mfa_required'] = 'true' end diff --git a/spec/apps/hanami/.gitignore b/spec/apps/hanami/.gitignore new file mode 100644 index 00000000..f1112aba --- /dev/null +++ b/spec/apps/hanami/.gitignore @@ -0,0 +1,2 @@ +.env +log/* diff --git a/spec/apps/hanami/Procfile.dev b/spec/apps/hanami/Procfile.dev new file mode 100644 index 00000000..9c043624 --- /dev/null +++ b/spec/apps/hanami/Procfile.dev @@ -0,0 +1 @@ +web: bundle exec hanami server diff --git a/spec/apps/hanami/Rakefile b/spec/apps/hanami/Rakefile new file mode 100644 index 00000000..c526f383 --- /dev/null +++ b/spec/apps/hanami/Rakefile @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "hanami/rake_tasks" diff --git a/spec/apps/hanami/app/action.rb b/spec/apps/hanami/app/action.rb new file mode 100644 index 00000000..ec957c67 --- /dev/null +++ b/spec/apps/hanami/app/action.rb @@ -0,0 +1,8 @@ +# auto_register: false +# frozen_string_literal: true + +require 'hanami/action' + +class HanamiTest::Action < Hanami::Action + class RecordNotFound < StandardError; end +end diff --git a/spec/rails/app/assets/images/.keep b/spec/apps/hanami/app/actions/.keep similarity index 100% rename from spec/rails/app/assets/images/.keep rename to spec/apps/hanami/app/actions/.keep diff --git a/spec/apps/hanami/app/actions/extensions/create.rb b/spec/apps/hanami/app/actions/extensions/create.rb new file mode 100644 index 00000000..d74aed17 --- /dev/null +++ b/spec/apps/hanami/app/actions/extensions/create.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Extensions + class Create < HanamiTest::Action + def handle(_request, response) + response.body = [{ name: 'my-ext-1' }].to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/extensions/index.rb b/spec/apps/hanami/app/actions/extensions/index.rb new file mode 100644 index 00000000..be1a4a0b --- /dev/null +++ b/spec/apps/hanami/app/actions/extensions/index.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Extensions + class Index < HanamiTest::Action + def handle(_request, response) + response.body = { message: 'created' }.to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/images/index.rb b/spec/apps/hanami/app/actions/images/index.rb new file mode 100644 index 00000000..0e498e1d --- /dev/null +++ b/spec/apps/hanami/app/actions/images/index.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Images + class Index < HanamiTest::Action + format :json + + def handle(_request, response) + list = [ + { + name: 'file.png', + tags: [], # Keep this empty to check empty array is accepted + }, + ] + + response.body = list.to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/images/show.rb b/spec/apps/hanami/app/actions/images/show.rb new file mode 100644 index 00000000..3e1f13eb --- /dev/null +++ b/spec/apps/hanami/app/actions/images/show.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Images + class Show < HanamiTest::Action + def handle(_request, response) + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjPQBoAAAAASUVORK5CYII=' + .unpack('m').first + + response.format = :png + response.body = png + response.headers.merge!( + { + 'Content-Type' => 'image/png', + 'Content-Disposition' => 'inline', + } + ) + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/images/upload.rb b/spec/apps/hanami/app/actions/images/upload.rb new file mode 100644 index 00000000..9b04f68f --- /dev/null +++ b/spec/apps/hanami/app/actions/images/upload.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Images + class Upload < HanamiTest::Action + # format :form + + def handle(_request, response) + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjPQBoAAAAASUVORK5CYII=' + .unpack('m').first + + response.format = :png + response.body = png + response.headers.merge!( + { + 'Content-Type' => 'image/png', + 'Content-Disposition' => 'inline', + } + ) + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/images/upload_multiple.rb b/spec/apps/hanami/app/actions/images/upload_multiple.rb new file mode 100644 index 00000000..f468a843 --- /dev/null +++ b/spec/apps/hanami/app/actions/images/upload_multiple.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Images + class UploadMultiple < HanamiTest::Action + # format :multipart + + def handle(_request, response) + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjPQBoAAAAASUVORK5CYII=' + .unpack('m').first + + response.format = :png + response.body = png + response.headers.merge!( + { + 'Content-Type' => 'image/png', + 'Content-Disposition' => 'inline', + } + ) + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/images/upload_multiple_nested.rb b/spec/apps/hanami/app/actions/images/upload_multiple_nested.rb new file mode 100644 index 00000000..365f7709 --- /dev/null +++ b/spec/apps/hanami/app/actions/images/upload_multiple_nested.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Images + class UploadMultipleNested < HanamiTest::Action + def handle(_request, response) + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjPQBoAAAAASUVORK5CYII=' + .unpack('m').first + + response.format = :png + response.body = png + response.headers.merge!( + { + 'Content-Type' => 'image/png', + 'Content-Disposition' => 'inline', + } + ) + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/images/upload_nested.rb b/spec/apps/hanami/app/actions/images/upload_nested.rb new file mode 100644 index 00000000..480f0bbf --- /dev/null +++ b/spec/apps/hanami/app/actions/images/upload_nested.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Images + class UploadNested < HanamiTest::Action + def handle(_request, response) + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjPQBoAAAAASUVORK5CYII=' + .unpack('m').first + + response.format = :png + response.body = png + response.headers.merge!( + { + 'Content-Type' => 'image/png', + 'Content-Disposition' => 'inline', + } + ) + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/secret_items/index.rb b/spec/apps/hanami/app/actions/secret_items/index.rb new file mode 100644 index 00000000..b2166e35 --- /dev/null +++ b/spec/apps/hanami/app/actions/secret_items/index.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module SecretItems + class Index < HanamiTest::Action + format :json + + def handle(_request, response) + response.body = { items: ['secrets'] }.to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/tables/create.rb b/spec/apps/hanami/app/actions/tables/create.rb new file mode 100644 index 00000000..a20939aa --- /dev/null +++ b/spec/apps/hanami/app/actions/tables/create.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Tables + class Create < TableAction + format :json + + def handle(request, response) + if request.params[:name].blank? || request.params[:name] == 'some_invalid_name' + response.status = 422 + response.body = { error: 'invalid name parameter' }.to_json + else + response.status = 201 + response.body = find_table.to_json + end + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/tables/destroy.rb b/spec/apps/hanami/app/actions/tables/destroy.rb new file mode 100644 index 00000000..2d4d04bf --- /dev/null +++ b/spec/apps/hanami/app/actions/tables/destroy.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Tables + class Destroy < TableAction + def handle(request, response) + response.format = :json + if request.params[:no_content] + response.status = 202 + else + response.body = find_table(request.params[:id]).to_json + end + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/tables/index.rb b/spec/apps/hanami/app/actions/tables/index.rb new file mode 100644 index 00000000..65fe6f70 --- /dev/null +++ b/spec/apps/hanami/app/actions/tables/index.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Tables + class Index < TableAction + def handle(request, response) + response.headers['X-Cursor'] = 100 + + response.format = :json + + response.body = if request.params[:show_columns] + [find_table('42')].to_json + else + [find_table].to_json + end + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/tables/show.rb b/spec/apps/hanami/app/actions/tables/show.rb new file mode 100644 index 00000000..88bdb211 --- /dev/null +++ b/spec/apps/hanami/app/actions/tables/show.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Tables + class Show < TableAction + format :json + + def handle(request, response) + response.body = find_table(request.params[:id]).to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/tables/table_action.rb b/spec/apps/hanami/app/actions/tables/table_action.rb new file mode 100644 index 00000000..ed97748a --- /dev/null +++ b/spec/apps/hanami/app/actions/tables/table_action.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Tables + class TableAction < HanamiTest::Action + APIKEY = 'k0kubun'.freeze + + include TableRepository + + handle_exception RecordNotFound => :handle_not_fount_error + + before :authenticate + + private + + def handle_not_fount_error(_request, _response, _exception) + halt 404, { message: 'not found' }.to_json + end + + def authenticate(request, response) + return unless request.get_header('AUTHORIZATION') != APIKEY + + response.format = :json + halt 401, { message: 'Unauthorized' }.to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/tables/table_repository.rb b/spec/apps/hanami/app/actions/tables/table_repository.rb new file mode 100644 index 00000000..e4bf7f54 --- /dev/null +++ b/spec/apps/hanami/app/actions/tables/table_repository.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Tables + module TableRepository + class RecordNotFound < StandardError; end + + def find_table(id = nil) + time = Time.parse('2020-07-17 00:00:00') + case id + when '1', nil + { + id: 1, + name: 'access', + description: 'logs', + database: { + id: 2, + name: 'production', + }, + null_sample: nil, + storage_size: 12.3, + created_at: time.iso8601, + updated_at: time.iso8601, + } + when '42' + { + id: 42, + name: 'access', + description: 'logs', + database: { + id: 4242, + name: 'production', + }, + columns: [ + { name: 'id', column_type: 'integer' }, + { name: 'description', column_type: 'varchar' }, + ], + null_sample: nil, + storage_size: 12.3, + created_at: time.iso8601, + updated_at: time.iso8601, + } + else + raise RecordNotFound + end + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/tables/update.rb b/spec/apps/hanami/app/actions/tables/update.rb new file mode 100644 index 00000000..2638b9a3 --- /dev/null +++ b/spec/apps/hanami/app/actions/tables/update.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Tables + class Update < TableAction + def handle(request, response) + response.format = :json + response.body = find_table(request.params[:id]).to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/users/active.rb b/spec/apps/hanami/app/actions/users/active.rb new file mode 100644 index 00000000..70c8789f --- /dev/null +++ b/spec/apps/hanami/app/actions/users/active.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Users + class Active < UserAction + format :json + + def handle(request, response) + response.body = find_user(request.params[:id]).present?.to_json # present not exist in hanami + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/users/create.rb b/spec/apps/hanami/app/actions/users/create.rb new file mode 100644 index 00000000..189ce0ea --- /dev/null +++ b/spec/apps/hanami/app/actions/users/create.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Users + class Create < UserAction + format :json + + def handle(_request, response) + res = { + name: params[:name], + relations: { + avatar: { + url: params[:avatar_url] || 'https://example.com/avatar.png', + }, + pets: params[:pets] || [], + }, + } + + response.status = 201 + response.body = res.to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/users/show.rb b/spec/apps/hanami/app/actions/users/show.rb new file mode 100644 index 00000000..bc136ecd --- /dev/null +++ b/spec/apps/hanami/app/actions/users/show.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Users + class Show < UserAction + format :json + + def handle(request, response) + response.body = find_user(request.params[:id]).to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/users/user_action.rb b/spec/apps/hanami/app/actions/users/user_action.rb new file mode 100644 index 00000000..b757e707 --- /dev/null +++ b/spec/apps/hanami/app/actions/users/user_action.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Tables + class UserAction < HanamiTest::Action + include UserRepository + + handle_exception RecordNotFound => :handle_not_fount_error + + before :authenticate + + private + + def handle_not_fount_error(_request, response, _exception) + response.status = 404 + response.body = { message: 'not found' }.to_json + end + end + end + end +end diff --git a/spec/apps/hanami/app/actions/users/user_repository.rb b/spec/apps/hanami/app/actions/users/user_repository.rb new file mode 100644 index 00000000..f2228a5b --- /dev/null +++ b/spec/apps/hanami/app/actions/users/user_repository.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module HanamiTest + module Actions + module Users + module UserRepository + class RecordNotFound < StandardError; end + + def find_user(id = nil) + case id + when '1', nil + { + name: 'John Doe', + relations: { + avatar: { + url: 'https://example.com/avatar.jpg', + }, + pets: [ + { name: 'doge', age: 8 }, + ], + }, + } + when '2' + {} + else + raise RecordNotFound + end + end + end + end + end +end diff --git a/spec/apps/hanami/bin/dev b/spec/apps/hanami/bin/dev new file mode 100755 index 00000000..74ade166 --- /dev/null +++ b/spec/apps/hanami/bin/dev @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +if ! gem list foreman -i --silent; then + echo "Installing foreman..." + gem install foreman +fi + +exec foreman start -f Procfile.dev "$@" diff --git a/spec/apps/hanami/config.ru b/spec/apps/hanami/config.ru new file mode 100644 index 00000000..83dc7857 --- /dev/null +++ b/spec/apps/hanami/config.ru @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require 'hanami/boot' + +run Hanami.app diff --git a/spec/apps/hanami/config/app.rb b/spec/apps/hanami/config/app.rb new file mode 100644 index 00000000..102b93c9 --- /dev/null +++ b/spec/apps/hanami/config/app.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'hanami' + +module HanamiTest + class App < Hanami::App + config.middleware.use :body_parser, :json + end +end diff --git a/spec/apps/hanami/config/puma.rb b/spec/apps/hanami/config/puma.rb new file mode 100644 index 00000000..df893721 --- /dev/null +++ b/spec/apps/hanami/config/puma.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# +# Environment and port +# +port ENV.fetch('HANAMI_PORT', 2300) +environment ENV.fetch('HANAMI_ENV', 'development') + +# +# Threads within each Puma/Ruby process (aka worker) +# + +# Configure the minimum and maximum number of threads to use to answer requests. +max_threads_count = ENV.fetch('HANAMI_MAX_THREADS', 5) +min_threads_count = ENV.fetch('HANAMI_MIN_THREADS') { max_threads_count } + +threads min_threads_count, max_threads_count + +# +# Workers (aka Puma/Ruby processes) +# + +puma_concurrency = Integer(ENV.fetch('HANAMI_WEB_CONCURRENCY', 0)) +puma_cluster_mode = puma_concurrency > 1 + +# How many worker (Puma/Ruby) processes to run. +# Typically this is set to the number of available cores. +workers puma_concurrency + +# +# Cluster mode (aka multiple workers) +# + +if puma_cluster_mode + # Preload the application before starting the workers. Only in cluster mode. + preload_app! + + # Code to run immediately before master process forks workers (once on boot). + # + # These hooks can block if necessary to wait for background operations unknown + # to puma to finish before the process terminates. This can be used to close + # any connections to remote servers (database, redis, …) that were opened when + # preloading the code. + before_fork do + Hanami.shutdown + end +end diff --git a/spec/apps/hanami/config/routes.rb b/spec/apps/hanami/config/routes.rb new file mode 100644 index 00000000..6d6caf52 --- /dev/null +++ b/spec/apps/hanami/config/routes.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module HanamiTest + class Routes < Hanami::Routes + # Add your routes here. See https://guides.hanamirb.org/routing/overview/ for details. + get '/secret_items', to: 'secret_items.index' + + get '/tables', to: 'tables.index' + get '/tables/:id', to: 'tables.show' + post '/tables', to: 'tables.create' + patch '/tables/:id', to: 'tables.update' + delete '/tables/:id', to: 'tables.destroy' + + get '/images', to: 'images.index' + get '/images/:id', to: 'images.show' + post '/images/upload', to: 'images.upload' + post '/images/upload_nested', to: 'images.upload_nested' + post '/images/upload_multiple', to: 'images.upload_multiple' + post '/images/upload_multiple_nested', to: 'images.upload_multiple_nested' + + post '/users', to: 'users.create' + get '/users/:id', to: 'users.show' + get '/users/active', to: 'users.active' + + get '/test_block', to: ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['A TEST']] } + + slice :my_engine, at: '/my_engine' do + get '/test', to: ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['ANOTHER TEST']] } + get '/eng/example', to: 'eng.example' + end + + scope 'admin' do + scope 'masters' do + get '/extensions', to: 'extensions.index' + post '/extensions', to: 'extensions.create' + end + end + end +end diff --git a/spec/apps/hanami/config/settings.rb b/spec/apps/hanami/config/settings.rb new file mode 100644 index 00000000..126f822f --- /dev/null +++ b/spec/apps/hanami/config/settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module HanamiTest + class Settings < Hanami::Settings + # Define your app settings here, for example: + # + # setting :my_flag, default: false, constructor: Types::Params::Bool + end +end diff --git a/spec/apps/hanami/doc/openapi.json b/spec/apps/hanami/doc/openapi.json new file mode 100644 index 00000000..9d628946 --- /dev/null +++ b/spec/apps/hanami/doc/openapi.json @@ -0,0 +1,1007 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "OpenAPI Documentation", + "version": "1.0.0", + "description": "My beautiful hanami API", + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "servers": [ + { + "url": "http://localhost:3000" + } + ], + "paths": { + "/images": { + "get": { + "summary": "GET /images", + "responses": { + "200": { + "description": "can return an object with an attribute of empty array", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + } + } + }, + "required": [ + "name", + "tags" + ] + } + }, + "example": [ + { + "name": "file.png", + "tags": [ + + ] + } + ] + } + } + } + } + } + }, + "/images/1": { + "get": { + "summary": "GET /images/1", + "responses": { + "200": { + "description": "returns a image payload", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/images/upload": { + "post": { + "summary": "POST /images/upload", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "image" + ] + }, + "example": { + "image": "test.png" + } + } + } + }, + "responses": { + "200": { + "description": "returns a image payload with upload", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/images/upload_multiple": { + "post": { + "summary": "POST /images/upload_multiple", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + } + } + }, + "required": [ + "images" + ] + }, + "example": { + "images": [ + "test.png", + "test.png" + ] + } + } + } + }, + "responses": { + "200": { + "description": "returns a image payload with upload multiple", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/images/upload_multiple_nested": { + "post": { + "summary": "POST /images/upload_multiple_nested", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "image" + ] + } + } + }, + "required": [ + "images" + ] + }, + "example": { + "images": [ + { + "image": "test.png" + }, + { + "image": "test.png" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "returns a image payload with upload multiple nested", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/images/upload_nested": { + "post": { + "summary": "POST /images/upload_nested", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "nested_image": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary" + }, + "caption": { + "type": "string" + } + }, + "required": [ + "image", + "caption" + ] + } + }, + "required": [ + "nested_image" + ] + }, + "example": { + "nested_image": { + "image": "test.png", + "caption": "Some caption" + } + } + } + } + }, + "responses": { + "200": { + "description": "returns a image payload with upload nested", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/my_engine/eng/example": { + "get": { + "summary": "GET /my_engine/eng/example", + "responses": { + "200": { + "description": "returns the block content", + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": "AN ENGINE TEST" + } + } + } + } + } + }, + "/my_engine/test": { + "get": { + "summary": "GET /my_engine/test", + "responses": { + "200": { + "description": "returns some content from the engine", + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": "ANOTHER TEST" + } + } + } + } + } + }, + "/secret_items": { + "get": { + "summary": "GET /secret_items", + "security": [ + { + "SecretApiKeyAuth": [ + + ] + } + ], + "responses": { + "200": { + "description": "authorizes with secret key", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "items" + ] + }, + "example": { + "items": [ + "secrets" + ] + } + } + } + } + } + } + }, + "/tables": { + "get": { + "summary": "GET /tables", + "responses": { + "200": { + "description": "with different deep query parameters", + "headers": { + "X-Cursor": { + "schema": { + "type": "integer" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "database": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "null_sample": { + "nullable": true + }, + "storage_size": { + "type": "number", + "format": "float" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "description", + "database", + "null_sample", + "storage_size", + "created_at", + "updated_at" + ] + } + }, + "example": [ + { + "id": 1, + "name": "access", + "description": "logs", + "database": { + "id": 2, + "name": "production" + }, + "null_sample": null, + "storage_size": 12.3, + "created_at": "2020-07-17T00:00:00+00:00", + "updated_at": "2020-07-17T00:00:00+00:00" + } + ] + } + } + }, + "401": { + "description": "does not return tables if unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + }, + "example": { + "message": "Unauthorized" + } + } + } + } + }, + "parameters": [ + { + "name": "X-Authorization-Token", + "in": "header", + "required": true, + "schema": { + "type": "string" + }, + "example": "token" + }, + { + "name": "filter[name]", + "in": "query", + "required": false, + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "example": { + "name": "Example Table" + } + }, + { + "name": "filter[price]", + "in": "query", + "required": false, + "schema": { + "type": "object", + "properties": { + "price": { + "type": "string" + } + }, + "required": [ + "price" + ] + }, + "example": { + "price": "0" + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "example": 1 + }, + { + "name": "per", + "in": "query", + "required": false, + "schema": { + "type": "integer" + }, + "example": 10 + } + ] + }, + "post": { + "summary": "POST /tables", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "database_id": { + "type": "integer" + } + }, + "required": [ + "name", + "description", + "database_id" + ] + }, + "example": { + "name": "k0kubun", + "description": "description", + "database_id": 2 + } + } + } + }, + "responses": { + "201": { + "description": "returns a table", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "database": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "null_sample": { + "nullable": true + }, + "storage_size": { + "type": "number", + "format": "float" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "description", + "database", + "null_sample", + "storage_size", + "created_at", + "updated_at" + ] + }, + "example": { + "id": 1, + "name": "access", + "description": "logs", + "database": { + "id": 2, + "name": "production" + }, + "null_sample": null, + "storage_size": 12.3, + "created_at": "2020-07-17T00:00:00+00:00", + "updated_at": "2020-07-17T00:00:00+00:00" + } + } + } + }, + "422": { + "description": "fails to create a table (2)", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "example": { + "error": "invalid name parameter" + } + } + } + } + } + } + }, + "/tables/1": { + "delete": { + "summary": "DELETE /tables/1", + "responses": { + "200": { + "description": "returns a table", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "database": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "null_sample": { + "nullable": true + }, + "storage_size": { + "type": "number", + "format": "float" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "description", + "database", + "null_sample", + "storage_size", + "created_at", + "updated_at" + ] + }, + "example": { + "id": 1, + "name": "access", + "description": "logs", + "database": { + "id": 2, + "name": "production" + }, + "null_sample": null, + "storage_size": 12.3, + "created_at": "2020-07-17T00:00:00+00:00", + "updated_at": "2020-07-17T00:00:00+00:00" + } + } + } + }, + "202": { + "description": "returns no content if specified" + } + }, + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "no_content": { + "type": "string" + } + } + }, + "example": { + "no_content": "true" + } + } + } + } + }, + "get": { + "summary": "GET /tables/1", + "responses": { + "200": { + "description": "returns a table", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "database": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "null_sample": { + "nullable": true + }, + "storage_size": { + "type": "number", + "format": "float" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "description", + "database", + "null_sample", + "storage_size", + "created_at", + "updated_at" + ] + }, + "example": { + "id": 1, + "name": "access", + "description": "logs", + "database": { + "id": 2, + "name": "production" + }, + "null_sample": null, + "storage_size": 12.3, + "created_at": "2020-07-17T00:00:00+00:00", + "updated_at": "2020-07-17T00:00:00+00:00" + } + } + } + }, + "401": { + "description": "does not return a table if unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + }, + "example": { + "message": "Unauthorized" + } + } + } + } + } + }, + "patch": { + "summary": "PATCH /tables/1", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + }, + "example": { + "name": "test" + } + } + } + }, + "responses": { + "200": { + "description": "returns a table", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "database": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + }, + "null_sample": { + "nullable": true + }, + "storage_size": { + "type": "number", + "format": "float" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "description", + "database", + "null_sample", + "storage_size", + "created_at", + "updated_at" + ] + }, + "example": { + "id": 1, + "name": "access", + "description": "logs", + "database": { + "id": 2, + "name": "production" + }, + "null_sample": null, + "storage_size": 12.3, + "created_at": "2020-07-17T00:00:00+00:00", + "updated_at": "2020-07-17T00:00:00+00:00" + } + } + } + } + } + } + }, + "/tables/2": { + "get": { + "summary": "GET /tables/2", + "responses": { + "404": { + "description": "does not return a table if not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ] + }, + "example": { + "message": "not found" + } + } + } + } + } + } + }, + "/test_block": { + "get": { + "summary": "GET /test_block", + "responses": { + "200": { + "description": "returns the block content", + "content": { + "text/plain": { + "schema": { + "type": "string" + }, + "example": "A TEST" + } + } + } + }, + "deprecated": true + } + } + }, + "components": { + "securitySchemes": { + "SecretApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "Secret-Key" + } + } + } +} \ No newline at end of file diff --git a/spec/apps/hanami/doc/openapi.yaml b/spec/apps/hanami/doc/openapi.yaml new file mode 100644 index 00000000..f26243ac --- /dev/null +++ b/spec/apps/hanami/doc/openapi.yaml @@ -0,0 +1,665 @@ +# This file is auto-generated by rspec-openapi https://github.com/k0kubun/rspec-openapi +# +# When you write a spec in spec/requests, running the spec with `OPENAPI=1 rspec` will +# update this file automatically. You can also manually edit this file. +--- +openapi: 3.0.3 +info: + title: OpenAPI Documentation + version: 1.0.0 + description: My beautiful hanami API + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html +servers: +- url: http://localhost:3000 +paths: + "/images": + get: + summary: GET /images + responses: + '200': + description: can return an object with an attribute of empty array + content: + application/json: + schema: + type: array + items: + type: object + properties: + name: + type: string + tags: + type: array + items: {} + required: + - name + - tags + example: + - name: file.png + tags: [] + "/images/1": + get: + summary: GET /images/1 + responses: + '200': + description: returns a image payload + content: + image/png: + schema: + type: string + format: binary + "/images/upload": + post: + summary: POST /images/upload + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + image: + type: string + format: binary + required: + - image + example: + image: test.png + responses: + '200': + description: returns a image payload with upload + content: + image/png: + schema: + type: string + format: binary + "/images/upload_multiple": + post: + summary: POST /images/upload_multiple + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + images: + type: array + items: + type: string + format: binary + required: + - images + example: + images: + - test.png + - test.png + responses: + '200': + description: returns a image payload with upload multiple + content: + image/png: + schema: + type: string + format: binary + "/images/upload_multiple_nested": + post: + summary: POST /images/upload_multiple_nested + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + images: + type: array + items: + type: object + properties: + image: + type: string + format: binary + required: + - image + required: + - images + example: + images: + - image: test.png + - image: test.png + responses: + '200': + description: returns a image payload with upload multiple nested + content: + image/png: + schema: + type: string + format: binary + "/images/upload_nested": + post: + summary: POST /images/upload_nested + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + nested_image: + type: object + properties: + image: + type: string + format: binary + caption: + type: string + required: + - image + - caption + required: + - nested_image + example: + nested_image: + image: test.png + caption: Some caption + responses: + '200': + description: returns a image payload with upload nested + content: + image/png: + schema: + type: string + format: binary + "/my_engine/eng/example": + get: + summary: GET /my_engine/eng/example + responses: + '200': + description: returns the block content + content: + text/plain: + schema: + type: string + example: AN ENGINE TEST + "/my_engine/test": + get: + summary: GET /my_engine/test + responses: + '200': + description: returns some content from the engine + content: + text/plain: + schema: + type: string + example: ANOTHER TEST + "/secret_items": + get: + summary: GET /secret_items + security: + - SecretApiKeyAuth: [] + responses: + '200': + description: authorizes with secret key + content: + application/json: + schema: + type: object + properties: + items: + type: array + items: + type: string + required: + - items + example: + items: + - secrets + '401': + description: authorizes with secret key + content: + text/html: + schema: + type: string + example: '' + "/tables": + get: + summary: GET /tables + parameters: + - name: X-Authorization-Token + in: header + required: true + schema: + type: string + example: token + - name: filter[name] + in: query + required: false + schema: + type: object + properties: + name: + type: string + required: + - name + example: + name: Example Table + - name: filter[price] + in: query + required: false + schema: + type: object + properties: + price: + type: string + required: + - price + example: + price: '0' + - name: page + in: query + required: false + schema: + type: integer + example: 1 + - name: per + in: query + required: false + schema: + type: integer + example: 10 + responses: + '200': + description: with different deep query parameters + headers: + X-Cursor: + schema: + type: integer + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + database: + type: object + properties: + id: + type: integer + name: + type: string + required: + - id + - name + null_sample: + nullable: true + storage_size: + type: number + format: float + created_at: + type: string + updated_at: + type: string + required: + - id + - name + - description + - database + - null_sample + - storage_size + - created_at + - updated_at + example: + - id: 1 + name: access + description: logs + database: + id: 2 + name: production + null_sample: + storage_size: 12.3 + created_at: '2020-07-17T00:00:00+00:00' + updated_at: '2020-07-17T00:00:00+00:00' + '401': + description: does not return tables if unauthorized + content: + application/json: + schema: + type: object + properties: + message: + type: string + required: + - message + example: + message: Unauthorized + post: + summary: POST /tables + responses: + '201': + description: returns a table + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + database: + type: object + properties: + id: + type: integer + name: + type: string + required: + - id + - name + null_sample: + nullable: true + storage_size: + type: number + format: float + created_at: + type: string + updated_at: + type: string + required: + - id + - name + - description + - database + - null_sample + - storage_size + - created_at + - updated_at + example: + id: 1 + name: access + description: logs + database: + id: 2 + name: production + null_sample: + storage_size: 12.3 + created_at: '2020-07-17T00:00:00+00:00' + updated_at: '2020-07-17T00:00:00+00:00' + '422': + description: fails to create a table (2) + content: + application/json: + schema: + type: object + properties: + error: + type: string + required: + - error + example: + error: invalid name parameter + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: + type: string + database_id: + type: integer + required: + - name + - description + - database_id + example: + name: k0kubun + description: description + database_id: 2 + "/tables/1": + delete: + summary: DELETE /tables/1 + responses: + '200': + description: returns a table + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + database: + type: object + properties: + id: + type: integer + name: + type: string + required: + - id + - name + null_sample: + nullable: true + storage_size: + type: number + format: float + created_at: + type: string + updated_at: + type: string + required: + - id + - name + - description + - database + - null_sample + - storage_size + - created_at + - updated_at + example: + id: 1 + name: access + description: logs + database: + id: 2 + name: production + null_sample: + storage_size: 12.3 + created_at: '2020-07-17T00:00:00+00:00' + updated_at: '2020-07-17T00:00:00+00:00' + '202': + description: returns no content if specified + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + no_content: + type: string + example: + no_content: 'true' + get: + summary: GET /tables/1 + responses: + '200': + description: returns a table + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + database: + type: object + properties: + id: + type: integer + name: + type: string + required: + - id + - name + null_sample: + nullable: true + storage_size: + type: number + format: float + created_at: + type: string + updated_at: + type: string + required: + - id + - name + - description + - database + - null_sample + - storage_size + - created_at + - updated_at + example: + id: 1 + name: access + description: logs + database: + id: 2 + name: production + null_sample: + storage_size: 12.3 + created_at: '2020-07-17T00:00:00+00:00' + updated_at: '2020-07-17T00:00:00+00:00' + '401': + description: does not return a table if unauthorized + content: + application/json: + schema: + type: object + properties: + message: + type: string + required: + - message + example: + message: Unauthorized + patch: + summary: PATCH /tables/1 + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + type: string + required: + - name + example: + name: test + responses: + '200': + description: returns a table + content: + application/json: + schema: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + database: + type: object + properties: + id: + type: integer + name: + type: string + required: + - id + - name + null_sample: + nullable: true + storage_size: + type: number + format: float + created_at: + type: string + updated_at: + type: string + required: + - id + - name + - description + - database + - null_sample + - storage_size + - created_at + - updated_at + example: + id: 1 + name: access + description: logs + database: + id: 2 + name: production + null_sample: + storage_size: 12.3 + created_at: '2020-07-17T00:00:00+00:00' + updated_at: '2020-07-17T00:00:00+00:00' + "/tables/2": + get: + summary: GET /tables/2 + responses: + '404': + description: does not return a table if not found + content: + application/json: + schema: + type: object + properties: + message: + type: string + required: + - message + example: + message: not found + "/test_block": + get: + summary: GET /test_block + responses: + '200': + description: returns the block content + content: + text/plain: + schema: + type: string + example: A TEST + deprecated: true +components: + securitySchemes: + SecretApiKeyAuth: + type: apiKey + in: header + name: Secret-Key diff --git a/spec/apps/hanami/lib/hanami_test/types.rb b/spec/apps/hanami/lib/hanami_test/types.rb new file mode 100644 index 00000000..a5c02023 --- /dev/null +++ b/spec/apps/hanami/lib/hanami_test/types.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "dry/types" + +module HanamiTest + Types = Dry.Types + + module Types + # Define your custom types here + end +end diff --git a/spec/rails/app/controllers/concerns/.keep b/spec/apps/hanami/lib/tasks/.keep similarity index 100% rename from spec/rails/app/controllers/concerns/.keep rename to spec/apps/hanami/lib/tasks/.keep diff --git a/spec/apps/hanami/public/404.html b/spec/apps/hanami/public/404.html new file mode 100644 index 00000000..51242009 --- /dev/null +++ b/spec/apps/hanami/public/404.html @@ -0,0 +1,82 @@ + + +
+ + +