lib/active_model_serializers/test/schema.rb



module ActiveModelSerializers
  module Test
    module Schema
      # A Minitest Assertion that test the response is valid against a schema.
      # @param schema_path [String] a custom schema path
      # @param message [String] a custom error message
      # @return [Boolean] true when the response is valid
      # @return [Minitest::Assertion] when the response is invalid
      # @example
      #   get :index
      #   assert_response_schema
      def assert_response_schema(schema_path = nil, message = nil)
        matcher = AssertResponseSchema.new(schema_path, response, message)
        assert(matcher.call, matcher.message)
      end

      MissingSchema = Class.new(Errno::ENOENT)
      InvalidSchemaError = Class.new(StandardError)

      class AssertResponseSchema
        attr_reader :schema_path, :response, :message

        def initialize(schema_path, response, message)
          require_json_schema!
          @response = response
          @schema_path = schema_path || schema_path_default
          @message = message
          @document_store = JsonSchema::DocumentStore.new
          add_schema_to_document_store
        end

        def call
          json_schema.expand_references!(store: document_store)
          status, errors = json_schema.validate(response_body)
          @message ||= errors.map(&:to_s).to_sentence
          status
        end

        protected

        attr_reader :document_store

        def controller_path
          response.request.filtered_parameters[:controller]
        end

        def action
          response.request.filtered_parameters[:action]
        end

        def schema_directory
          ActiveModelSerializers.config.schema_path
        end

        def schema_full_path
          "#{schema_directory}/#{schema_path}"
        end

        def schema_path_default
          "#{controller_path}/#{action}.json"
        end

        def schema_data
          load_json_file(schema_full_path)
        end

        def response_body
          load_json(response.body)
        end

        def json_schema
          @json_schema ||= JsonSchema.parse!(schema_data)
        end

        def add_schema_to_document_store
          Dir.glob("#{schema_directory}/**/*.json").each do |path|
            schema_data = load_json_file(path)
            extra_schema = JsonSchema.parse!(schema_data)
            document_store.add_schema(extra_schema)
          end
        end

        def load_json(json)
          JSON.parse(json)
        rescue JSON::ParserError => ex
          raise InvalidSchemaError, ex.message
        end

        def load_json_file(path)
          load_json(File.read(path))
        rescue Errno::ENOENT
          raise MissingSchema, "No Schema file at #{schema_full_path}"
        end

        def require_json_schema!
          require 'json_schema'
        rescue LoadError
          raise LoadError, "You don't have json_schema installed in your application. Please add it to your Gemfile and run bundle install"
        end
      end
    end
  end
end