diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c426ab2..be92cca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,8 +19,9 @@ jobs: ruby-version: - 3.2 - 3.3 + - 3.4 + - 3.5.0-preview1 rails-version: - - 7.1.5.1 - 7.2.2.1 - 8.0.1 postgres-version: diff --git a/Rakefile b/Rakefile index 938787b..b771bae 100644 --- a/Rakefile +++ b/Rakefile @@ -10,7 +10,7 @@ namespace :test do ENCODERS.each do |encoder| Rake::TestTask.new(encoder => ["#{encoder}:env"]) do |t| t.libs << 'lib' << 'test' - t.test_files = FileList['test/**/*_test.rb'] + t.test_files = FileList[ARGV[1] ? ARGV[1] : 'test/**/*_test.rb'] t.warning = true t.verbose = false end @@ -19,7 +19,7 @@ namespace :test do task(:env) { ENV["TSENCODER"] = encoder } end end - + desc "Run test with all encoders" task all: ENCODERS.shuffle.map{ |e| "test:#{e}" } end diff --git a/lib/standard_api/controller.rb b/lib/standard_api/controller.rb index a8e0995..b371a41 100644 --- a/lib/standard_api/controller.rb +++ b/lib/standard_api/controller.rb @@ -8,6 +8,7 @@ def self.included(klass) :default_limit klass.before_action :set_standardapi_headers klass.before_action :includes, except: [:destroy, :add_resource, :remove_resource, :json_schema] + klass.rescue_from StandardAPI::ParameterMissing, with: :bad_request klass.rescue_from StandardAPI::UnpermittedParameters, with: :bad_request klass.append_view_path(File.join(File.dirname(__FILE__), 'views')) @@ -29,7 +30,7 @@ def schema Rails.application.eager_load! if !Rails.application.config.eager_load end end - + def json_schema Rails.application.eager_load! if !Rails.application.config.eager_load @includes = StandardAPI::Includes.normalize(params[:include] || {}) @@ -290,9 +291,10 @@ def resources query end - + def resource - return @resource if instance_variable_get('@resource') + return @resource if instance_variable_defined?(:@resource) + @resource = if action_name == "create" model.new else @@ -303,7 +305,10 @@ def resource def nested_includes(model, attributes) includes = {} attributes&.each do |key, value| - if association = model.reflect_on_association(key) + association = model.reflect_on_association(key) + if association && + self.respond_to?("nested_#{model_name(model)}_attributes", true) && + self.send("nested_#{model_name(model)}_attributes").include?(association.name) includes[key] = value.is_a?(Array) ? {} : nested_includes(association.klass, value) end end @@ -319,7 +324,7 @@ def includes end if (action_name == 'create' || action_name == 'update') && model && params.has_key?(model.model_name.singular) - @includes.reverse_merge!(nested_includes(model, params[model.model_name.singular].to_unsafe_h)) + @includes.reverse_merge!(nested_includes(model, params[model.model_name.singular].to_unsafe_h)) end @includes diff --git a/lib/standard_api/test_case.rb b/lib/standard_api/test_case.rb index 6e0e9fe..aaf8c0a 100644 --- a/lib/standard_api/test_case.rb +++ b/lib/standard_api/test_case.rb @@ -20,7 +20,7 @@ def assert_equal_or_nil(expected, *args) end def self.included(klass) - [:filters, :orders, :includes].each do |attribute| + [:filters, :includes].each do |attribute| klass.send(:class_attribute, attribute) end @@ -28,7 +28,6 @@ def self.included(klass) model_class = klass.name.gsub(/Test$/, '').constantize.model klass.send(:filters=, model_class.attribute_names) - klass.send(:orders=, model_class.attribute_names) klass.send(:includes=, model_class.reflect_on_all_associations.map(&:name)) rescue NameError => e raise e if e.message != "uninitialized constant #{model_class_name}" @@ -60,6 +59,10 @@ def supports_format(format, action=nil) count > 0 end + def orders + controller_class.new.send(model.model_name.singular + "_orders") + end + def default_orders controller_class.new.send(:default_orders) end diff --git a/lib/standard_api/version.rb b/lib/standard_api/version.rb index ed51e2b..cbd0a54 100644 --- a/lib/standard_api/version.rb +++ b/lib/standard_api/version.rb @@ -1,3 +1,3 @@ module StandardAPI - VERSION = '8.0.1' + VERSION = '8.0.2' end diff --git a/standardapi.gemspec b/standardapi.gemspec index 5df19f3..cc53b75 100644 --- a/standardapi.gemspec +++ b/standardapi.gemspec @@ -17,9 +17,9 @@ Gem::Specification.new do |spec| spec.test_files = `git ls-files -- {test}/*`.split("\n") spec.require_paths = ["lib", "test"] - spec.add_runtime_dependency 'rails', '>= 7.1.3' - spec.add_runtime_dependency 'activesupport', '>= 7.1.3' - spec.add_runtime_dependency 'actionpack', '>= 7.1.3' + spec.add_runtime_dependency 'rails', '>= 7.2.2' + spec.add_runtime_dependency 'activesupport', '>= 7.2.2' + spec.add_runtime_dependency 'actionpack', '>= 7.2.2' spec.add_runtime_dependency 'activerecord-sort', '>= 6.1.0' spec.add_runtime_dependency 'activerecord-filter', '>= 8.0.0' @@ -30,6 +30,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'jbuilder' spec.add_development_dependency "rake" spec.add_development_dependency 'minitest' + spec.add_development_dependency 'minitest-reporters' spec.add_development_dependency "simplecov" spec.add_development_dependency "factory_bot_rails" spec.add_development_dependency "faker" diff --git a/test/standard_api/controller/include_test.rb b/test/standard_api/controller/include_test.rb index 8fbb568..daa65b2 100644 --- a/test/standard_api/controller/include_test.rb +++ b/test/standard_api/controller/include_test.rb @@ -92,7 +92,7 @@ class ControllerIncludesTest < ActionDispatch::IntegrationTest assert_equal 1, property.reload.photos.count assert_response :created end - + # This test passes because includes are not used, response is a HEAD response # and no includes are used. test "Controller#remove_resource with an invalid include" do diff --git a/test/standard_api/json_schema_test.rb b/test/standard_api/json_schema_test.rb index 81ca65d..f577214 100644 --- a/test/standard_api/json_schema_test.rb +++ b/test/standard_api/json_schema_test.rb @@ -37,7 +37,7 @@ class JSONSchemaTest < ActionDispatch::IntegrationTest schema = JSON(response.body) assert_equal true, schema.dig('properties', 'created_at', 'readOnly') - assert_equal nil, schema.dig('properties', 'name', 'readOnly') + assert_nil schema.dig('properties', 'name', 'readOnly') end test 'Controller#json_schema.json type' do diff --git a/test/standard_api/nested_attributes/belongs_to_polymorphic_test.rb b/test/standard_api/nested_attributes/belongs_to_polymorphic_test.rb new file mode 100644 index 0000000..31711b0 --- /dev/null +++ b/test/standard_api/nested_attributes/belongs_to_polymorphic_test.rb @@ -0,0 +1,21 @@ +require 'standard_api/test_helper' + +module NestedAttributes + class BelongsToPolymorphicTest < ActionDispatch::IntegrationTest + # include StandardAPI::TestCase + include StandardAPI::Helpers + + # = Create Test + + # TODO: we maybe should return an error response when something is filtered + # out of a create or update + test 'create record and nested polymorphic record not created because rejected by the ACL' do + @controller = AccountsController.new + + assert_nothing_raised do + post account_path, params: { account: { name: 'Smee', subject: {make: 'Nokia'}, subject_type: 'Camera' } }, as: :json + end + end + + end +end diff --git a/test/standard_api/nested_attributes/belongs_to_test.rb b/test/standard_api/nested_attributes/belongs_to_test.rb index 9885dd2..94d03c9 100644 --- a/test/standard_api/nested_attributes/belongs_to_test.rb +++ b/test/standard_api/nested_attributes/belongs_to_test.rb @@ -16,10 +16,10 @@ class BelongsToTest < ActionDispatch::IntegrationTest photo = Photo.last assert_equal 'Big Ben', photo.account.name end - + test 'create record and update nested record' do account = create(:account, name: 'Big Ben') - + @controller = PhotosController.new post photos_path, params: { photo: { account: {id: account.id, name: 'Little Jimmie'}} }, as: :json @@ -31,13 +31,13 @@ class BelongsToTest < ActionDispatch::IntegrationTest end # = Update Test - + test 'update record and create nested record' do photo = create(:photo) @controller = PhotosController.new put photo_path(photo), params: { photo: { account: {name: 'Big Ben'}} }, as: :json - + assert_response :ok photo.reload assert_equal 'Big Ben', photo.account.name @@ -49,7 +49,7 @@ class BelongsToTest < ActionDispatch::IntegrationTest @controller = PhotosController.new put photo_path(photo), params: { photo: { account: {name: 'Little Jimmie'}} }, as: :json - + assert_response :ok photo.reload assert_equal 'Little Jimmie', photo.account.name @@ -61,7 +61,7 @@ class BelongsToTest < ActionDispatch::IntegrationTest @controller = PhotosController.new put photo_path(photo), params: { photo: { account: nil} }, as: :json - + assert_response :ok photo.reload assert_nil photo.account diff --git a/test/standard_api/nested_attributes/has_many_test.rb b/test/standard_api/nested_attributes/has_many_test.rb index 5e4c981..84e2569 100644 --- a/test/standard_api/nested_attributes/has_many_test.rb +++ b/test/standard_api/nested_attributes/has_many_test.rb @@ -77,6 +77,7 @@ class HasManyTest < ActionDispatch::IntegrationTest attributes = JSON.parse(response.body) assert_response :ok + puts response.body assert_equal account.id, attributes["accounts"][0]["id"] assert_equal "B Co.", attributes["accounts"][0]["name"] end diff --git a/test/standard_api/nested_attributes/has_one_test.rb b/test/standard_api/nested_attributes/has_one_test.rb index 527cf38..b6357dd 100644 --- a/test/standard_api/nested_attributes/has_one_test.rb +++ b/test/standard_api/nested_attributes/has_one_test.rb @@ -17,7 +17,7 @@ class HasOneTest < ActionDispatch::IntegrationTest assert_equal photo.id, photo.camera.photo_id assert_equal 'Sony', photo.camera.make end - + test 'create record and update nested record' do camera = create(:camera, make: 'Sony') @@ -31,7 +31,7 @@ class HasOneTest < ActionDispatch::IntegrationTest end # = Update Test - + test 'update record and create nested record' do photo = create(:photo) @@ -68,4 +68,4 @@ class HasOneTest < ActionDispatch::IntegrationTest end end -end \ No newline at end of file +end diff --git a/test/standard_api/standard_api_test.rb b/test/standard_api/standard_api_test.rb index f04a1f6..526e260 100644 --- a/test/standard_api/standard_api_test.rb +++ b/test/standard_api/standard_api_test.rb @@ -105,7 +105,7 @@ def normalizers @controller.action_name = 'create' assert_equal @controller.send(:model_params), ActionController::Parameters.new end - + test 'Controller#model_params is conditional based on existing resource' do document = create(:document, type: 'movie') @controller = DocumentsController.new @@ -265,12 +265,12 @@ def normalizers patch document_path(pdf), params: { document: pdf.attributes } assert_redirected_to document_path(pdf) end - + test 'Controller#create has Affected-Rows header' do attrs = attributes_for(:property) post properties_path, params: { property: attrs }, as: :json assert_equal response.headers['Affected-Rows'], 1 - + attrs = attributes_for(:property, :invalid) post properties_path, params: { property: attrs }, as: :json assert_equal response.headers['Affected-Rows'], 0 @@ -280,7 +280,7 @@ def normalizers property = create(:property) patch property_path(property), params: { property: property.attributes }, as: :json assert_equal response.headers['Affected-Rows'], 1 - + attrs = attributes_for(:property, :invalid) patch property_path(property), params: { property: attrs }, as: :json assert_equal response.headers['Affected-Rows'], 0 @@ -290,13 +290,13 @@ def normalizers property = create(:property) delete property_path(property), as: :json assert_equal response.headers['Affected-Rows'], 1 - + assert_raises ActiveRecord::RecordNotFound do delete property_path(property), as: :json assert_equal response.headers['Affected-Rows'], 0 end end - + # = View Tests test 'rendering tables' do diff --git a/test/standard_api/test_app/models.rb b/test/standard_api/test_app/models.rb index c771137..493c595 100644 --- a/test/standard_api/test_app/models.rb +++ b/test/standard_api/test_app/models.rb @@ -29,7 +29,7 @@ class Property < ActiveRecord::Base has_one :landlord, class_name: 'Account' has_one :document_attachments, class_name: "Attachment", as: :record, inverse_of: :record has_one :document, through: "document_attachments" - + accepts_nested_attributes_for :photos def english_name @@ -151,7 +151,7 @@ def self.up t.integer "numericality", default: 2 t.string "build_type" t.boolean "agree_to_terms", default: true - t.string "phone_number", default: '999-999-9999' + t.string "phone_number", default: '999-999-9999' end create_table "references", force: :cascade do |t| diff --git a/test/standard_api/test_helper.rb b/test/standard_api/test_helper.rb index 62353fd..64a38d7 100644 --- a/test/standard_api/test_helper.rb +++ b/test/standard_api/test_helper.rb @@ -7,6 +7,10 @@ require 'standard_api/test_case' require 'byebug' require 'mocha/minitest' +require 'minitest/reporters' + + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new # Setup the test db ActiveSupport.test_order = :random