# frozen_string_literal: true
require 'helper'

class SchemaTest < MiniTest::Unit::TestCase
  # Schema.to_s returns a simple human-readable description of the schema
  # instance with the description embedded in it.
  def test_to_s
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    assert_equal(
      '#<Heroics::Schema description="Sample schema for use in tests.">',
      schema.to_s)
  end

  # Schema.resource returns a ResourceSchema for the named resource.
  def test_resource
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    assert_equal('resource', schema.resource('resource').name)
  end

  # Schema.resource raises a SchemaError is an unknown resource is requested.
  def test_resource_with_unknown_name
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    error = assert_raises Heroics::SchemaError do
      schema.resource('unknown-resource')
    end
    assert_equal("Unknown resource 'unknown-resource'.", error.message)
  end

  # Schema.resources returns a sequence of ResourceSchema children.
  def test_resources
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    assert_equal(['resource', 'another-resource', 'underscored_resource'],
                 schema.resources.map(&:name))
  end
end

class ResourceSchemaTest < MiniTest::Unit::TestCase
  # ResourceSchema.link returns a LinkSchema for the named link.
  def test_link
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('list')
    assert_equal('list', link.name)
  end

  # ResourceSchema.link raises a SchemaError is an unknown link is requested.
  def test_link_with_unknown_name
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    error = assert_raises Heroics::SchemaError do
      schema.resource('resource').link('unknown-link')
    end
    assert_equal("Unknown link 'unknown-link'.", error.message)
  end

  # ResourceSchema.links returns an array of LinkSchema children.
  def test_links
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    assert_equal(
      ['list', 'info', 'identify_resource', 'create', 'submit', 'update', 'delete'],
      schema.resource('resource').links.map { |link| link.name })
  end

  def test_duplicate_links
    duplicate_links_schema = Marshal.load(Marshal.dump(SAMPLE_SCHEMA))
    link = duplicate_links_schema['definitions']['resource']['links'].first
    duplicate_links_schema['definitions']['resource']['links'] = [link, link]

    error = assert_raises Heroics::SchemaError do
      Heroics::Schema.new(duplicate_links_schema)
    end
    assert_equal("Duplicate 'resource' link names: 'list'.", error.message)
  end
end

class LinkSchemaTest < MiniTest::Unit::TestCase
  # LinkSchema.name returns the sanitized link name.
  def test_name
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    assert_equal('list', schema.resource('resource').link('list').name)
  end

  # LinkSchema.resource_name returns the parent resource name.
  def test_resource_name
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    assert_equal('resource',
                 schema.resource('resource').link('list').resource_name)
  end

  # LinkSchema.description returns the link description.
  def test_description
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    assert_equal('Show all sample resources',
                 schema.resource('resource').link('list').description)
  end

  # LinkSchema.parameters returns an empty array if the link doesn't require
  # parameters.
  def test_parameters_without_parameters
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('list')
    assert_equal([], link.parameters)
  end

  # LinkSchema.parameters returns an array of named parameter required to
  # invoke the link correctly.
  def test_parameters
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('info')
    assert_equal(['uuid_field'], link.parameters)
  end

  # LinkSchema.parameters returns a parameter name for multiple parameters
  # when the parameter contains a 'oneOf' element that references more than
  # one parameter.
  def test_parameters_with_one_of_field
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('identify_resource')
    assert_equal(['uuid_field|email_field'], link.parameters)
  end

  # LinkSchema.parameter_details returns an empty array if the link doesn't
  # require parameters.
  def test_parameter_details_without_parameters
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('list')
    assert_equal([], link.parameter_details)
  end

  # LinkSchema.parameter_details returns an array of Parameter with information
  # about the parameters accepted by the link.
  def test_parameter_details
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('info')
    parameters = link.parameter_details
    assert_equal(1, parameters.length)
    parameter = parameters[0]
    assert_equal('resource_uuid_field', parameter.name)
    assert_equal('A sample UUID field', parameter.description)
  end

  # LinkSchema.parameter_details returns an array of ParameterChoices, when
  # more than one value may be used with a property, with information about
  # the parameters accepted by the link.
  def test_parameter_details_with_one_of_field
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('identify_resource')
    parameters = link.parameter_details
    assert_equal(1, parameters.length)
    parameter = parameters[0]
    assert_equal('resource_uuid_field_or_resource_email_field', parameter.name)
    assert_equal('A sample UUID field or A sample email address field',
                 parameter.description)
  end

  # LinkSchema.body returns nil if the link doesn't accept a request body.
  def test_example_body_without_body
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('info')
    assert_equal(nil, link.example_body)
  end

  # LinkSchema.body returns a sample body generated from the properties and
  # embedded examples.
  def test_example_body
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('create')
    assert_equal({'date_field' => '2013-10-19 22:10:29Z',
                  'string_field' => 'Sample text.',
                  'boolean_field' => true,
                  'uuid_field' => '44724831-bf66-4bc2-865f-e2c4c2b14c78',
                  'email_field' => 'username@example.com'},
                 link.example_body)
  end

  # LinkSchema.format_path converts an array of parameters into a path.
  def test_format_path
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('info')
    assert_equal(['/resource/44724831-bf66-4bc2-865f-e2c4c2b14c78', nil],
                 link.format_path(['44724831-bf66-4bc2-865f-e2c4c2b14c78']))
  end

  # LinkSchema.format_path escapes special URL characters in parameters.
  def test_format_path_with_illegal_literals
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('info')
    assert_equal(['/resource/foobar%25', nil],
                 link.format_path(['foobar%']))
  end

  # LinkSchema.format_path correctly returns a parameter as a body if a path
  # doesn't have any parameters.
  def test_format_path_with_body
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('create')
    assert_equal(['/resource', {'new' => 'resource'}],
                 link.format_path([{'new' => 'resource'}]))
  end

  # LinkSchema.format_path correctly returns a parameter as a body if a path
  # doesn't have any parameters.
  def test_format_path_with_path_and_body
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('update')
    assert_equal(['/resource/44724831-bf66-4bc2-865f-e2c4c2b14c78',
                  {'new' => 'resource'}],
                 link.format_path(['44724831-bf66-4bc2-865f-e2c4c2b14c78',
                                   {'new' => 'resource'}]))
  end

  # LinkSchema.format_path only attempts to format path parameters. The body (if
  # supplied), is *not* passed to LinkSchema#format_parameter
  def test_format_path_only_formats_path_parameters
    param = '44724831-bf66-4bc2-865f-e2c4c2b14c78'
    body = { 'new' => 'resource' }
    formatted_values = []
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('update')
    link.singleton_class.send(:alias_method, :orig_format_parameter, :format_parameter)
    link.define_singleton_method(:format_parameter) do |value|
      formatted_values.push(value)
      orig_format_parameter(value)
    end

    assert_equal(["/resource/#{param}", body], link.format_path([param, body]))
    assert_equal([param], formatted_values)
  end

  # LinkSchema.format_path raises an ArgumentError if too few parameters are
  # provided
  def test_format_path_with_too_few_parameters
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('info')
    error = assert_raises ArgumentError do
      link.format_path([])
    end
    assert_equal('wrong number of arguments (0 for 1)', error.message)
  end

  # LinkSchema.format_path raises an ArgumentError if too many parameters are
  # provided
  def test_format_path_with_too_many_parameters
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('info')
    error = assert_raises ArgumentError do
      link.format_path(['too', 'many', 'parameters'])
    end
    assert_equal('wrong number of arguments (3 for 1)', error.message)
  end

  # LinkSchema.pretty_resource_name returns the resource name in a pretty
  # form, with underscores converted to dashes.
  def test_pretty_resource_name
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('another-resource').link('list')
    assert_equal('another-resource', link.pretty_resource_name)
  end

  # LinkSchema.pretty_name returns the link name in a pretty form, with
  # underscores converted to dashes.
  def test_pretty_name
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('identify_resource')
    assert_equal('identify-resource', link.pretty_name)
  end

  # LinkSchema.content_type returns the media type associated with this
  # resource.
  def test_content_type
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('submit')
    assert_equal('application/x-www-form-urlencoded', link.content_type)
  end

  # The content type should default to application/json
  def test_default_content_type
    schema = Heroics::Schema.new(SAMPLE_SCHEMA)
    link = schema.resource('resource').link('identify_resource')
    assert_equal('application/json', link.content_type)
  end
end

class DownloadSchemaTest < MiniTest::Unit::TestCase
  include ExconHelper

  # download_schema makes a request to fetch the schema, decodes the
  # downloaded JSON and returns a Ruby hash.
  def test_download_schema
    Excon.stub(method: :get) do |request|
      assert_equal('example.com', request[:host])
      assert_equal('/schema', request[:path])
      assert_equal('application/vnd.heroku+json; version=3',
                   request[:headers]['Accept'])
      Excon.stubs.pop
      {status: 200, headers: {'Content-Type' => 'application/json'},
       body: MultiJson.dump(SAMPLE_SCHEMA)}
    end

    schema = Heroics::download_schema(
      'https://username:token@example.com/schema',
      default_headers: {'Accept' => 'application/vnd.heroku+json; version=3'})
    assert_equal(SAMPLE_SCHEMA, schema.schema)
  end
end
