<?php

/**
 * i-Educar - Sistema de gestão escolar
 *
 * Copyright (C) 2006  Prefeitura Municipal de Itajaí
 *                     <ctima@itajai.sc.gov.br>
 *
 * Este programa é software livre; você pode redistribuí-lo e/ou modificá-lo
 * sob os termos da Licença Pública Geral GNU conforme publicada pela Free
 * Software Foundation; tanto a versão 2 da Licença, como (a seu critério)
 * qualquer versão posterior.
 *
 * Este programa é distribuí­do na expectativa de que seja útil, porém, SEM
 * NENHUMA GARANTIA; nem mesmo a garantia implí­cita de COMERCIABILIDADE OU
 * ADEQUAÇÃO A UMA FINALIDADE ESPECÍFICA. Consulte a Licença Pública Geral
 * do GNU para mais detalhes.
 *
 * Você deve ter recebido uma cópia da Licença Pública Geral do GNU junto
 * com este programa; se não, escreva para a Free Software Foundation, Inc., no
 * endereço 59 Temple Street, Suite 330, Boston, MA 02111-1307 USA.
 *
 * @author      Eriksen Costa Paixão <eriksen.paixao_bs@cobra.com.br>
 * @category    i-Educar
 * @license     @@license@@
 * @package     CoreExt_Entity
 * @subpackage  UnitTests
 * @since       Arquivo disponível desde a versão 1.1.0
 * @version     $Id$
 */

require_once 'CoreExt/Entity.php';
require_once 'CoreExt/_stub/Entity.php';
require_once 'CoreExt/_stub/EntityDataMapper.php';
require_once 'CoreExt/_stub/ParentEntity.php';
require_once 'CoreExt/_stub/ChildEntity.php';
require_once 'CoreExt/_stub/ChildEntityDataMapper.php';
require_once 'CoreExt/_stub/EnumSex.php';

/**
 * CoreExt_EntityTest class.
 *
 * @author      Eriksen Costa Paixão <eriksen.paixao_bs@cobra.com.br>
 * @category    i-Educar
 * @license     @@license@@
 * @package     CoreExt_Entity
 * @subpackage  UnitTests
 * @since       Classe disponível desde a versão 1.1.0
 * @version     @@package_version@@
 */
class CoreExt_EntityTest extends UnitBaseTest
{
  public function testNovaInstanciaEMarcadaComoNew()
  {
    $entity = new CoreExt_EntityStub();
    $this->assertTrue($entity->isNew());
  }

  public function testSetaAtributoDoComponente()
  {
    $esperado = 'fooBar';
    $entity = new CoreExt_EntityStub();
    $entity->nome = $esperado;
    $this->assertEquals($esperado, $entity->nome);
  }

  public function testPrintDaInstancia()
  {
    $this->outputBuffer(TRUE);
    print new CoreExt_EntityStub();
    $output = $this->outputBuffer(FALSE);
    $this->assertEquals('CoreExt_EntityStub', $output);
  }

  /**
   * @expectedException CoreExt_Exception_InvalidArgumentException
   */
  public function testSetarAtributoQueNaoExisteLancaExcecaoNaoVerificada()
  {
    $entity = new CoreExt_EntityStub();
    $entity->foo = 'bar';
  }

  public function testAtributoEstaSetado()
  {
    $entity = new CoreExt_EntityStub();
    $this->assertFalse(isset($entity->nome));
    $entity->nome = 'fooBar';
    $this->assertTrue(isset($entity->nome));
  }

  public function testAtributoFoiDestruido()
  {
    $entity = new CoreExt_EntityStub();
    $entity->nome = 'fooBar';
    $this->assertTrue(isset($entity->nome));
    unset($entity->nome);
    $this->assertFalse(isset($entity->nome));
  }

  public function testCampoIdentidadeEConsideradoNuloParaNaoNumericos()
  {
    $entity = new CoreExt_EntityStub();
    $entity->id = '';
    $this->assertNull($entity->id);
    $entity->id = '0';
    $this->assertNull($entity->id);
    $entity->id = 0;
    $this->assertNull($entity->id);
    $entity->id = NULL;
    $this->assertNull($entity->id);
  }

  public function testAutoConversaoParaBooleano()
  {
    $entity = new CoreExt_EntityStub();

    $entity->doador = 'f';
    $this->assertSame(FALSE, $entity->doador);
    $entity->doador = 0;
    $this->assertSame(FALSE, $entity->doador);

    $entity->doador = 't';
    $this->assertSame(TRUE, $entity->doador);
    $entity->doador = 1;
    $this->assertSame(TRUE, $entity->doador);
  }

  public function testNovaInstanciaENula()
  {
    $entity = new CoreExt_EntityStub();
    $this->assertTrue($entity->isNull());
  }

  public function testInstanciaComAlgumAtributoNaoNuloTornaseNaoNula()
  {
    $entity = new CoreExt_EntityStub();
    $entity->nome = 'fooBar';
    $this->assertFalse($entity->isNull());
  }

  /**
   * @group CoreExt_Locale
   */
  public function testAutoConversaoTiposNumericos()
  {
    $entity = new CoreExt_ChildEntityStub();
    $entity->peso = '12,5';
    $this->assertType('float', $entity->peso);
  }

  public function testSetaAtributosNaInstanciacao()
  {
    $data = array(
      'id' => 1,
      'nome' => 'fooBar',
      'estadoCivil' => 'solteiro',
      'doador' => TRUE
    );
    $entity = new CoreExt_EntityStub($data);
    $this->assertEquals($data, $entity->toArray());
  }

  public function testSetaDataMapper()
  {
    $expected = new CoreExt_EntityDataMapperStub();

    $entity = new CoreExt_EntityStub();
    $entity->setDataMapper($expected);

    $this->assertSame($expected, $entity->getDataMapper());
  }

  /**
   * @group CoreExt_Locale
   */
  public function testInstanciaRetornaObjetoCoreextLocalePorPadrao()
  {
    $entity = new CoreExt_EntityStub();
    $this->assertType('CoreExt_Locale', $entity->getLocale());
  }

  /**
   * @group CoreExt_Validate_Validatable
   */
  public function testConfiguraValidadorParaAtributo()
  {
    $entity = new CoreExt_EntityStub();
    $entity->setValidator('estadoCivil', new CoreExt_Validate_String());
    $this->assertType('CoreExt_Validate_String', $entity->getValidator('estadoCivil'));
  }

  /**
   * @expectedException Exception
   * @group CoreExt_Validate_Validatable
   */
  public function testConfigurarValidadorParaAtributoInexistenteLancaExcecao()
  {
    $entity = new CoreExt_EntityStub();
    $entity->setValidator('fooAttr', new CoreExt_Validate_String());
  }

  /**
   * @group CoreExt_Validate_Validatable
   */
  public function testAtributosDaInstanciaSaoValidos()
  {
    $data = array(
      'nome' => 'fooBar'
    );
    $entity = new CoreExt_EntityStub($data);
    $this->assertTrue($entity->isValid('nome'), 'Failed asserting isValid() for "nome" attribute.');
    $this->assertTrue($entity->isValid(), 'Failed asserting isValid() class\' attributes.');
    $this->assertFalse($entity->hasError('nome'));
    $this->assertFalse($entity->hasErrors());
  }

  /**
   * @group CoreExt_Validate_Validatable
   */
  public function testValidacaoGeralRetornaFalseSeUmAtributoForInvalido()
  {
    $data = array(
      'nome' => ''
    );
    $entity = new CoreExt_EntityStub($data);
    $this->assertFalse($entity->isValid());
    $this->assertEquals('Obrigatório.', $entity->getError('nome'));
    $this->assertTrue($entity->hasErrors());
  }

  /**
   * Testa com instância de CoreExt_Entity que contenha referências DataMapper.
   * @group Overload
   * @group CoreExt_Validate_Validatable
   * @see   testSetterDeReferenciaOverloadAtribuiValorParaAReferenciaENoAtributoQuandoUmaInstanciaDeCoreExtEntityComFieldIdentityEPassada
   */
  public function testValidacaoSanitizaValorDeAtributoComReferenciasDataMapper()
  {
    $data = array(
      'id'   => 1,
      'nome' => ' FooBar Jr ',
      'sexo' => 1,
      'tipoSanguineo' => 1,
      'peso' => '12,5'
    );
    $child = new CoreExt_ChildEntityStub($data);

    $data = array(
      'nome' => ' FooBar ',
      'filho' => $child
    );
    $entity = new CoreExt_ParentEntityStub($data);

    // Atribui validadores para os atributos
    $entity->setValidator('nome', new CoreExt_Validate_String());
    $entity->setValidator('filho', new CoreExt_Validate_Choice(array('choices' => array(1, 2))));

    // Valida e verifica pelos valores
    $entity->isValid();
    $this->assertEquals(1, $entity->get('filho'));
    $this->assertEquals('FooBar', $entity->nome);
  }

  /**
   * Testa com instância de CoreExt_Entity que contenha referências Enum.
   * @group CoreExt_Validate_Validatable
   */
  public function testValidacaoSanitizaValorDeAtributoComReferenciasEnum()
  {
    $data = array(
      'nome' => 'fooBar ',
      'sexo' => 1,
      'tipoSanguineo' => 1,
      'peso' => '12,5'
    );
    $entity = new CoreExt_ChildEntityStub($data);

    // Atribui validadores para os atributos
    $entity->setValidator('nome', new CoreExt_Validate_String());
    $entity->setValidator('sexo', new CoreExt_Validate_Choice(array('choices' => array(1, 2))));
    $entity->setValidator('tipoSanguineo', new CoreExt_Validate_Choice(array('choices' => array(1, 2))));
    $entity->setValidator('peso', new CoreExt_Validate_Numeric());

    // Valida e verifica pelos valores
    $this->assertTrue($entity->isValid());
    $this->assertEquals('fooBar', $entity->nome);
    $this->assertType('float', $entity->peso);
  }

  /**
   * @group CoreExt_Validate_Validatable
   */
  public function testCriaUmValidadorDependendoDoValorDeUmAtribudoDaInstancia()
  {
    $entity = new CoreExt_EntityStub(array('nome' => 'fooBar', 'estadoCivil' => ''));

    // Validador condicional
    $validator = $entity->validateIfEquals(
      'nome', 'fooBar', 'CoreExt_Validate_String', array('min' => 1, 'max' => 5), array('required' => FALSE)
    );

    // Retornou o validador do If
    $entity->setValidator('estadoCivil', $validator);
    $this->assertFalse($entity->isValid('estadoCivil'), 'Falhou na asserção de validateIfEquals() para caso If.');

    // Validador condicional
    $entity->nome = 'barFoo';
    $validator = $entity->validateIfEquals(
      'nome', 'fooBar', 'CoreExt_Validate_String', array('min' => 1, 'max' => 5), array('required' => FALSE)
    );

    // Retornou o validador do Else
    $entity->setValidator('estadoCivil', $validator);
    $this->assertTrue($entity->isValid('estadoCivil'), 'Falhou na asserção de validateIfEquals() para caso Else.');
  }

  /**
   * @expectedException CoreExt_Exception_InvalidArgumentException
   * @group CoreExt_Validate_Validatable
   */
  public function testMetodoDeCriacaoDeValidadorSensivelAoCasoLancaExcecaoQuandoClasseNaoESubclasseDeCoreextValidateAbstract()
  {
    $entity = new CoreExt_EntityStub();
    $entity->validateIfEquals(
      'nome', '', 'CoreExt_Validate_Abstract', array(), array()
    );
  }

  public function testTransformaEntityEmArrayDeValores()
  {
    $data = array(
      'id' => 1,
      'nome' => 'fooBar',
      'estadoCivil' => 'solteiro',
      'doador' => TRUE
    );
    $entity = new CoreExt_EntityStub($data);
    $array = $entity->filterAttr('id');
    $this->assertEquals(array(1), array_values($array));
  }

  public function testTransformaEntityEmArrayAssociativo()
  {
    $data = array(
      'id' => 1,
      'nome' => 'fooBar',
      'estadoCivil' => 'solteiro',
      'doador' => TRUE
    );
    $entity = new CoreExt_EntityStub($data);

    // Interface de instância
    $array = $entity->filterAttr('id', 'nome');
    $this->assertEquals(array(1 => 'fooBar'), $array);

    // Interface estática
    $array = CoreExt_Entity::entityFilterAttr($entity, 'id', 'nome');
    $this->assertEquals(array(1 => 'fooBar'), $array);
  }

  public function testTransformaEntitiesEmArrayAssociativo()
  {
    $data1 = $data2 = array(
      'id' => 1,
      'nome' => 'fooBar',
      'estadoCivil' => 'solteiro',
      'doador' => TRUE
    );

    $data2['id'] = 2;
    $data2['nome'] = 'barFoo';

    $entity1 = new CoreExt_EntityStub($data1);
    $entity2 = new CoreExt_EntityStub($data2);

    $entities = array($entity1, $entity2);

    $array = CoreExt_EntityStub::entityFilterAttr($entities, 'id', 'nome');

    $this->assertEquals(array(1 => 'fooBar', 2 => 'barFoo'), $array);
  }

  /**
   * @expectedException CoreExt_Exception_InvalidArgumentException
   */
  public function testSetterDeReferenciaParaAtributoInexistenteLancaExcecao()
  {
    $expected = new CoreExt_ChildEntityStub();
    $expected->setReference('foo', array());
  }

  /**
   * @expectedException CoreExt_Exception_InvalidArgumentException
   */
  public function testSetterDeReferenciaLancaExcecaoCasoArrayDeDefinicaoContenhaConfiguracaoInexistente()
  {
    $expected = new CoreExt_ChildEntityStub();
    $expected->setReference('nome', array('autoload' => TRUE));
  }

  /**
   * @expectedException CoreExt_Exception_InvalidArgumentException
   */
  public function testSetterDeReferenciaLancaExcecaoParaClasseCoreextDataMapperInvalida()
  {
    $expected = new CoreExt_ChildEntityStub();
    $expected->setReference('nome', array('class' => new stdClass()));
  }

  public function testSetterDeReferenciaOverloadAtribuiValorParaAReferenciaQuandoEInteiro()
  {
    $entity = new CoreExt_ParentEntityStub(array('id' => 1, 'nome' => 'fooBar', 'filho' => 1));
    $this->assertEquals(1, $entity->get('filho'));
  }

  public function testSetterDeReferenciaOverloadAtribuiValorNullParaAReferenciaQuandoElaENullable()
  {
    $entity = new CoreExt_ParentEntityStub(array('id' => 1, 'nome' => 'fooBar', 'filho' => NULL));
    $this->assertNull($entity->filho);
    $this->assertNull($entity->get('filho'));
  }

  /**
   * @group Overload
   */
  public function testSetterDeReferenciaOverloadAtribuiValorParaAReferenciaENoAtributoQuandoUmaInstanciaDeCoreExtEntityComFieldIdentityEPassada()
  {
    $child = new CoreExt_ChildEntityStub(array('id' => 1, 'nome' => 'FooBar Jr'));
    $entity = new CoreExt_ParentEntityStub(array('id' => 1, 'nome' => 'fooBar', 'filho' => $child));
    $this->assertEquals($child, $entity->filho);
    $this->assertEquals(1, $entity->get('filho'));
  }

  /**
   * @expectedException CoreExt_Exception_InvalidArgumentException
   */
  public function testSetterDeReferenciaOverloadLancaExcecaoParaAtribuicaoDeValorNullEmReferenciaNaoNullable()
  {
    $entity = new CoreExt_ChildEntityStub(array('id' => 1, 'sexo' => NULL));
  }

  public function testEntityRecuperadaPeloDataMapperEMarcadaComoVelha()
  {
    $entity = new CoreExt_EntityStub(array('nome' => 'fooBar'));
    $entity->markOld();

    $mapper = $this->getCleanMock('CoreExt_EntityDataMapperStub');
    $mapper->expects($this->once())
           ->method('find')
           ->with(1)
           ->will($this->returnValue($entity));

    $this->assertFalse($mapper->find(1)->isNew());
  }

  public function testReferenciaInstanciaAClasseAtribuidaAutomaticamente()
  {
    $parent = new CoreExt_ParentEntityStub(array('filho' => 1));
    $child  = new CoreExt_ChildEntityStub(array('id' => 1, 'nome' => 'fooBar', 'sexo' => 1));

    $filhoMapper = $this->getCleanMock('CoreExt_ChildEntityDataMapperStub');
    $filhoMapper->expects($this->once())
                ->method('find')
                ->with(1)
                ->will($this->returnValue($child));

    $parent->setReferenceClass('filho', $filhoMapper);

    $enum = CoreExt_EnumSexStub::getInstance();
    $expected = $enum[CoreExt_EnumSexStub::MALE];

    $this->assertEquals($child, $parent->filho);
    $this->assertEquals($expected, $child->sexo);
  }

  public function testInstanciaDeClassNaoEspecificaEArmazenadaNoArrayStaticDaClasse()
  {
    CoreExt_EntityStub::addClassToStorage('StdClassExtStub',
      NULL, 'CoreExt/_stub/StdClassExt.php');
    $this->assertType('StdClassExtStub', CoreExt_EntityStub::getClassFromStorage('StdClassExtStub'));
  }

  public function testInstanciaDeClassNaoEspecificaArmazenaInstanciaEspecifica()
  {
    $obj = new stdClass();
    $oid = spl_object_hash($obj);
    CoreExt_EntityStub::addClassToStorage('stdClass', $obj);
    $this->assertType('stdClass', CoreExt_EntityStub::getClassFromStorage('stdClass'));
    $this->assertEquals($oid, spl_object_hash(CoreExt_EntityStub::getClassFromStorage('stdClass')));
  }

  public function testInstanciaDeClassNaoEspecificaPodeSerRepostaPorNovaInstancia()
  {
    // Só para facilitar.
    for ($i = 0; $i <= 1; $i++) {
      $obj = new stdClass();
      $obj->i = $i;
      $oid = spl_object_hash($obj);
      CoreExt_EntityStub::addClassToStorage('stdClass', $obj);
      $this->assertType('stdClass', CoreExt_EntityStub::getClassFromStorage('stdClass'));
      $this->assertEquals($oid, spl_object_hash(CoreExt_EntityStub::getClassFromStorage('stdClass')));
    }
    CoreExt_EntityStub::addClassToStorage('stdClass');
    $this->assertType('stdClass', CoreExt_EntityStub::getClassFromStorage('stdClass'));
    $this->assertEquals($oid, spl_object_hash(CoreExt_EntityStub::getClassFromStorage('stdClass')));
  }

  public function testInstanciaDeClassESobresritaPorInstanciasSticky()
  {
    $oid1 = CoreExt_EntityStub::addClassToStorage('stdClass', new stdClass(), NULL, TRUE);
    $oid1 = spl_object_hash($oid1);

    $oid2 = CoreExt_EntityStub::addClassToStorage('stdClass', new stdClass(), NULL, TRUE);
    $oid2 = spl_object_hash($oid2);

    $this->assertNotEquals($oid2, $oid1);
  }

  public function testInstanciaDeClassNaoEspecificaRetornaNullQuandoNaoEncontraUmaInstanciaDaClasse()
  {
    $this->assertNull(CoreExt_EntityStub::getClassFromStorage('fooBar'));
  }

  /**
   * @expectedException CoreExt_Exception_InvalidArgumentException
   */
  public function testInstanciaDeClassNaoEspecificaLancaExcecaoQuandoInstanciaPassadaNaoTemAMesmaAssinaturaDoNomeDeClasseInformado()
  {
    CoreExt_EntityStub::addClassToStorage('fooBar', new stdClass());
  }

  public function testLazyLoadParaReferenciaNumericaAInstanciaEntity()
  {
    $data = array(
      'nome' => 'fooBar',
      'filho' => 1
    );

    $expected = new CoreExt_ChildEntityStub(array('id' => 1, 'nome' => 'barFoo'));

    $filhoMapper = $this->getCleanMock('CoreExt_ChildEntityDataMapperStub');
    $filhoMapper->expects($this->once())
                ->method('find')
                ->with(1)
                ->will($this->returnValue($expected));

    $parent = new CoreExt_ParentEntityStub($data);
    $parent->setReference('filho', array('value' => 1, 'class' => $filhoMapper));

    $this->assertType('CoreExt_ChildEntityStub', $parent->filho);
    $this->assertEquals(1, $parent->filho->id);
  }

  /**
   * @group LazyLoad
   * @group CoreExt_Validate_Validatable
   */
  public function testLazyLoadNaoCarregaInstanciaEntityParaValidacao()
  {
    $data = array(
      'nome' => 'fooBar',
    );

    $expected = new CoreExt_ChildEntityStub(array('id' => 1, 'nome' => 'barFoo'));

    $filhoMapper = $this->getCleanMock('CoreExt_ChildEntityDataMapperStub');
    $filhoMapper->expects($this->never())
                ->method('find');

    $parent = new CoreExt_ParentEntityStub($data);
    $parent->setReference('filho', array('value' => 1, 'class' => $filhoMapper));

    $this->assertTrue($parent->isValid());
  }

  public function testLazyLoadParaReferenciaObjetoAInstanciaEntityNaoCausaLazyLoad()
  {
    $expected = new CoreExt_ChildEntityStub(array('id' => 1, 'nome' => 'barFoo'));

    $data = array(
      'id'    => 1,
      'nome'  => 'fooBar',
      'filho' => $expected
    );

    // O método "find" não deve ser chamado pois "child" já existe como atributo
    // de "parent"
    $filhoMapper = $this->getCleanMock('CoreExt_ChildEntityDataMapperStub');
    $filhoMapper->expects($this->never())
                ->method('find');

    // Configurando com mock, para ter certeza que não irá tentar carregar
    // "child"
    $parent = new CoreExt_ParentEntityStub($data);
    $parent->setReference('filho', array('value' => 1, 'class' => $filhoMapper));

    $this->assertType('CoreExt_ChildEntityStub', $parent->filho);
    $this->assertEquals(1, $parent->filho->id);
    $this->assertEquals('barFoo', $parent->filho->nome);

    // toArray() causa chamadas lazy load. Garantindo que não ocorra.
    $this->assertEquals($data, $parent->toArray());

    // toDataArray() retorna sempre a referência numérica.
    $data['filho'] = 1;
    $this->assertEquals($data, $parent->toDataArray());
  }

  public function testLazyLoadNaoERealizadoQuandoReferenciaIgualA0OuNulo()
  {
    $expected = new CoreExt_ChildEntityStub(array('id' => 1, 'nome' => 'barFoo'));

    $data = array(
      'id'    => 1,
      'nome'  => 'fooBar'
    );

    // O método "find" não deve ser chamado pois "child" já existe como atributo
    // de "parent"
    $filhoMapper = $this->getCleanMock('CoreExt_ChildEntityDataMapperStub');
    $filhoMapper->expects($this->never())
                ->method('find');

    // Configurando com mock, para ter certeza que não irá tentar carregar
    // "child"
    $parent = new CoreExt_ParentEntityStub($data);

    $this->assertNull($parent->filho);
  }

  /**
   * @expectedException CoreExt_Exception_InvalidArgumentException
   */
  public function testLazyLoadParametroQueEReferenciaLancaExcecaoQuandoNaoEDoTipoIntegerOuCoreextEntity()
  {
    $expected = new stdClass();

    $data = array(
      'id'    => 1,
      'nome'  => 'fooBar',
      'filho' => $expected
    );

    $parent = new CoreExt_ParentEntityStub($data);
  }

  public function testLazyLoadDeReferenciaAUmTipoCoreextEnum()
  {
    $enum = CoreExt_EnumSexStub::getInstance();

    $child1 = new CoreExt_ChildEntityStub(array('id' => 1, 'nome' => 'barFoo'));
    $child2 = new CoreExt_ChildEntityStub(array('id' => 1, 'nome' => 'barFoo'));

    $child1->setReference('sexo', array('value' => 1, 'class' => $enum));
    $this->assertEquals('masculino', $child1->sexo);

    $child2->setReference('sexo', array('value' => 2, 'class' => $enum));
    $this->assertEquals('feminino', $child2->sexo);
  }
}