<commit_before><?php

/*
 * This file is part of the Spira framework.
 *
 * @link https://github.com/spira/spira
 *
 * For the full copyright and license information, please view the LICENSE file that was distributed with this source code.
 */

use App\Models\TestEntity;
use Rhumsaa\Uuid\Uuid;

/**
 * Class EntityTest.
 * @group integration
 */
class EntityTest extends TestCase
{
    public function setUp()
    {
        parent::setUp();

        // Workaround for model event firing.
        // The package Bosnadev\Database used for automatic UUID creation relies
        // on model events (creating) to generate the UUID.
        //
        // Laravel/Lumen currently doesn't fire repeated model events during
        // unit testing, see: https://github.com/laravel/framework/issues/1181
        App\Models\TestEntity::flushEventListeners();
        App\Models\TestEntity::boot();
    }

    /**
     * Prepare a factory generated entity to be sent as input data.
     *
     * @param Arrayable $entity
     *
     * @return array
     */
    protected function prepareEntity($entity)
    {
        // We run the entity through the transformer to get the attributes named
        // as if they came from the frontend.
        $transformer = $this->app->make(\App\Http\Transformers\EloquentModelTransformer::class);
        $entity = $transformer->transform($entity);

        // As the hidden attribute is hidden when we get the array, we need to
        // add it back so it's part of the request.
        $entity['hidden'] = true;

        // And get a reformatted date
        $entity['date'] = Carbon\Carbon::createFromFormat('Y-m-d', substr($entity['date'], 0, 10))->toDateString();

        return $entity;
    }

    protected function addRelatedEntities($model)
    {
        $model->testMany = factory(App\Models\SecondTestEntity::class, 5)->make()->all();
        $model->push();
    }

    public function testGetAll()
    {
        $this->getJson('/test/entities');

        $this->assertResponseOk();
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();
    }

    public function testGetAllWithNested()
    {
        $entity = factory(App\Models\TestEntity::class)->create();
        $this->addRelatedEntities($entity);

        $this->getJson('/test/entities', ['with-nested' => 'testMany']);
        $objects = json_decode($this->response->getContent());

        $this->assertResponseOk();
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();

        $asserted = false;
        foreach ($objects as $object) {
            if (count($object->_testMany) == 5) {
                $asserted = true;
                $this->assertEquals(5, count($object->_testMany));
                foreach ($object->_testMany as $nestedObject) {
                    $this->assertObjectHasAttribute('_self', $nestedObject);
                    $this->assertTrue(is_string($nestedObject->_self), '_self is a string');
                    $this->assertObjectHasAttribute('entityId', $nestedObject);
                    $this->assertStringMatchesFormat('%x-%x-%x-%x-%x', $nestedObject->entityId);
                    $this->assertTrue(strlen($nestedObject->entityId) === 36, 'UUID has 36 chars');
                }
            }
        }

        $this->assertTrue($asserted, 'There was nested entity inside answer');
    }

    public function testGetAllPaginated()
    {
        $defaultLimit = 10;
        $entities = factory(App\Models\TestEntity::class, $defaultLimit + 1)->create();
        $this->getJson('/test/entities/pages', ['Range' => 'entities=0-']);
        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();

        $object = json_decode($this->response->getContent());
        $this->assertEquals($defaultLimit, count($object));
    }

    public function testGetAllPaginatedWithNested()
    {
        $defaultLimit = 10;
        $entities = factory(App\Models\TestEntity::class, $defaultLimit + 1)->create();
        foreach ($entities as $entity) {
            $this->addRelatedEntities($entity);
        }

        $this->getJson('/test/entities/pages', ['Range' => 'entities=-10','with-nested' => 'testMany']);
        $object = json_decode($this->response->getContent());

        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertEquals($defaultLimit, count($object));
        $object = current($object);

        $this->assertObjectHasAttribute('_testMany', $object);
        $this->assertEquals(5, count($object->_testMany));
        foreach ($object->_testMany as $nestedObject) {
            $this->assertObjectHasAttribute('_self', $nestedObject);
            $this->assertTrue(is_string($nestedObject->_self), '_self is a string');
            $this->assertObjectHasAttribute('entityId', $nestedObject);
            $this->assertStringMatchesFormat('%x-%x-%x-%x-%x', $nestedObject->entityId);
            $this->assertTrue(strlen($nestedObject->entityId) === 36, 'UUID has 36 chars');
        }
    }

    public function testGetAllPaginatedNoRangeHeader()
    {
        $this->getJson('/test/entities/pages');
        $this->assertResponseStatus(400);
    }

    public function testGetAllPaginatedInvalidRangeHeader()
    {
        $this->getJson('/test/entities/pages', ['Range' => '0-']);
        $this->assertResponseStatus(400);
    }

    public function testGetAllPaginatedSimpleRange()
    {
        $entities = factory(App\Models\TestEntity::class, 20)->create();
        $totalCount = TestEntity::count();
        $this->getJson('/test/entities/pages', ['Range' => 'entities=0-19']);
        $object = json_decode($this->response->getContent());
        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();
        $this->assertEquals(20, count($object));
        list($first, $last, $total) = $this->parseRange($this->response->headers->get('content-range'));
        $this->assertEquals($total, $totalCount);
        $this->assertEquals($first, 0);
        $this->assertEquals($last, 19);
    }

    public function testPaginationBadRanges()
    {
        $entities = factory(App\Models\TestEntity::class, 20)->create();
        $this->getJson('/test/entities/pages', ['Range' => 'entities=19-18']);
        $this->assertResponseStatus(400);
    }

    public function testPaginationOutOfRange()
    {
        $entities = factory(App\Models\TestEntity::class, 10)->create();
        $totalCount = TestEntity::count();
        $this->getJson('/test/entities/pages', ['Range' => 'entities='.$totalCount.'-']);
        $this->assertResponseStatus(416);
    }

    public function testPaginationMoreThanInRepo()
    {
        $entities = factory(App\Models\TestEntity::class, 10)->create();
        $totalCount = TestEntity::count();
        $this->getJson('/test/entities/pages', ['Range' => 'entities='.($totalCount - 2).'-'.($totalCount + 20)]);
        $object = json_decode($this->response->getContent());
        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();
        $this->assertEquals(2, count($object));
        list($first, $last, $total) = $this->parseRange($this->response->headers->get('content-range'));
        $this->assertEquals($total, $totalCount);
        $this->assertEquals($first, $totalCount - 2);
        $this->assertEquals($last, $totalCount - 1);
    }

    public function testPaginationGetLast()
    {
        $entities = factory(App\Models\TestEntity::class, 10)->create();
        $totalCount = TestEntity::count();
        $this->getJson('/test/entities/pages', ['Range' => 'entities=-5']);
        $object = json_decode($this->response->getContent());
        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();
        $this->assertEquals(5, count($object));
        list($first, $last, $total) = $this->parseRange($this->response->headers->get('content-range'));
        $this->assertEquals($total, $totalCount);
        $this->assertEquals($first, $totalCount - 5);
        $this->assertEquals($last, $totalCount - 1);
    }

    protected function parseRange($header)
    {
        $splitTotal = explode('/', str_replace('entities ', '', $header));
        $total = null;
        if (isset($splitTotal[1]) && $splitTotal[1] !== '') {
            $total = $splitTotal[1];
        }
        $firstAndLast = explode('-', $splitTotal[0]);
        $first = null;
        $last = null;
        if (isset($firstAndLast[0]) && $firstAndLast[0] !== '') {
            $first = $firstAndLast[0];
        }

        if (isset($firstAndLast[1]) && $firstAndLast[1] !== '') {
            $last = $firstAndLast[1];
        }

        return [$first,$last,$total];
    }

    public function testGetOne()
    {
        $entity = factory(App\Models\TestEntity::class)->create();

        $this->getJson('/test/entities/'.$entity->entity_id);

        $this->assertResponseOk();
        $this->shouldReturnJson();

        $object = json_decode($this->response->getContent());

        $this->assertTrue(is_object($object), 'Response is an object');

        $this->assertObjectHasAttribute('_self', $object);
        $this->assertTrue(is_string($object->_self), '_self is a string');

        $this->assertObjectHasAttribute('entityId', $object);
        $this->assertStringMatchesFormat('%x-%x-%x-%x-%x', $object->entityId);
        $this->assertTrue(strlen($object->entityId) === 36, 'UUID has 36 chars');

        $this->assertTrue(is_string($object->varchar), 'Varchar column type is text');
        $this->assertTrue(is_string($object->hash), 'Hash column is a hash');
        $this->assertTrue(is_int($object->integer), 'Integer column type is integer');
        $this->assertTrue(is_float($object->decimal) || is_int($object->decimal), 'Decimal column type is integer');
        $this->assertNull($object->nullable, 'Nullable column type is null');
        $this->assertTrue(is_string($object->text), 'Text column type is text');
        $this->assertValidIso8601Date($object->date, 'Date column type is a valid ISO8601 date');
        $this->assertObjectHasAttribute('multiWordColumnTitle', $object, 'Multi word colum is camel cased');

        $this->assertValidIso8601Date($object->createdAt, 'createdAt column is a valid ISO8601 date');
        $this->assertValidIso8601Date($object->updatedAt, 'updatedAt column is a valid ISO8601 date');

        $this->assertObjectNotHasAttribute('hidden', $object, 'Hidden is not hidden.');
    }

    public function testGetOneWithNested()
    {
        $entity = factory(App\Models\TestEntity::class)->create();
        $this->addRelatedEntities($entity);

        $this->getJson('/test/entities/'.$entity->entity_id, ['with-nested' => 'testMany']);
        $object = json_decode($this->response->getContent());

        $this->assertResponseOk();
        $this->shouldReturnJson();

        $this->assertTrue(is_object($object), 'Response is an object');
        $this->assertObjectHasAttribute('_self', $object);
        $this->assertTrue(is_string($object->_self), '_self is a string');

        $this->assertObjectHasAttribute('_testMany', $object);
        $this->assertEquals(5, count($object->_testMany));
        foreach ($object->_testMany as $nestedObject) {
            $this->assertObjectHasAttribute('_self', $nestedObject);
            $this->assertTrue(is_string($nestedObject->_self), '_self is a string');
            $this->assertObjectHasAttribute('entityId', $nestedObject);
            $this->assertStringMatchesFormat('%x-%x-%x-%x-%x', $nestedObject->entityId);
            $this->assertTrue(strlen($nestedObject->entityId) === 36, 'UUID has 36 chars');
        }
    }

    public function testGetOneWithInvalidNested()
    {
        $this->markTestSkipped("Skipped until https://github.com/laravel/framework/pull/10309 is fixed");

        $entity = factory(App\Models\TestEntity::class)->create();
        $this->addRelatedEntities($entity);

        $this->getJson('/test/entities/'.$entity->entity_id, ['with-nested' => 'not-a-valid-nesting']);

        $this->shouldReturnJson();
        $this->assertResponseStatus(400);
    }

    public function testPostOneValid()
    {
        $entity = factory(App\Models\TestEntity::class)->make();

        $this->postJson('/test/entities', $this->prepareEntity($entity));

        $this->shouldReturnJson();

        $object = json_decode($this->response->getContent());

        $this->assertResponseStatus(201);
        $this->assertTrue(is_object($object));
        $this->assertStringStartsWith('http', $object->_self);<commit_msg>Reverted invalid nested entity test that was failing due to https://github.com/laravel/framework/pull/10309<commit_after><?php

/*
 * This file is part of the Spira framework.
 *
 * @link https://github.com/spira/spira
 *
 * For the full copyright and license information, please view the LICENSE file that was distributed with this source code.
 */

use App\Models\TestEntity;
use Rhumsaa\Uuid\Uuid;

/**
 * Class EntityTest.
 * @group integration
 */
class EntityTest extends TestCase
{
    public function setUp()
    {
        parent::setUp();

        // Workaround for model event firing.
        // The package Bosnadev\Database used for automatic UUID creation relies
        // on model events (creating) to generate the UUID.
        //
        // Laravel/Lumen currently doesn't fire repeated model events during
        // unit testing, see: https://github.com/laravel/framework/issues/1181
        App\Models\TestEntity::flushEventListeners();
        App\Models\TestEntity::boot();
    }

    /**
     * Prepare a factory generated entity to be sent as input data.
     *
     * @param Arrayable $entity
     *
     * @return array
     */
    protected function prepareEntity($entity)
    {
        // We run the entity through the transformer to get the attributes named
        // as if they came from the frontend.
        $transformer = $this->app->make(\App\Http\Transformers\EloquentModelTransformer::class);
        $entity = $transformer->transform($entity);

        // As the hidden attribute is hidden when we get the array, we need to
        // add it back so it's part of the request.
        $entity['hidden'] = true;

        // And get a reformatted date
        $entity['date'] = Carbon\Carbon::createFromFormat('Y-m-d', substr($entity['date'], 0, 10))->toDateString();

        return $entity;
    }

    protected function addRelatedEntities($model)
    {
        $model->testMany = factory(App\Models\SecondTestEntity::class, 5)->make()->all();
        $model->push();
    }

    public function testGetAll()
    {
        $this->getJson('/test/entities');

        $this->assertResponseOk();
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();
    }

    public function testGetAllWithNested()
    {
        $entity = factory(App\Models\TestEntity::class)->create();
        $this->addRelatedEntities($entity);

        $this->getJson('/test/entities', ['with-nested' => 'testMany']);
        $objects = json_decode($this->response->getContent());

        $this->assertResponseOk();
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();

        $asserted = false;
        foreach ($objects as $object) {
            if (count($object->_testMany) == 5) {
                $asserted = true;
                $this->assertEquals(5, count($object->_testMany));
                foreach ($object->_testMany as $nestedObject) {
                    $this->assertObjectHasAttribute('_self', $nestedObject);
                    $this->assertTrue(is_string($nestedObject->_self), '_self is a string');
                    $this->assertObjectHasAttribute('entityId', $nestedObject);
                    $this->assertStringMatchesFormat('%x-%x-%x-%x-%x', $nestedObject->entityId);
                    $this->assertTrue(strlen($nestedObject->entityId) === 36, 'UUID has 36 chars');
                }
            }
        }

        $this->assertTrue($asserted, 'There was nested entity inside answer');
    }

    public function testGetAllPaginated()
    {
        $defaultLimit = 10;
        $entities = factory(App\Models\TestEntity::class, $defaultLimit + 1)->create();
        $this->getJson('/test/entities/pages', ['Range' => 'entities=0-']);
        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();

        $object = json_decode($this->response->getContent());
        $this->assertEquals($defaultLimit, count($object));
    }

    public function testGetAllPaginatedWithNested()
    {
        $defaultLimit = 10;
        $entities = factory(App\Models\TestEntity::class, $defaultLimit + 1)->create();
        foreach ($entities as $entity) {
            $this->addRelatedEntities($entity);
        }

        $this->getJson('/test/entities/pages', ['Range' => 'entities=-10','with-nested' => 'testMany']);
        $object = json_decode($this->response->getContent());

        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertEquals($defaultLimit, count($object));
        $object = current($object);

        $this->assertObjectHasAttribute('_testMany', $object);
        $this->assertEquals(5, count($object->_testMany));
        foreach ($object->_testMany as $nestedObject) {
            $this->assertObjectHasAttribute('_self', $nestedObject);
            $this->assertTrue(is_string($nestedObject->_self), '_self is a string');
            $this->assertObjectHasAttribute('entityId', $nestedObject);
            $this->assertStringMatchesFormat('%x-%x-%x-%x-%x', $nestedObject->entityId);
            $this->assertTrue(strlen($nestedObject->entityId) === 36, 'UUID has 36 chars');
        }
    }

    public function testGetAllPaginatedNoRangeHeader()
    {
        $this->getJson('/test/entities/pages');
        $this->assertResponseStatus(400);
    }

    public function testGetAllPaginatedInvalidRangeHeader()
    {
        $this->getJson('/test/entities/pages', ['Range' => '0-']);
        $this->assertResponseStatus(400);
    }

    public function testGetAllPaginatedSimpleRange()
    {
        $entities = factory(App\Models\TestEntity::class, 20)->create();
        $totalCount = TestEntity::count();
        $this->getJson('/test/entities/pages', ['Range' => 'entities=0-19']);
        $object = json_decode($this->response->getContent());
        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();
        $this->assertEquals(20, count($object));
        list($first, $last, $total) = $this->parseRange($this->response->headers->get('content-range'));
        $this->assertEquals($total, $totalCount);
        $this->assertEquals($first, 0);
        $this->assertEquals($last, 19);
    }

    public function testPaginationBadRanges()
    {
        $entities = factory(App\Models\TestEntity::class, 20)->create();
        $this->getJson('/test/entities/pages', ['Range' => 'entities=19-18']);
        $this->assertResponseStatus(400);
    }

    public function testPaginationOutOfRange()
    {
        $entities = factory(App\Models\TestEntity::class, 10)->create();
        $totalCount = TestEntity::count();
        $this->getJson('/test/entities/pages', ['Range' => 'entities='.$totalCount.'-']);
        $this->assertResponseStatus(416);
    }

    public function testPaginationMoreThanInRepo()
    {
        $entities = factory(App\Models\TestEntity::class, 10)->create();
        $totalCount = TestEntity::count();
        $this->getJson('/test/entities/pages', ['Range' => 'entities='.($totalCount - 2).'-'.($totalCount + 20)]);
        $object = json_decode($this->response->getContent());
        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();
        $this->assertEquals(2, count($object));
        list($first, $last, $total) = $this->parseRange($this->response->headers->get('content-range'));
        $this->assertEquals($total, $totalCount);
        $this->assertEquals($first, $totalCount - 2);
        $this->assertEquals($last, $totalCount - 1);
    }

    public function testPaginationGetLast()
    {
        $entities = factory(App\Models\TestEntity::class, 10)->create();
        $totalCount = TestEntity::count();
        $this->getJson('/test/entities/pages', ['Range' => 'entities=-5']);
        $object = json_decode($this->response->getContent());
        $this->assertResponseStatus(206);
        $this->shouldReturnJson();
        $this->assertJsonArray();
        $this->assertJsonMultipleEntries();
        $this->assertEquals(5, count($object));
        list($first, $last, $total) = $this->parseRange($this->response->headers->get('content-range'));
        $this->assertEquals($total, $totalCount);
        $this->assertEquals($first, $totalCount - 5);
        $this->assertEquals($last, $totalCount - 1);
    }

    protected function parseRange($header)
    {
        $splitTotal = explode('/', str_replace('entities ', '', $header));
        $total = null;
        if (isset($splitTotal[1]) && $splitTotal[1] !== '') {
            $total = $splitTotal[1];
        }
        $firstAndLast = explode('-', $splitTotal[0]);
        $first = null;
        $last = null;
        if (isset($firstAndLast[0]) && $firstAndLast[0] !== '') {
            $first = $firstAndLast[0];
        }

        if (isset($firstAndLast[1]) && $firstAndLast[1] !== '') {
            $last = $firstAndLast[1];
        }

        return [$first,$last,$total];
    }

    public function testGetOne()
    {
        $entity = factory(App\Models\TestEntity::class)->create();

        $this->getJson('/test/entities/'.$entity->entity_id);

        $this->assertResponseOk();
        $this->shouldReturnJson();

        $object = json_decode($this->response->getContent());

        $this->assertTrue(is_object($object), 'Response is an object');

        $this->assertObjectHasAttribute('_self', $object);
        $this->assertTrue(is_string($object->_self), '_self is a string');

        $this->assertObjectHasAttribute('entityId', $object);
        $this->assertStringMatchesFormat('%x-%x-%x-%x-%x', $object->entityId);
        $this->assertTrue(strlen($object->entityId) === 36, 'UUID has 36 chars');

        $this->assertTrue(is_string($object->varchar), 'Varchar column type is text');
        $this->assertTrue(is_string($object->hash), 'Hash column is a hash');
        $this->assertTrue(is_int($object->integer), 'Integer column type is integer');
        $this->assertTrue(is_float($object->decimal) || is_int($object->decimal), 'Decimal column type is integer');
        $this->assertNull($object->nullable, 'Nullable column type is null');
        $this->assertTrue(is_string($object->text), 'Text column type is text');
        $this->assertValidIso8601Date($object->date, 'Date column type is a valid ISO8601 date');
        $this->assertObjectHasAttribute('multiWordColumnTitle', $object, 'Multi word colum is camel cased');

        $this->assertValidIso8601Date($object->createdAt, 'createdAt column is a valid ISO8601 date');
        $this->assertValidIso8601Date($object->updatedAt, 'updatedAt column is a valid ISO8601 date');

        $this->assertObjectNotHasAttribute('hidden', $object, 'Hidden is not hidden.');
    }

    public function testGetOneWithNested()
    {
        $entity = factory(App\Models\TestEntity::class)->create();
        $this->addRelatedEntities($entity);

        $this->getJson('/test/entities/'.$entity->entity_id, ['with-nested' => 'testMany']);
        $object = json_decode($this->response->getContent());

        $this->assertResponseOk();
        $this->shouldReturnJson();

        $this->assertTrue(is_object($object), 'Response is an object');
        $this->assertObjectHasAttribute('_self', $object);
        $this->assertTrue(is_string($object->_self), '_self is a string');

        $this->assertObjectHasAttribute('_testMany', $object);
        $this->assertEquals(5, count($object->_testMany));
        foreach ($object->_testMany as $nestedObject) {
            $this->assertObjectHasAttribute('_self', $nestedObject);
            $this->assertTrue(is_string($nestedObject->_self), '_self is a string');
            $this->assertObjectHasAttribute('entityId', $nestedObject);
            $this->assertStringMatchesFormat('%x-%x-%x-%x-%x', $nestedObject->entityId);
            $this->assertTrue(strlen($nestedObject->entityId) === 36, 'UUID has 36 chars');
        }
    }

    public function testGetOneWithInvalidNested()
    {
        $entity = factory(App\Models\TestEntity::class)->create();
        $this->addRelatedEntities($entity);

        $this->getJson('/test/entities/'.$entity->entity_id, ['with-nested' => 'not-a-valid-nesting']);

        $this->shouldReturnJson();
        $this->assertResponseStatus(400);
    }

    public function testPostOneValid()
    {
        $entity = factory(App\Models\TestEntity::class)->make();

        $this->postJson('/test/entities', $this->prepareEntity($entity));

        $this->shouldReturnJson();

        $object = json_decode($this->response->getContent());

        $this->assertResponseStatus(201);
        $this->assertTrue(is_object($object));
        $this->assertStringStartsWith('http', $object->_self);
{'fraction_non_alphanumeric': 0.1664544507466142, 'fraction_numerical': 0.010958058417255084, 'mean_word_length': 2.9837073470642483, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 426, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1540868', 'n_tokens_mistral': 7405, 'n_tokens_neox': 7225, 'n_words': 1306}