require 'request_helper'

RSpec.describe Inferno::DSL::Runnable do
  include Rack::Test::Methods
  include RequestHelpers
  let(:test_suites_repo) { Inferno::Repositories::TestSuites.new }
  let(:test_groups_repo) { Inferno::Repositories::TestGroups.new }

  describe '.resume_test_route' do
    let(:test_suite) { test_suites_repo.find('demo') }
    let(:test_group) { test_groups_repo.find('demo-wait_group') }
    let(:wait_test) { test_group.tests[1] }
    let(:test_session) { repo_create(:test_session, test_suite_id: test_suite.id) }
    let!(:test_run) do
      repo_create(
        :test_run,
        test_session_id: test_session.id,
        runnable: { test_group_id: test_group.id },
        identifier: 'IDENTIFIER',
        status: 'waiting',
        wait_timeout: Time.now + 300.seconds
      )
    end

    let!(:result) do
      repo_create(
        :result,
        test_run_id: test_run.id,
        runnable: wait_test.reference_hash,
        result_message: 'waiting for incoming request',
        result: 'wait'
      )
    end

    context 'when the identifier does not match a test run' do
      it 'renders a 500 error' do
        get '/custom/demo/resume?xyz=BAD_IDENTIFIER'
        expect(last_response.status).to eq(500)
      end
    end

    context 'when the wait timeout has been passed' do
      it 'renders a 500 error' do
        Inferno::Repositories::TestRuns.new.update(test_run.id, wait_timeout: Time.now - 5.seconds)
        get '/custom/demo/resume?xyz=IDENTIFIER'
        expect(last_response.status).to eq(500)
      end
    end

    context 'when a matching test run is found' do
      it 'redirects the user to the waiting group' do
        get '/custom/demo/resume?xyz=IDENTIFIER'

        expect(last_response.status).to eq(302)

        location_header = last_response.get_header('location')

        expect(location_header).to eq("/test_sessions/#{test_session.id}##{test_group.id}")
      end

      it 'updates the waiting test result' do
        get '/custom/demo/resume?xyz=IDENTIFIER'

        updated_result = Inferno::Repositories::Results.new.find(result.id)

        expect(updated_result.result).to eq('pass')
        expect(updated_result.result_message).to be_nil
      end

      it 'updates the waiting test run' do
        get '/custom/demo/resume?xyz=IDENTIFIER'

        updated_run = Inferno::Repositories::TestRuns.new.find(test_run.id)

        expect(updated_run.identifier).to be_nil
      end
    end
  end

  describe '.test_count' do
    it 'returns 1 for a test' do
      klass = Class.new(Inferno::Entities::Test)

      expect(klass.test_count).to eq(1)
    end

    it 'returns the number of tests in a non-nested group' do
      group = test_groups_repo.find('DemoIG_STU1::DemoGroup')

      expect(group.test_count).to eq(group.children.length)
    end

    it 'returns the total number of tests in a nested group' do
      base_group = test_groups_repo.find('DemoIG_STU1::DemoGroup')
      parent_group = test_suites_repo.find('demo').children[1]

      expect(parent_group.test_count).to eq(base_group.test_count * 2)
    end

    it 'returns the total number of tests in a suite' do
      demo_group = test_groups_repo.find('DemoIG_STU1::DemoGroup')
      wait_group = test_groups_repo.find('demo-wait_group')
      run_as_group_examples = test_groups_repo.find('demo-run_as_group_examples')
      suite = test_suites_repo.find('demo')

      expect(suite.test_count).to eq(demo_group.test_count * 3 +
                                     wait_group.test_count +
                                     run_as_group_examples.test_count)
    end
  end

  describe '.missing_inputs' do
    it 'returns missing inputs for a test' do
      example_test = Class.new(Inferno::Entities::Test)
      example_test.input :a, :b, :c
      example_test.input :d, optional: true
      missing_inputs = example_test.missing_inputs([{ name: 'a', value: 'a' }])
      expect(missing_inputs).to eq(['b', 'c'])

      missing_inputs = example_test.missing_inputs([{ name: 'a', value: 'a' }, { name: 'b', value: 'b' },
                                                    { name: 'c', value: 'c' }])
      expect(missing_inputs).to eq([])
    end

    it 'looks through children for required inputs' do
      example_test_group = Class.new(Inferno::Entities::TestGroup)
      example_test_group.input :e, :f
      example_test_group.test 'child test' do
        input :a, :b, :c
        input :d, optional: true
      end
      missing_inputs = example_test_group.missing_inputs([{ name: 'a', value: 'a' }, { name: 'e', value: 'e' }])
      expect(missing_inputs).to eq(['f', 'b', 'c'])

      missing_inputs = example_test_group.missing_inputs([{ name: 'a', value: 'a' }, { name: 'b', value: 'b' },
                                                          { name: 'c', value: 'c' }, { name: 'e', value: 'e' },
                                                          { name: 'f', value: 'f' }])
      expect(missing_inputs).to eq([])
    end

    it 'respects outputs of prior tests' do
      example_test_group = Class.new(Inferno::Entities::TestGroup)
      example_test_group.test 'child test with outputs' do
        input :a, :b
        output :c
      end
      example_test_group.test 'child test uses output' do
        input :c, :d
      end
      missing_inputs = example_test_group.missing_inputs([{ name: 'a', value: 'a' }, { name: 'd', value: 'd' }])
      expect(missing_inputs).to eq(['b'])
    end

    it 'does not include output that in a later test' do
      example_test_group = Class.new(Inferno::Entities::TestGroup)
      example_test_group.test 'child test' do
        input :a, :b
      end
      example_test_group.test 'child test with output' do
        output :a
      end
      missing_inputs = example_test_group.missing_inputs([{ name: 'b', value: 'b' }])
      expect(missing_inputs).to eq(['a'])
    end
  end

  describe '.user_runnable?' do
    let(:run_as_group_examples) { test_groups_repo.find('demo-run_as_group_examples') }

    it 'sets user_runnable on groups and tests to true by default' do
      example_test_group = Class.new(Inferno::Entities::TestGroup)
      example_test_group.test 'test' do
        input :a
      end
      expect(example_test_group.user_runnable?).to eq(true)
      expect(example_test_group.tests.first.user_runnable?).to eq(true)
    end

    it 'sets user_runnable on groups to true if not under groups set to run_as_group' do
      expect(run_as_group_examples.groups.first.user_runnable?).to be(true)
    end

    it 'sets user_runnable on groups to false if under groups set to run_as_group' do
      expect(run_as_group_examples.groups.first.groups.first.user_runnable?).to be(false)
      expect(run_as_group_examples.groups.first.groups.second.user_runnable?).to be(false)
    end

    it 'sets user_runnable on tests two levels under groups that are run_as_group to be false' do
      expect(run_as_group_examples.groups.first.groups.first.tests.first.user_runnable?).to be(false)
      expect(run_as_group_examples.groups.first.groups.second.tests.first.user_runnable?).to be(false)
    end

    it 'sets user_runnable on tests immediately under groups that are run_as_group to be false' do
      expect(run_as_group_examples.groups.second.tests.first.user_runnable?).to be(false)
    end
  end
end
