<?php
/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         3.3.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Test\TestCase\Datasource;

use Cake\Datasource\FactoryLocator;
use Cake\TestSuite\TestCase;

/**
 * FactoryLocatorTest test case
 */
class FactoryLocatorTest extends TestCase
{
    /**
     * Test get factory
     *
     * @return void
     */
    public function testGet()
    {
        $this->assertInternalType('callable', FactoryLocator::get('Table'));
    }

    /**
     * Test get non existing factory
     *
     * @return void
     */
    public function testGetNonExisting()
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('Unknown repository type "Test". Make sure you register a type before trying to use it.');
        FactoryLocator::get('Test');
    }

    /**
     * test add()
     *
     * @return void
     */
    public function testAdd()
    {
        FactoryLocator::add('Test', function ($name) {
            $mock = new \StdClass();
            $mock->name = $name;

            return $mock;
        });

        $this->assertInternalType('callable', FactoryLocator::get('Test'));
    }

    /**
     * test drop()
     *
     * @return void
     */
    public function testDrop()
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('Unknown repository type "Test". Make sure you register a type before trying to use it.');
        FactoryLocator::drop('Test');

        FactoryLocator::get('Test');
    }

    /**
     * test loadModel() with plugin prefixed models
     *
     * Load model should not be called with Foo.Model Bar.Model Model
     * But if it is, the first call wins.
     *
     * @return void
     */
    public function testLoadModelPlugin()
    {
        $stub = new Stub();
        $stub->setProps('Articles');
        $stub->setModelType('Table');

        $result = $stub->loadModel('TestPlugin.Comments');
        $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $result);
        $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $stub->Comments);

        $result = $stub->loadModel('Comments');
        $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $result);
        $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $stub->Comments);
    }

    /**
     * test alternate model factories.
     *
     * @return void
     */
    public function testModelFactory()
    {
        $stub = new Stub();
        $stub->setProps('Articles');

        $stub->modelFactory('Table', function ($name) {
            $mock = new \StdClass();
            $mock->name = $name;

            return $mock;
        });

        $result = $stub->loadModel('Magic', 'Table');
        $this->assertInstanceOf('\StdClass', $result);
        $this->assertInstanceOf('\StdClass', $stub->Magic);
        $this->assertEquals('Magic', $stub->Magic->name);
    }

    /**
     * test alternate default model type.
     *
     * @return void
     */
    public function testModelType()
    {
        $stub = new Stub();
        $stub->setProps('Articles');

        FactoryLocator::add('Test', function ($name) {
            $mock = new \StdClass();
            $mock->name = $name;

            return $mock;
        });
        $stub->setModelType('Test');

        $result = $stub->loadModel('Magic');
        $this->assertInstanceOf('\StdClass', $result);
        $this->assertInstanceOf('\StdClass', $stub->Magic);
        $this->assertEquals('Magic', $stub->Magic->name);
    }

    /**
     * test MissingModelException being thrown
     *
     * @return void
     */
    public function testMissingModelException()
    {
        $this->expectException(\Cake\Datasource\Exception\MissingModelException::class);
        $this->expectExceptionMessage('Model class "Magic" of type "Test" could not be found.');
        $stub = new Stub();

        FactoryLocator::add('Test', function ($name) {
            return false;
        });

        $stub->loadModel('Magic', 'Test');
    }

    public function tearDown()
    {
        FactoryLocator::drop('Test');

        parent::tearDown();
    }
}
