##########################################################################
# test_taskscheduler.rb
#
# Test suite for the win32-taskscheduler package. You should run this
# via the 'rake test' task.
##########################################################################
require 'win32/taskscheduler'
require 'win32/security'
require 'socket'
require 'test-unit'
require 'etc'
include Win32

class TC_TaskScheduler < Test::Unit::TestCase
  def self.startup
    @@host = Socket.gethostname
    @@user = Etc.getlogin
    @@elevated = Win32::Security.elevated_security?
  end

  def setup
    @task = 'foo'

    @trigger = {
      :start_year   => 2015,
      :start_month  => 4,
      :start_day    => 11,
      :start_hour   => 7,
      :start_minute => 14,
      :trigger_type => TaskScheduler::DAILY,
      :type         => { :days_interval => 1 }
    }

    @ts = TaskScheduler.new
  end

  # Helper method
  def setup_task
    @ts.new_work_item(@task, @trigger)
    @ts.activate(@task)
  end

  test "version constant is set to expected value" do
    assert_equal('0.3.2', TaskScheduler::VERSION)
  end

  test "account_information method basic functionality" do
    assert_respond_to(@ts, :account_information)
  end

  test "account_information returns the task owner" do
    setup_task
    assert_equal("#{@@host}\\#{@@user}", @ts.account_information)
  end

  test "account_information returns nil if no task has been activated" do
    assert_nil(@ts.account_information)
  end

  test "account_information does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.account_information('foo') }
  end

  test "set_account_information basic functionality" do
    assert_respond_to(@ts, :set_account_information)
  end

  test "set_account_information works as expected" do
    setup_task
    #assert_nothing_raised{ @ts.set_account_information(@@user, 'XXXX') }
    #assert_equal('test', @ts.account_information)
  end

  test "set_account_information requires two arguments" do
    setup_task
    assert_raise(ArgumentError){ @ts.set_account_information }
    assert_raise(ArgumentError){ @ts.set_account_information('x') }
    assert_raise(ArgumentError){ @ts.set_account_information('x', 'y', 'z') }
  end

  test "arguments to set_account_information must be strings" do
    setup_task
    assert_raise(TypeError){ @ts.set_account_information(1, 'XXX') }
    assert_raise(TypeError){ @ts.set_account_information('x', 1) }
  end

  test "activate method basic functionality" do
    assert_respond_to(@ts, :activate)
  end

  test "activate behaves as expected" do
    @ts.new_work_item(@task, @trigger)
    assert_nothing_raised{ @ts.activate(@task) }
  end

  test "calling activate on the same object multiple times has no effect" do
    @ts.new_work_item(@task, @trigger)
    assert_nothing_raised{ @ts.activate(@task) }
    assert_nothing_raised{ @ts.activate(@task) }
  end

  test "activate requires a single argument" do
    assert_raise(ArgumentError){ @ts.activate }
    assert_raise(ArgumentError){ @ts.activate('foo', 'bar') }
  end

  test "activate requires a string argument" do
    assert_raise(TypeError){ @ts.activate(1) }
  end

  test "attempting to activate a bad task results in an error" do
    assert_raise(TaskScheduler::Error){ @ts.activate('bogus') }
  end

  test "application_name basic functionality" do
    assert_respond_to(@ts, :application_name)
  end

  test "application_name returns a string or nil" do
    setup_task
    assert_nothing_raised{ @ts.application_name }
    assert_kind_of([String, NilClass], @ts.application_name)
  end

  test "application_name does not accept any arguments" do
    assert_raises(ArgumentError){ @ts.application_name('bogus') }
  end

  test "application_name= basic functionality" do
    assert_respond_to(@ts, :application_name=)
  end

  test "application_name= works as expected" do
    setup_task
    assert_nothing_raised{ @ts.application_name = "notepad.exe" }
    assert_equal("notepad.exe", @ts.application_name)
  end

  test "application_name= requires a string argument" do
    assert_raise(TypeError){ @ts.application_name = 1 }
  end

  test "comment method basic functionality" do
    assert_respond_to(@ts, :comment)
  end

  test "comment method returns a string" do
    setup_task
    assert_nothing_raised{ @ts.comment }
    assert_kind_of(String, @ts.comment)
  end

  test "comment method does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.comment('test') }
  end

  test "comment= method basic functionality" do
    assert_respond_to(@ts, :comment=)
  end

  test "comment= works as expected" do
    setup_task
    assert_nothing_raised{ @ts.comment = "test" }
    assert_equal("test", @ts.comment)
  end

  test "comment= method requires a string argument" do
    assert_raise(TypeError){ @ts.comment = 1 }
  end

  test "creator method basic functionality" do
    setup_task
    assert_respond_to(@ts, :creator)
    assert_nothing_raised{ @ts.creator }
    assert_kind_of(String, @ts.creator)
  end

  test "creator method returns expected value" do
    setup_task
    assert_equal('', @ts.creator)
  end

  test "creator method does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.creator('foo') }
  end

  test "creator= method basic functionality" do
    assert_respond_to(@ts, :creator=)
  end

  test "creator= method works as expected" do
    setup_task
    assert_nothing_raised{ @ts.creator = "Test Creator" }
    assert_equal("Test Creator", @ts.creator)
  end

  test "creator= method requires a string argument" do
    assert_raise(TypeError){ @ts.creator = 1 }
  end

  test "delete method basic functionality" do
    assert_respond_to(@ts, :delete)
  end

  test "delete method works as expected" do
    setup_task
    assert_nothing_raised{ @ts.delete(@task) }
    assert_false(@ts.exists?(@task))
  end

  test "delete method requires a single argument" do
    assert_raise(ArgumentError){ @ts.delete }
  end

  test "delete method raises an error if the task does not exist" do
    assert_raise(TaskScheduler::Error){ @ts.delete("foofoo") }
  end

  test "enum basic functionality" do
    assert_respond_to(@ts, :enum)
    assert_nothing_raised{ @ts.enum }
  end

  test "enum method returns an array of strings" do
    assert_kind_of(Array, @ts.enum)
    assert_kind_of(String, @ts.enum.first)
  end

  test "tasks is an alias for enum" do
    assert_respond_to(@ts, :tasks)
    assert_alias_method(@ts, :tasks, :enum)
  end

  test "enum method does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.enum(1) }
  end

  test "exists? basic functionality" do
    assert_respond_to(@ts, :exists?)
    assert_boolean(@ts.exists?(@task))
  end

  test "exists? returns expected value" do
    setup_task
    assert_true(@ts.exists?(@task))
    assert_false(@ts.exists?('bogusXYZ'))
  end

  test "exists? method requires a single argument" do
    assert_raise(ArgumentError){ @ts.exists? }
    assert_raise(ArgumentError){ @ts.exists?('foo', 'bar') }
  end

  test "exit_code basic functionality" do
    setup_task
    assert_respond_to(@ts, :exit_code)
    assert_nothing_raised{ @ts.exit_code }
    assert_kind_of(Fixnum, @ts.exit_code)
  end

  test "exit_code takes no arguments" do
    assert_raise(ArgumentError){ @ts.exit_code(true) }
    assert_raise(NoMethodError){ @ts.exit_code = 1 }
  end

  test "machine= basic functionality" do
    assert_respond_to(@ts, :machine=)
  end

  test "machine= works as expected" do
    omit_unless(@@elevated)
    setup_task
    assert_nothing_raised{ @ts.machine = @@host }
    assert_equal(@@host, @ts.machine)
  end

  test "host= is an alias for machine=" do
    assert_alias_method(@ts, :machine=, :host=)
  end

  test "set_machine basic functionality" do
    assert_respond_to(@ts, :set_machine)
  end

  test "set_host is an alias for set_machine" do
    assert_alias_method(@ts, :set_machine, :set_host)
  end

  test "max_run_time basic functionality" do
    assert_respond_to(@ts, :max_run_time)
  end

  test "max_run_time works as expected" do
    setup_task
    assert_nothing_raised{ @ts.max_run_time }
    assert_kind_of(Fixnum, @ts.max_run_time)
  end

  test "max_run_time accepts no arguments" do
    assert_raise(ArgumentError){ @ts.max_run_time(true) }
  end

  test "max_run_time= basic functionality" do
    assert_respond_to(@ts, :max_run_time=)
  end

  test "max_run_time= works as expected" do
    setup_task
    assert_nothing_raised{ @ts.max_run_time = 20000 }
    assert_equal(20000, @ts.max_run_time)
  end

  test "max_run_time= requires a numeric argument" do
    assert_raise(TypeError){ @ts.max_run_time = true }
  end

  test "most_recent_run_time basic functionality" do
    setup_task
    assert_respond_to(@ts, :most_recent_run_time)
    assert_nothing_raised{ @ts.most_recent_run_time }
  end

  test "most_recent_run_time is nil if task hasn't run" do
    setup_task
    assert_nil(@ts.most_recent_run_time)
  end

  test "most_recent_run_time does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.most_recent_run_time(true) }
    assert_raise(NoMethodError){ @ts.most_recent_run_time = Time.now }
  end

  test "new_work_item basic functionality" do
    assert_respond_to(@ts, :new_work_item)
  end

  test "new_work_item accepts a trigger/hash" do
    assert_nothing_raised{ @ts.new_work_item('bar', @trigger) }
  end

  test "new_work_item fails if a bogus trigger key is present" do
    assert_raise(ArgumentError){ @ts.new_work_item('test', {:bogus => 1}) }
  end

  test "new_task is an alias for new_work_item" do
    assert_respond_to(@ts, :new_task)
    assert_alias_method(@ts, :new_task, :new_work_item)
  end

  test "new_work_item requires a task name and a trigger" do
    assert_raise(ArgumentError){ @ts.new_work_item }
    assert_raise(ArgumentError){ @ts.new_work_item('bar') }
  end

  test "new_work_item expects a string for the first argument" do
    assert_raise(TypeError){ @ts.new_work_item(1, @trigger) }
  end

  test "new_work_item expects a hash for the second argument" do
    assert_raise(TypeError){ @ts.new_work_item(@task, 1) }
  end

  test "next_run_time basic functionality" do
    assert_respond_to(@ts, :next_run_time)
  end

  test "next_run_time returns a Time object" do
    setup_task
    assert_nothing_raised{ @ts.next_run_time }
    assert_kind_of(Time, @ts.next_run_time)
  end

  test "next_run_time does not take any arguments" do
    assert_raise(ArgumentError){ @ts.next_run_time(true) }
    assert_raise(NoMethodError){ @ts.next_run_time = Time.now }
  end

  test "parameters basic functionality" do
    setup_task
    assert_respond_to(@ts, :parameters)
    assert_nothing_raised{ @ts.parameters }
  end

  test "parameters method does not take any arguments" do
    assert_raise(ArgumentError){ @ts.parameters('test') }
  end

  test "parameters= basic functionality" do
    assert_respond_to(@ts, :parameters=)
  end

  test "parameters= works as expected" do
    setup_task
    assert_nothing_raised{ @ts.parameters = "somefile.txt" }
    assert_equal("somefile.txt", @ts.parameters)
  end

  test "parameters= requires a string argument" do
    assert_raise(ArgumentError){ @ts.send(:parameters=) }
    assert_raise(TypeError){ @ts.parameters = 1 }
  end

  test "priority basic functionality" do
    assert_respond_to(@ts, :priority)
  end

  test "priority returns the expected value" do
    setup_task
    assert_nothing_raised{ @ts.priority }
    assert_kind_of(String, @ts.priority)
  end

  test "priority method does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.priority(true) }
  end

  test "priority= basic functionality" do
    assert_respond_to(@ts, :priority=)
  end

  test "priority= works as expected" do
    setup_task
    assert_nothing_raised{ @ts.priority = TaskScheduler::NORMAL }
    assert_equal('normal', @ts.priority)
  end

  test "priority= requires a numeric argument" do
    assert_raise(TypeError){ @ts.priority = 'alpha' }
  end

  # TODO: Find a harmless way to test this.
  test "run basic functionality" do
    assert_respond_to(@ts, :run)
  end

  test "run does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.run(true) }
    assert_raise(NoMethodError){ @ts.run = true }
  end

  test "save basic functionality" do
    assert_respond_to(@ts, :save)
  end

  test "status basic functionality" do
    setup_task
    assert_respond_to(@ts, :status)
    assert_nothing_raised{ @ts.status }
    assert_kind_of(String, @ts.status)
  end

  test "status returns the expected value" do
    setup_task
    assert_equal('ready', @ts.status)
  end

  test "status does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.status(true) }
  end

  test "terminate basic functionality" do
    assert_respond_to(@ts, :terminate)
  end

  test "terminate does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.terminate(true) }
    assert_raise(NoMethodError){ @ts.terminate = true }
  end

  test "calling terminate on a task that isn't running raises an error" do
    assert_raise(TaskScheduler::Error){ @ts.terminate }
  end

  test "trigger basic functionality" do
    setup_task
    assert_respond_to(@ts, :trigger)
    assert_nothing_raised{ @ts.trigger(0) }
  end

  test "trigger returns a hash object" do
    setup_task
    assert_kind_of(Hash, @ts.trigger(0))
  end

  test "trigger requires an index" do
    assert_raises(ArgumentError){ @ts.trigger }
  end

  test "trigger raises an error if the index is invalid" do
    assert_raises(TaskScheduler::Error){ @ts.trigger(9999) }
  end

  test "trigger= basic functionality" do
    assert_respond_to(@ts, :trigger=)
  end

  test "trigger= works as expected" do
    setup_task
    assert_nothing_raised{ @ts.trigger = @trigger }
  end

  test "trigger= requires a hash argument" do
    assert_raises(TypeError){ @ts.trigger = 'blah' }
  end

  test "add_trigger basic functionality" do
    assert_respond_to(@ts, :add_trigger)
  end

  test "add_trigger works as expected" do
    setup_task
    assert_nothing_raised{ @ts.add_trigger(0, @trigger) }
  end

  test "add_trigger requires two arguments" do
    assert_raises(ArgumentError){ @ts.add_trigger }
    assert_raises(ArgumentError){ @ts.add_trigger(0) }
  end

  test "add_trigger reqiuires an integer for the first argument" do
    assert_raises(TypeError){ @ts.add_trigger('foo', @trigger) }
  end

  test "add_trigger reqiuires a hash for the second argument" do
    assert_raises(TypeError){ @ts.add_trigger(0, 'foo') }
  end

  test "trigger_count basic functionality" do
    setup_task
    assert_respond_to(@ts, :trigger_count)
    assert_nothing_raised{ @ts.trigger_count }
    assert_kind_of(Fixnum, @ts.trigger_count)
  end

  test "trigger_count returns the expected value" do
    setup_task
    assert_equal(1, @ts.trigger_count)
  end

  test "trigger_count does not accept any arguments" do
    assert_raise(ArgumentError){ @ts.trigger_count(true) }
    assert_raise(NoMethodError){ @ts.trigger_count = 1 }
  end

  test "delete_trigger basic functionality" do
    assert_respond_to(@ts, :delete_trigger)
  end

  test "delete_trigger works as expected" do
    setup_task
    assert_nothing_raised{ @ts.delete_trigger(0) }
    assert_equal(0, @ts.trigger_count)
  end

  test "passing a bad index to delete_trigger will raise an error" do
    assert_raise(TaskScheduler::Error){ @ts.delete_trigger(9999) }
  end

  test "delete_trigger requires at least one argument" do
    assert_raise(ArgumentError){ @ts.delete_trigger }
  end

  test "trigger_string basic functionality" do
    setup_task
    assert_respond_to(@ts, :trigger_string)
    assert_nothing_raised{ @ts.trigger_string(0) }
  end

  test "trigger_string returns the expected value" do
    setup_task
    assert_equal('Starting 2015-04-11T07:14:00', @ts.trigger_string(0))
  end

  test "trigger_string requires a single argument" do
    assert_raise(ArgumentError){ @ts.trigger_string }
    assert_raise(ArgumentError){ @ts.trigger_string(0, 0) }
  end

  test "trigger_string requires a numeric argument" do
    assert_raise(TypeError){ @ts.trigger_string('alpha') }
  end

  test "trigger_string raises an error if the index is invalid" do
    assert_raise(TaskScheduler::Error){ @ts.trigger_string(9999) }
  end

  test "working_directory basic functionality" do
    setup_task
    assert_respond_to(@ts, :working_directory)
    assert_nothing_raised{ @ts.working_directory }
    assert_kind_of(String, @ts.working_directory)
  end

  test "working_directory takes no arguments" do
    assert_raise(ArgumentError){ @ts.working_directory(true) }
  end

  test "working_directory= basic functionality" do
    assert_respond_to(@ts, :working_directory=)
  end

  test "working_directory= works as expected" do
    setup_task
    assert_nothing_raised{ @ts.working_directory = "C:\\" }
  end

  test "working_directory= requires a string argument" do
    setup_task
    assert_raise(TypeError){ @ts.working_directory = 1 }
  end

  test "expected day constants are defined" do
    assert_not_nil(TaskScheduler::MONDAY)
    assert_not_nil(TaskScheduler::TUESDAY)
    assert_not_nil(TaskScheduler::WEDNESDAY)
    assert_not_nil(TaskScheduler::THURSDAY)
    assert_not_nil(TaskScheduler::FRIDAY)
    assert_not_nil(TaskScheduler::SATURDAY)
    assert_not_nil(TaskScheduler::SUNDAY)
  end

  test "expected month constants are defined" do
    assert_not_nil(TaskScheduler::JANUARY)
    assert_not_nil(TaskScheduler::FEBRUARY)
    assert_not_nil(TaskScheduler::MARCH)
    assert_not_nil(TaskScheduler::APRIL)
    assert_not_nil(TaskScheduler::MAY)
    assert_not_nil(TaskScheduler::JUNE)
    assert_not_nil(TaskScheduler::JULY)
    assert_not_nil(TaskScheduler::AUGUST)
    assert_not_nil(TaskScheduler::SEPTEMBER)
    assert_not_nil(TaskScheduler::OCTOBER)
    assert_not_nil(TaskScheduler::NOVEMBER)
    assert_not_nil(TaskScheduler::DECEMBER)
  end

  test "expected repeat constants are defined" do
    assert_not_nil(TaskScheduler::ONCE)
    assert_not_nil(TaskScheduler::DAILY)
    assert_not_nil(TaskScheduler::WEEKLY)
    assert_not_nil(TaskScheduler::MONTHLYDATE)
    assert_not_nil(TaskScheduler::MONTHLYDOW)
  end

  test "expected start constants are defined" do
    assert_not_nil(TaskScheduler::ON_IDLE)
    assert_not_nil(TaskScheduler::AT_SYSTEMSTART)
    assert_not_nil(TaskScheduler::AT_LOGON)
  end

  test "expected flag constants are defined" do
    assert_not_nil(TaskScheduler::INTERACTIVE)
    assert_not_nil(TaskScheduler::DELETE_WHEN_DONE)
    assert_not_nil(TaskScheduler::DISABLED)
    assert_not_nil(TaskScheduler::START_ONLY_IF_IDLE)
    assert_not_nil(TaskScheduler::KILL_ON_IDLE_END)
    assert_not_nil(TaskScheduler::DONT_START_IF_ON_BATTERIES)
    assert_not_nil(TaskScheduler::KILL_IF_GOING_ON_BATTERIES)
    assert_not_nil(TaskScheduler::HIDDEN)
    assert_not_nil(TaskScheduler::RESTART_ON_IDLE_RESUME)
    assert_not_nil(TaskScheduler::SYSTEM_REQUIRED)
    assert_not_nil(TaskScheduler::FLAG_HAS_END_DATE)
    assert_not_nil(TaskScheduler::FLAG_KILL_AT_DURATION_END)
    assert_not_nil(TaskScheduler::FLAG_DISABLED)
    assert_not_nil(TaskScheduler::MAX_RUN_TIMES)
  end

  test "expected priority constants are defined" do
    assert_not_nil(TaskScheduler::IDLE)
    assert_not_nil(TaskScheduler::NORMAL)
    assert_not_nil(TaskScheduler::HIGH)
    assert_not_nil(TaskScheduler::REALTIME)
    assert_not_nil(TaskScheduler::ABOVE_NORMAL)
    assert_not_nil(TaskScheduler::BELOW_NORMAL)
  end

  def teardown
    @ts.delete(@task) if @ts.exists?(@task)
    @ts.delete('bar') if @ts.exists?('bar')
    @ts = nil
    @task = nil
    @trigger = nil
  end

  def self.shutdown
    @@host = nil
    @@user = nil
    @@elevated = nil
  end
end