require 'test_helper'

class TestConstructTest < Minitest::Test
  include TestConstruct::Helpers

  def teardown
    Dir.chdir File.expand_path("../..", __FILE__)
    TestConstruct.destroy_all!
  end

  testing 'using within_construct explicitly' do

    test 'creates construct' do
      num = rand(1_000_000_000)
      TestConstruct.stubs(:rand).returns(num)
      directory = false
      TestConstruct::within_construct do |construct|
        directory = File.directory?(File.join(TestConstruct.tmpdir, "construct_container-#{$PROCESS_ID}-#{num}"))
      end
      assert directory

      directory = false
      TestConstruct.within_construct do |construct|
        directory = File.directory?(File.join(TestConstruct.tmpdir, "construct_container-#{$PROCESS_ID}-#{num}"))
      end
      assert directory
    end

  end

  testing 'creating a construct container' do

    test 'exists' do
      num = rand(1_000_000_000)
      self.stubs(:rand).returns(num)
      within_construct do |construct|
        assert File.directory?(File.join(TestConstruct.tmpdir, "construct_container-#{$PROCESS_ID}-#{num}"))
      end
    end

    test 'yields to its block' do
      sensor = 'no yield'
      within_construct do
        sensor = 'yielded'
      end
      assert_equal 'yielded', sensor
    end

    test 'block argument is container directory Pathname' do
      num = rand(1_000_000_000)
      self.stubs(:rand).returns(num)
      within_construct do |container_path|
        expected_path = (Pathname(TestConstruct.tmpdir) +
          "construct_container-#{$PROCESS_ID}-#{num}")
        assert_equal(expected_path, container_path)
      end
    end

    test 'does not exist afterwards' do
      path = nil
      within_construct do |container_path|
        path = container_path
      end
      assert !path.exist?
    end

    test 'removes entire tree afterwards' do
      path = nil
      within_construct do |container_path|
        path = container_path
        (container_path + 'foo').mkdir
      end
      assert !path.exist?
    end

    test 'removes dir if block raises exception' do
      path = nil
      begin
        within_construct do |container_path|
          path = container_path
          raise 'something bad happens here'
        end
      rescue
      end
      assert !path.exist?
    end

    test 'does not capture exceptions raised in block' do
      err = RuntimeError.new('an error')
      begin
        within_construct do
          raise err
        end
      rescue RuntimeError => e
        assert_same err, e
      end
    end

  end

  testing 'creating a file in a container' do

    test 'exists while in construct block' do
      within_construct do |construct|
        construct.file('foo.txt')
        assert File.exist?(construct + 'foo.txt')
      end
    end

    test 'does not exist after construct block' do
      filepath = 'unset'
      within_construct do |construct|
        filepath = construct.file('foo.txt')
      end
      assert !File.exist?(filepath)
    end

    test 'has empty file contents by default' do
      within_construct do |construct|
        construct.file('foo.txt')
        assert_equal '', File.read(construct + 'foo.txt')
      end
    end

    test 'writes contents to file' do
      within_construct do |construct|
        construct.file('foo.txt','abcxyz')
        assert_equal 'abcxyz', File.read(construct+'foo.txt')
      end
    end

    test 'contents can be given in a block' do
      within_construct do |construct|
        construct.file('foo.txt') do
          <<-EOS
File
Contents
          EOS
        end
        assert_equal "File\nContents\n", File.read(construct+'foo.txt')
      end
    end

    test 'contents block overwrites contents argument' do
      within_construct do |construct|
        construct.file('foo.txt','abc') do
          'xyz'
        end
        assert_equal 'xyz', File.read(construct+'foo.txt')
      end
    end

    test 'block is passed File object' do
      within_construct do |construct|
        construct.file('foo.txt') do |file|
          assert_equal((construct+'foo.txt').to_s, file.path)
        end
      end
    end

    test 'writes to File object passed to block' do
      within_construct do |construct|
        construct.file('foo.txt') do |file|
          file << 'abc'
        end
        assert_equal 'abc', File.read(construct+'foo.txt')
      end
    end

    test 'closes file after block ends' do
      within_construct do |construct|
        construct_file = nil
        construct.file('foo.txt') do |file|
          construct_file = file
        end
        assert construct_file.closed?
      end
    end

    test 'block return value not used as content if passed File object' do
      within_construct do |construct|
        construct.file('foo.txt') do |file|
          file << 'abc'
          'xyz'
        end
        assert_equal 'abc', File.read(construct+'foo.txt')
      end
    end

    test 'contents argument is ignored if block takes File arg' do
      within_construct do |construct|
        construct.file('foo.txt','xyz') do |file|
          file << 'abc'
        end
        assert_equal 'abc', File.read(construct+'foo.txt')
      end
    end

    test 'returns file path' do
      within_construct do |construct|
        assert_equal(construct+'foo.txt', construct.file('foo.txt'))
      end
    end

    test 'creates file including path in one call' do
      within_construct do |construct|
        construct.file('foo/bar/baz.txt')
        assert (construct+'foo/bar/baz.txt').exist?
      end
    end

    test 'creates file including path in one call when directories exists' do
      within_construct do |construct|
        construct.directory('foo/bar')
        construct.file('foo/bar/baz.txt')
        assert (construct+'foo/bar/baz.txt').exist?
      end
    end

    test 'creates file including path with chained calls' do
      within_construct do |construct|
        construct.directory('foo').directory('bar').file('baz.txt')
        assert (construct+'foo/bar/baz.txt').exist?
      end
    end

  end

    testing 'creating a subdirectory in container' do

    test 'exists while in construct block' do
      within_construct do |construct|
        construct.directory 'foo'
        assert (construct+'foo').directory?
      end
    end

    test 'does not exist after construct block' do
      subdir = 'unset'
      within_construct do |construct|
        construct.directory 'foo'
        subdir = construct + 'foo'
      end
      assert !subdir.directory?
    end

    test 'returns the new path name' do
      within_construct do |construct|
        assert_equal((construct+'foo'), construct.directory('foo'))
      end
    end

    test 'yield to block' do
      sensor = 'unset'
      within_construct do |construct|
        construct.directory('bar') do
          sensor = 'yielded'
        end
      end
      assert_equal 'yielded', sensor
    end

    test 'block argument is subdirectory path' do
      within_construct do |construct|
        construct.directory('baz') do |dir|
          assert_equal((construct+'baz'),dir)
        end
      end
    end

    test 'creates nested directory in one call' do
      within_construct do |construct|
        construct.directory('foo/bar')
        assert (construct+'foo/bar').directory?
      end
    end

    test 'creates a nested directory in two calls' do
      within_construct do |construct|
        construct.directory('foo').directory('bar')
        assert (construct+'foo/bar').directory?
      end
    end

  end

  testing "subdirectories changing the working directory" do

    test 'forces directory stays the same' do
      within_construct do |construct|
        old_pwd = Dir.pwd
        construct.directory('foo', :chdir => false) do
          assert_equal old_pwd, Dir.pwd
        end
      end
    end

    test 'defaults chdir setting from construct' do
      within_construct(:chdir => false) do |construct|
        old_pwd = Dir.pwd
        construct.directory('foo') do
          assert_equal old_pwd, Dir.pwd
        end
      end
    end

    test 'overrides construct default' do
      within_construct(:chdir => false) do |construct|
        construct.directory('foo', :chdir => true) do |dir|
          assert_equal dir.to_s, Dir.pwd
        end
      end
    end

    test 'current working directory is within subdirectory' do
      within_construct do |construct|
        construct.directory('foo') do |dir|
          assert_equal dir.to_s, Dir.pwd
        end
      end
    end

    test 'current working directory is unchanged outside of subdirectory' do
      within_construct do |construct|
        old_pwd = Dir.pwd
        construct.directory('foo')
        assert_equal old_pwd, Dir.pwd
      end
    end

    test 'current working directory is unchanged after exception' do
      within_construct do |construct|
        old_pwd = Dir.pwd
        begin
          construct.directory('foo') do
            raise 'something bad happens here'
          end
        rescue
        end
        assert_equal old_pwd, Dir.pwd
      end
    end

    test 'captures exceptions raised in block' do
      within_construct do |construct|
        error = assert_raises RuntimeError do
          construct.directory('foo') do
            raise 'fail!'
          end
        end
        assert_equal 'fail!', error.message
      end
    end

    test 'checking for a file is relative to subdirectory' do
      within_construct do |construct|
        construct.directory('bar')  do |dir|
          dir.file('foo.txt')
          assert File.exist?('foo.txt')
        end
      end
    end

    test 'checking for a directory is relative to subdirectory' do
      within_construct do |construct|
        construct.directory('foo') do |dir|
          dir.directory('mydir')
          assert File.directory?('mydir')
        end
      end
    end

  end

  testing "changing the working directory" do

    test 'forces directory stays the same' do
      old_pwd = Dir.pwd
      within_construct(:chdir => false) do |construct|
        assert_equal old_pwd, Dir.pwd
      end
    end

    test 'current working directory is within construct' do
      within_construct do |construct|
        assert_equal construct.to_s, Dir.pwd
      end
    end

    test 'current working directory is unchanged outside of construct' do
      old_pwd = Dir.pwd
      within_construct do |construct|
      end
      assert_equal old_pwd, Dir.pwd
    end

    test 'current working directory is unchanged after exception' do
      old_pwd = Dir.pwd
      begin
        within_construct do |construct|
          raise 'something bad happens here'
        end
      rescue
      end
      assert_equal old_pwd, Dir.pwd
    end

    test 'does not capture exceptions raised in block' do
      error = assert_raises RuntimeError do
        within_construct do
          raise 'fail!'
        end
      end
      assert_equal 'fail!', error.message
    end

    test 'checking for a file is relative to container' do
      within_construct do |construct|
        construct.file('foo.txt')
        assert File.exist?('foo.txt')
      end
    end

    test 'checking for a directory is relative to container' do
      within_construct do |construct|
        construct.directory('mydir')
        assert File.directory?('mydir')
      end
    end

  end

  testing "#create_construct" do

    test "returns a working Construct" do
      it = create_construct
      it.directory "foo"
      it.file "bar", "CONTENTS"
      assert (it + "foo").directory?
      assert_equal "CONTENTS", (it + "bar").read
    end

  end

  testing "#chdir" do

    test "executes its block in the context of the construct" do
      it = create_construct
      refute_equal it.to_s, Dir.pwd
      sensor = :unset
      it.chdir do
        sensor = Dir.pwd
      end
      assert_equal it.to_s, sensor
    end

    test "leaves construct directory on block exit" do
      it = create_construct
      it.chdir do
        # NOOP
      end
      refute_equal it.to_s, Dir.pwd
    end
  end

  testing "#destroy!" do
    test "removes the construct container" do
      it = create_construct
      it.destroy!
      assert !File.exist?(it.to_s)
    end
  end

  testing "#finalize" do
    test "removes the construct container" do
      it = create_construct
      it.finalize
      assert !File.exist?(it.to_s)
    end

    test "leaves the container if keep is flagged" do
      it = create_construct
      it.keep
      it.finalize
      assert File.exist?(it.to_s)
    end

    test "leaves the container if keep is flagged in a subdir" do
      it = create_construct
      subdir = it.directory "subdir"
      subdir.keep
      it.finalize
      assert File.exist?(it.to_s)
    end
  end

  testing "keep_on_error = true" do
    test 'keeps dir when block raises exception' do
      path = nil
      begin
        within_construct(keep_on_error: true) do |container_path|
          path = container_path
          raise 'something bad happens here'
        end
      rescue
      end
      assert path.exist?
    end

    test 'updates exception message to include location of files' do
      path = nil
      begin
        within_construct(keep_on_error: true) do |container_path|
          path = container_path
          raise 'bad stuff'
        end
      rescue => e
        error = e
      end
      assert_equal "bad stuff\nTestConstruct files kept at: #{path}", error.message
    end
  end

  testing 'base_dir option' do
    test 'determines the location of construct dirs' do
      base_dir = File.expand_path("../temp", __FILE__)
      within_construct(base_dir: base_dir) do |container|
        assert_equal base_dir, container.dirname.to_s
      end
    end
  end

  testing 'name option' do
    test 'used in generation of the directory name' do
      within_construct(name: "My best test ever!") do |container|
        assert_match(/my-best-test-ever-$/, container.basename.to_s)
      end
    end
  end
end
