#--
# =============================================================================
# Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu)
# All rights reserved.
#
# This source file is distributed as part of the Needle dependency injection
# library for Ruby. This file (and the library as a whole) may be used only as
# allowed by either the BSD license, or the Ruby license (or, by association
# with the Ruby license, the GPL). See the "doc" subdirectory of the Needle
# distribution for the texts of these licenses.
# -----------------------------------------------------------------------------
# needle website : http://needle.rubyforge.org
# project website: http://rubyforge.org/projects/needle
# =============================================================================
#++

$:.unshift "../lib"

require 'needle/container'
require 'test/unit'

class TC_Container < Test::Unit::TestCase

  class TC_RegistrationContext < Test::Unit::TestCase
    class MockContainer
      attr_reader :events
      def initialize; @events = []; end
      def method_missing(s,*a,&b)
        @events << { :name => s, :args => a, :block => b }
      end
    end

    def setup
      @container = MockContainer.new
      @ctx = Needle::Container::RegistrationContext.new( @container )
    end

    def test_register
      assert_nothing_raised do
        @ctx.hello { "world" }
      end
      assert_equal :register, @container.events[0][:name]
      assert_equal [ :hello ], @container.events[0][:args]
      assert_not_nil @container.events[0][:block]
    end

    def test_reference_bad
      assert_raise( NoMethodError ) do
        @ctx.hello( :arg )
      end
    end

    def test_register_good
      assert_nothing_raised do
        @ctx.hello 
      end
      assert_equal :[], @container.events[0][:name]
      assert_equal [ :hello ], @container.events[0][:args]
      assert_nil @container.events[0][:block]
    end

    def test_intercept
      assert_nothing_raised do
        @ctx.intercept( :foo )
      end
      assert_equal :intercept, @container.events[0][:name]
      assert_equal [ :foo ], @container.events[0][:args]
      assert_nil @container.events[0][:block]
    end

  end

  class Model
    def initialize( container, opts={}, &callback )
      @container, @callback = container, callback
      @inst = nil
    end
    def instance
      return @inst if @inst
      @inst = @callback.call( @container )
    end
  end

  def test_default
    container = Needle::Container.new
    assert_nil container.parent
    assert_nil container.name
    assert_equal container, container.root
    assert_equal "", container.fullname
  end

  def test_named
    container = Needle::Container.new( nil, "name" )
    assert_nil container.parent
    assert_equal "name", container.name
    assert_equal container, container.root
    assert_equal "name", container.fullname
  end

  def test_nested
    outer = Needle::Container.new
    inner = Needle::Container.new( outer )
    assert_same outer, inner.parent
    assert_equal outer, inner.root
  end

  def test_root
    outer = Needle::Container.new
    middle = Needle::Container.new( outer )
    inner = Needle::Container.new( middle )
    assert_same middle, inner.parent
    assert_equal outer, inner.root
  end

  def test_nested_named
    outer = Needle::Container.new( nil, "outer" )
    inner = Needle::Container.new( outer, "inner" )
    assert_equal "inner", inner.name
    assert_equal "outer.inner", inner.fullname
  end

  def test_service_not_found
    container = Needle::Container.new
    assert_raise( Needle::ServiceNotFound ) do
      container[:test]
    end
  end

  def test_register
    container = Needle::Container.new
    container.register( :test, :model=>Model ) { Hash.new }

    assert_nothing_raised { container[:test] }
    assert_nothing_raised { container.test }

    assert_instance_of Hash, container[:test]
    assert_instance_of Hash, container.test

    assert container.respond_to?(:test)
  end

  def test_register!
    container = Needle::Container.new

    container.register! do
      test( :model=>Model ) { Hash.new }
      namespace :subitem, :model=>Model do
        test2( :model=>Model ) { Hash.new }
      end
    end

    assert container.has_key?( :test )
    assert_instance_of Hash, container.test
    assert container.subitem.has_key?( :test2 )
    assert_instance_of Hash, container.subitem.test2
  end

  def test_namespace
    container = Needle::Container.new
    container.namespace( :test, :model=>Model )
    assert_instance_of Needle::Container, container.test

    container.namespace( :test2, :model=>Model ) do |ns|
      assert_instance_of Needle::Container, ns
    end

    assert_instance_of Needle::Container, container.test2
  end

  def test_multi_namespace
    container = Needle::Container.new
    container.namespace( :test, :test2, :test3, :model=>Model )
    assert_instance_of Needle::Container, container.test
    assert_instance_of Needle::Container, container.test.test2
    assert_instance_of Needle::Container, container.test.test2.test3

    container.namespace( :test, :test2, :test3, :model=>Model ) do |ns|
      assert_same ns, container.test.test2.test3
    end
  end

  def test_has_key
    container = Needle::Container.new

    assert !container.has_key?(:test)
    container.register( :test, :model=>Model ) { Hash.new }
    assert container.has_key?(:test)
  end

  def test_knows_key
    container = Needle::Container.new

    assert !container.knows_key?(:test)
    container.register( :test, :model=>Model ) { Hash.new }
    assert container.knows_key?(:test)
  end

  def test_parent_knows_key
    outer = Needle::Container.new
    inner = Needle::Container.new( outer )

    outer.register( :test, :model=>Model ) { Hash.new }
    assert !inner.has_key?(:test)
    assert inner.knows_key?(:test)
  end

  def test_service_in_parent
    outer = Needle::Container.new
    inner = Needle::Container.new( outer )

    outer.register( :test, :model=>Model ) { Hash.new }
    assert_nothing_raised do
      inner[:test]
    end
  end

  def test_service_not_in_parent
    outer = Needle::Container.new
    inner = Needle::Container.new( outer )

    assert_raise( Needle::ServiceNotFound ) do
      inner[:test]
    end
  end

  def test_intercept_not_found
    container = Needle::Container.new
    assert_raise( Needle::ServiceNotFound ) do
      container.intercept( :test )
    end
  end

  def test_intercept
    container = Needle::Container.new
    container.register( :test, :model=>Model ) { Hash.new }

    filtered = false
    container.intercept( :test ).doing { |chain,ctx| filtered = true; chain.process_next(ctx) }

    assert !filtered
    svc = container.test
    svc[:hello] = :world
    assert filtered
  end

end