spec: generate openapi docs from specs

test plan:
- run some controller tests with the environment variable
  OPENAPI=1
- see that some files have been put in the spec/openapi
  directory
- e.g.: delete spec/openapi/lti/registration_token.yaml, then run
  OPENAPI=1 bundle exec rspec \
  spec/controllers/lti/ims/dynamic_registration_controller_spec.rb

refs INTEROP-8389

Change-Id: I3008a4079013e547bf264808aa7b2f092db8b6a6
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/323583
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Paul Gray <paul.gray@instructure.com>
QA-Review: Paul Gray <paul.gray@instructure.com>
Product-Review: Paul Gray <paul.gray@instructure.com>
Build-Review: Aaron Ogata <aogata@instructure.com>
This commit is contained in:
Tucker McKnight 2023-07-25 16:21:17 -06:00 committed by Tucker Mcknight
parent b93d6db55d
commit f53bf990ce
4 changed files with 73 additions and 0 deletions

View File

@ -34,6 +34,7 @@ group :test do
gem "once-ler", "~> 2.0"
gem "rspec-openapi"
gem "selenium-webdriver", "~> 4.12", require: false
gem "testrailtagging", "0.3.8.7", require: false

View File

@ -933,6 +933,9 @@ GEM
rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-openapi (0.10.0)
actionpack (>= 5.2.0)
rspec-core
rspec-rails (6.1.0)
actionpack (>= 6.1)
activesupport (>= 6.1)
@ -1303,6 +1306,7 @@ DEPENDENCIES
rrule (~> 0.5)
rspec (~> 3.12)
rspec-collection_matchers (~> 1.2)
rspec-openapi
rspec-rails (~> 6.0)
rspec_around_all (= 0.2.0)
rspecq!

View File

@ -31,6 +31,7 @@ rescue LoadError
end
require "crystalball"
require "rspec/openapi"
ENV["RAILS_ENV"] = "test"
@ -452,6 +453,26 @@ RSpec.configure do |config|
end
end
if ENV["OPENAPI"]
config.define_derived_metadata(file_path: %r{spec/controllers}) do |metadata|
metadata[:attempt_openapi_generation] = true
end
config.after(:example, :attempt_openapi_generation) do |example|
OpenApiGenerator.generate(self, example)
end
config.after(:suite) do
result_recorder = RSpec::OpenAPI::ResultRecorder.new(RSpec::OpenAPI.path_records)
result_recorder.record_results!
if result_recorder.errors?
error_message = result_recorder.error_message
colorizer = RSpec::Core::Formatters::ConsoleCodes
RSpec.configuration.reporter.message colorizer.wrap(error_message, :failure)
end
end
end
config.around do |example|
Rails.logger.info "STARTING SPEC #{example.full_description}"
SpecTimeLimit.enforce(example) do

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
#
# Copyright (C) 2023 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module OpenApiGenerator
def self.generate(running_context, example)
# This looks at the "request" variable that is available
# inside of the test ("running_context"). It will throw an exception
# if the test has a request variable defined that is not actually
# an HTTP request object.
# rubocop:disable Style/RedundantBegin
begin
# rubocop:enable Style/RedundantBegin
CanvasRails::Application.routes.router.recognize(running_context.request) do |route|
path_parts_array = route.path.spec.to_s.sub(/\(\.:format\)$/, "").split("/")
path_parts_array.reject! { |part| ["", "api", "v1"].include?(part) }
first_part_of_path = path_parts_array.first
if first_part_of_path == "lti"
first_part_of_path = path_parts_array[0..1].join("/")
end
openapi_doc_file_location = "spec/openapi/#{first_part_of_path}.yaml"
record = RSpec::OpenAPI::RecordBuilder.build(running_context, example:)
RSpec::OpenAPI.path_records[openapi_doc_file_location] << record if record
end
rescue NoMethodError
# If we are here, a request variable was defined in the test but
# it wasn't readable as an HTTP request, or for some reason didn't
# map to a route. This is fine; we won't document this one.
end
end
end