diff --git a/README.md b/README.md index 5b516ae..655fc19 100644 --- a/README.md +++ b/README.md @@ -38,21 +38,21 @@ composer cloc ``` ➜ playground-test git:(develop) ✗ composer cloc > cloc --exclude-dir=output,vendor . - 137 text files. - 79 unique files. - 59 files ignored. + 151 text files. + 88 unique files. + 64 files ignored. -github.com/AlDanial/cloc v 1.98 T=0.10 s (778.8 files/s, 87486.7 lines/s) +github.com/AlDanial/cloc v 1.98 T=0.11 s (795.4 files/s, 87907.6 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- -PHP 70 1556 1754 4816 +PHP 79 1703 2037 5209 XML 4 0 15 294 -YAML 1 5 0 249 +YAML 1 5 0 275 Markdown 3 35 0 88 -JSON 1 0 0 63 +JSON 1 0 0 65 ------------------------------------------------------------------------------- -SUM: 79 1596 1769 5510 +SUM: 88 1743 2052 5931 ------------------------------------------------------------------------------- ``` diff --git a/composer.json b/composer.json index 87bad1c..b3c7b7a 100644 --- a/composer.json +++ b/composer.json @@ -18,10 +18,12 @@ ], "require": { "php": "^8.2", + "brianium/paratest": "^7.4", "fakerphp/faker": "^1.23", "friendsofphp/php-cs-fixer": "^3.41", "larastan/larastan": "^2.0", "laravel/sanctum": "^4.0", + "nunomaduro/collision": "^8.0", "orchestra/testbench": "9.*", "phpstan/phpstan-phpunit": "^1.3", "phpunit/phpunit": "^11.0", diff --git a/database/factories/DemoFactory.php b/database/factories/DemoFactory.php new file mode 100644 index 0000000..2f12229 --- /dev/null +++ b/database/factories/DemoFactory.php @@ -0,0 +1,46 @@ + + */ +class DemoFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Demo::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $title = $this->faker->sentence(3); + + return [ + 'label' => $this->faker->sentence(3), + 'title' => $title, + 'slug' => Str::slug($title, '-'), + 'description' => $this->faker->sentence(3), + 'introduction' => $this->faker->sentence(3), + 'content' => $this->faker->sentence(3), + 'summary' => $this->faker->sentence(3), + ]; + } +} diff --git a/database/factories/WidgetFactory.php b/database/factories/WidgetFactory.php new file mode 100644 index 0000000..7c729f5 --- /dev/null +++ b/database/factories/WidgetFactory.php @@ -0,0 +1,46 @@ + + */ +class WidgetFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string + */ + protected $model = Widget::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $title = $this->faker->sentence(3); + + return [ + 'label' => $this->faker->sentence(3), + 'title' => $title, + 'slug' => Str::slug($title, '-'), + 'description' => $this->faker->sentence(3), + 'introduction' => $this->faker->sentence(3), + 'content' => $this->faker->sentence(3), + 'summary' => $this->faker->sentence(3), + ]; + } +} diff --git a/database/migrations-testing/9999_00_00_100001_create_testing_demo_table.php b/database/migrations-testing/9999_00_00_100001_create_testing_demo_table.php new file mode 100644 index 0000000..6596c1e --- /dev/null +++ b/database/migrations-testing/9999_00_00_100001_create_testing_demo_table.php @@ -0,0 +1,118 @@ +uuid('id')->primary(); + + // IDs + + $table->uuid('created_by_id')->nullable()->index(); + $table->uuid('modified_by_id')->nullable()->index(); + $table->uuid('owned_by_id')->nullable()->index(); + $table->uuid('parent_id')->nullable()->index(); + $table->string('demo_type')->nullable()->index(); + + // Dates + + $table->timestamps(); + + $table->softDeletes(); + + // Permissions + + $table->bigInteger('gids')->default(0)->unsigned(); + $table->tinyInteger('po')->default(0)->unsigned(); + $table->tinyInteger('pg')->default(0)->unsigned(); + $table->tinyInteger('pw')->default(0)->unsigned(); + $table->boolean('only_admin')->default(0); + $table->boolean('only_user')->default(0); + $table->boolean('only_guest')->default(0); + $table->boolean('allow_public')->default(0); + + // Status + + $table->bigInteger('status')->default(0)->unsigned(); + $table->bigInteger('rank')->default(0); + $table->bigInteger('size')->default(0); + + // Matrix + + $table->json('matrix')->nullable()->default(new Expression('(JSON_OBJECT())')); + $table->bigInteger('x')->nullable(); + $table->bigInteger('y')->nullable(); + $table->bigInteger('z')->nullable(); + $table->decimal('r', 65, 10)->nullable(); + $table->decimal('theta', 10, 6)->nullable(); + $table->decimal('rho', 10, 6)->nullable(); + $table->decimal('phi', 10, 6)->nullable(); + $table->decimal('elevation', 65, 10)->nullable(); + $table->decimal('latitude', 8, 6)->nullable(); + $table->decimal('longitude', 9, 6)->nullable(); + + // Flags + + $table->boolean('active')->default(1)->index(); + $table->boolean('flagged')->default(0); + $table->boolean('internal')->default(0); + $table->boolean('locked')->default(0); + $table->boolean('unknown')->default(0); + + // Columns + + $table->string('label', 128)->default(''); + $table->string('title', 255)->default(''); + $table->string('byline', 255)->default(''); + $table->string('slug', 128)->nullable()->index(); + $table->string('url', 512)->default(''); + $table->string('description', 512)->default(''); + $table->string('introduction', 512)->default(''); + $table->mediumText('content')->nullable(); + $table->mediumText('summary')->nullable(); + + // Ui + + $table->string('icon', 128)->default(''); + $table->string('image', 512)->default(''); + $table->string('avatar', 512)->default(''); + $table->json('ui')->nullable()->default(new Expression('(JSON_OBJECT())')); + + // JSON + + $table->json('assets')->nullable()->default(new Expression('(JSON_OBJECT())')); + $table->json('meta')->nullable()->default(new Expression('(JSON_OBJECT())')); + $table->json('notes')->nullable()->default(new Expression('(JSON_ARRAY())'))->comment('Array of note objects'); + $table->json('options')->nullable()->default(new Expression('(JSON_OBJECT())')); + $table->json('sources')->nullable()->default(new Expression('(JSON_OBJECT())')); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('testing_demo'); + } +}; diff --git a/database/migrations-testing/9999_00_00_100001_create_testing_widgets.php b/database/migrations-testing/9999_00_00_100001_create_testing_widgets.php new file mode 100644 index 0000000..d8f0771 --- /dev/null +++ b/database/migrations-testing/9999_00_00_100001_create_testing_widgets.php @@ -0,0 +1,119 @@ +uuid('id')->primary(); + + // IDs + + $table->uuid('created_by_id')->nullable()->index(); + $table->uuid('modified_by_id')->nullable()->index(); + $table->uuid('owned_by_id')->nullable()->index(); + $table->uuid('parent_id')->nullable()->index(); + $table->string('widget_type')->nullable()->index(); + $table->uuid('demo_id')->nullable()->index(); + + // Dates + + $table->timestamps(); + + $table->softDeletes(); + + // Permissions + + $table->bigInteger('gids')->default(0)->unsigned(); + $table->tinyInteger('po')->default(0)->unsigned(); + $table->tinyInteger('pg')->default(0)->unsigned(); + $table->tinyInteger('pw')->default(0)->unsigned(); + $table->boolean('only_admin')->default(0); + $table->boolean('only_user')->default(0); + $table->boolean('only_guest')->default(0); + $table->boolean('allow_public')->default(0); + + // Status + + $table->bigInteger('status')->default(0)->unsigned(); + $table->bigInteger('rank')->default(0); + $table->bigInteger('size')->default(0); + + // Matrix + + $table->json('matrix')->nullable()->default(new Expression('(JSON_OBJECT())')); + $table->bigInteger('x')->nullable(); + $table->bigInteger('y')->nullable(); + $table->bigInteger('z')->nullable(); + $table->decimal('r', 65, 10)->nullable(); + $table->decimal('theta', 10, 6)->nullable(); + $table->decimal('rho', 10, 6)->nullable(); + $table->decimal('phi', 10, 6)->nullable(); + $table->decimal('elevation', 65, 10)->nullable(); + $table->decimal('latitude', 8, 6)->nullable(); + $table->decimal('longitude', 9, 6)->nullable(); + + // Flags + + $table->boolean('active')->default(1)->index(); + $table->boolean('flagged')->default(0); + $table->boolean('internal')->default(0); + $table->boolean('locked')->default(0); + $table->boolean('unknown')->default(0); + + // Columns + + $table->string('label', 128)->default(''); + $table->string('title', 255)->default(''); + $table->string('byline', 255)->default(''); + $table->string('slug', 128)->nullable()->index(); + $table->string('url', 512)->default(''); + $table->string('description', 512)->default(''); + $table->string('introduction', 512)->default(''); + $table->mediumText('content')->nullable(); + $table->mediumText('summary')->nullable(); + + // Ui + + $table->string('icon', 128)->default(''); + $table->string('image', 512)->default(''); + $table->string('avatar', 512)->default(''); + $table->json('ui')->nullable()->default(new Expression('(JSON_OBJECT())')); + + // JSON + + $table->json('assets')->nullable()->default(new Expression('(JSON_OBJECT())')); + $table->json('meta')->nullable()->default(new Expression('(JSON_OBJECT())')); + $table->json('notes')->nullable()->default(new Expression('(JSON_ARRAY())'))->comment('Array of note objects'); + $table->json('options')->nullable()->default(new Expression('(JSON_OBJECT())')); + $table->json('sources')->nullable()->default(new Expression('(JSON_OBJECT())')); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('testing_widgets'); + } +}; diff --git a/src/Models/Demo.php b/src/Models/Demo.php new file mode 100644 index 0000000..4a063f0 --- /dev/null +++ b/src/Models/Demo.php @@ -0,0 +1,87 @@ + + */ + public function widgets(): HasMany + { + return $this->hasMany( + Widget::class, + 'demo_id', + 'id' + ); + } +} diff --git a/src/Models/UserWithRole.php b/src/Models/UserWithRole.php index 4360af0..f21c59a 100644 --- a/src/Models/UserWithRole.php +++ b/src/Models/UserWithRole.php @@ -25,7 +25,6 @@ class UserWithRole extends AbstractUser protected $attributes = [ 'name' => '', 'email' => '', - 'password' => '', 'role' => '', ]; diff --git a/src/Models/Widget.php b/src/Models/Widget.php new file mode 100644 index 0000000..17f7677 --- /dev/null +++ b/src/Models/Widget.php @@ -0,0 +1,88 @@ + + */ + public function demo(): HasOne + { + return $this->hasOne( + Demo::class, + 'id', + 'demo_id' + ); + } +} diff --git a/src/OrchestraTestCase.php b/src/OrchestraTestCase.php index 04db9d7..a41b0f0 100644 --- a/src/OrchestraTestCase.php +++ b/src/OrchestraTestCase.php @@ -6,6 +6,7 @@ */ namespace Playground\Test; +use Illuminate\Support\Carbon; use Orchestra\Testbench\TestCase as Orchestra; /** @@ -14,4 +15,145 @@ abstract class OrchestraTestCase extends Orchestra { use WithFaker; + + protected bool $setTestNow = true; + + protected bool $setUpUserCredentials = true; + + protected bool $setUpUserForAdmin = false; + + protected bool $setUpUserForLaravel = false; + + protected bool $setUpUserForLaravelSanctum = false; + + protected bool $setUpUserForPlayground = false; + + protected bool $setUpUserForPlaygroundSanctum = false; + + protected bool $setUpUserForPolicy = false; + + protected bool $setUpUserForPrivileges = false; + + protected bool $setUpUserForRoles = false; + + /** + * Setup the test environment. + */ + protected function setUp(): void + { + parent::setUp(); + + if ($this->setTestNow) { + Carbon::setTestNow(Carbon::now()); + } + } + + /** + * Define environment setup. + * + * Verification: + * - admin: $user->isAdmin() + * - policy: $user->can() + * - privileges: $user->hasPrivilege() + * - roles: $user->hasRole() + * - sanctum: $user->currentAccessToken()->can() + * - user: ! empty($user) + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + protected function defineEnvironment($app) + { + $verify = ''; + $userModel = ''; + + $sanctum = false; + $hasPrivilege = false; + $userPrivileges = false; + $hasRole = false; + $userRole = false; + $userRoles = false; + + $password = env('AUTH_TESTING_PASSWORD', 'password'); + $hashed = boolval(env('AUTH_TESTING_HASHED', false)); + + if ($this->setUpUserForPlayground) { + + $userModel = 'Playground\\Models\\User'; + + $verify = 'roles'; + + $hasPrivilege = true; + $userPrivileges = true; + $hasRole = true; + $userRole = true; + $userRoles = true; + + } elseif ($this->setUpUserForPlaygroundSanctum) { + + $userModel = 'Playground\\Test\\Models\\PlaygroundUserWithSanctum'; + + $verify = 'sanctum'; + + $sanctum = true; + $hasPrivilege = true; + $userPrivileges = true; + $hasRole = true; + $userRole = true; + $userRoles = true; + + } elseif ($this->setUpUserForLaravel) { + + $userModel = 'Playground\\Test\\Models\\DefaultUser'; + $verify = 'user'; + + } elseif ($this->setUpUserForAdmin) { + + $userModel = 'Playground\\Models\\User'; + $verify = 'admin'; + + } elseif ($this->setUpUserForPolicy) { + + $userModel = 'Playground\\Models\\User'; + $verify = 'privileges'; + + } elseif ($this->setUpUserForPrivileges) { + + $userModel = 'Playground\\Models\\User'; + $verify = 'policy'; + + } elseif ($this->setUpUserForRoles) { + + $userModel = 'Playground\\Models\\User'; + $verify = 'roles'; + + } elseif ($this->setUpUserForLaravelSanctum) { + + $userModel = 'Playground\\Test\\Models\\DefaultUser'; + + $verify = 'sanctum'; + + $sanctum = false; + } + + if ($userModel) { + $app['config']->set('auth.providers.users.model', $userModel); + } + + if ($this->setUpUserCredentials) { + $app['config']->set('auth.testing.password', $password); + $app['config']->set('auth.testing.hashed', $hashed); + } + + if ($verify) { + $app['config']->set('playground-auth.verify', $verify); + + $app['config']->set('playground-auth.sanctum', $sanctum); + $app['config']->set('playground-auth.hasPrivilege', $hasPrivilege); + $app['config']->set('playground-auth.userPrivileges', $userPrivileges); + $app['config']->set('playground-auth.hasRole', $hasRole); + $app['config']->set('playground-auth.userRole', $userRole); + $app['config']->set('playground-auth.userRoles', $userRoles); + } + } } diff --git a/src/Unit/Models/ModelCase.php b/src/Unit/Models/ModelCase.php index fd4f3f8..abfe000 100644 --- a/src/Unit/Models/ModelCase.php +++ b/src/Unit/Models/ModelCase.php @@ -81,18 +81,6 @@ abstract class ModelCase extends OrchestraTestCase 'morphToMany' => [], ]; - /** - * Set up the environment. - * - * @param \Illuminate\Foundation\Application $app - */ - protected function getEnvironmentSetUp($app) - { - $app['config']->set('auth.providers.users.model', 'Playground\\Models\\User'); - $app['config']->set('auth.testing.password', 'password'); - $app['config']->set('auth.testing.hashed', false); - } - protected function getModel(): Model { $modelClass = $this->getModelClass(); diff --git a/tests/Unit/Models/Demo/ModelTest.php b/tests/Unit/Models/Demo/ModelTest.php new file mode 100644 index 0000000..9a84d5f --- /dev/null +++ b/tests/Unit/Models/Demo/ModelTest.php @@ -0,0 +1,36 @@ + Test has one relationships. + */ + protected array $hasMany = [ + 'widgets', + ]; + + /** + * @var array Test has one relationships. + */ + protected array $hasOne = [ + 'creator', + 'modifier', + 'owner', + 'parent', + ]; +} diff --git a/tests/Unit/Models/ModelCase.php b/tests/Unit/Models/ModelCase.php new file mode 100644 index 0000000..6a6ca1e --- /dev/null +++ b/tests/Unit/Models/ModelCase.php @@ -0,0 +1,51 @@ +loadMigrationsFrom(workbench_path('database/migrations')); + $this->loadMigrationsFrom(dirname(dirname(__DIR__)).'/database/migration-testing'); + } + + protected function getPackageProviders($app) + { + return [ + PlaygroundServiceProvider::class, + ServiceProvider::class, + ]; + } + + // /** + // * Set up the environment. + // * + // * @param \Illuminate\Foundation\Application $app + // */ + // protected function getEnvironmentSetUp($app) + // { + // $app['config']->set('auth.providers.users.model', 'Playground\\Models\\User'); + // $app['config']->set('playground-auth.verify', 'user'); + // $app['config']->set('auth.testing.password', 'password'); + // $app['config']->set('auth.testing.hashed', false); + + // // $app['config']->set('playground-test.load.migrations', true); + // } +} diff --git a/tests/Unit/Models/UserWithRole/ModelTest.php b/tests/Unit/Models/UserWithRole/ModelTest.php new file mode 100644 index 0000000..2af81cd --- /dev/null +++ b/tests/Unit/Models/UserWithRole/ModelTest.php @@ -0,0 +1,113 @@ + + */ + public const MODEL_CLASS = UserWithRole::class; + + public function test_getAttributes(): void + { + $mc = static::MODEL_CLASS; + + /** + * @var UserWithRole $instance + */ + $instance = new $mc(); + + $expected = [ + 'name' => '', + 'email' => '', + 'role' => '', + ]; + + $attributes = $instance->getAttributes(); + + $this->assertIsArray($attributes); + + $this->assertSame($expected, $attributes); + } + + public function test_hasRole_is_false_without_role(): void + { + $mc = static::MODEL_CLASS; + + /** + * @var UserWithRole $instance + */ + $instance = new $mc(); + + $role = null; + + $this->assertFalse($instance->hasRole($role)); + } + + public function test_hasRole_is_true_with_matching_role(): void + { + $mc = static::MODEL_CLASS; + + /** + * @var UserWithRole $instance + */ + $instance = new $mc(); + + $role = 'user'; + + $instance->role = $role; + + $this->assertTrue($instance->hasRole($role)); + } + + public function test_hasRole_is_true_with_array_of_roles(): void + { + $mc = static::MODEL_CLASS; + + /** + * @var UserWithRole $instance + */ + $instance = new $mc(); + + $role = 'user'; + + $instance->role = $role; + + $this->assertTrue($instance->hasRole($role)); + $this->assertTrue($instance->hasRole(['publisher', 'user', 'admin'])); + $this->assertFalse($instance->hasRole('admin')); + $this->assertFalse($instance->isAdmin()); + } + + public function test_isAdmin_is_true_with_admin_role(): void + { + $mc = static::MODEL_CLASS; + + /** + * @var UserWithRole $instance + */ + $instance = new $mc(); + + $role = 'admin'; + + $instance->role = $role; + + $this->assertTrue($instance->isAdmin()); + } +} diff --git a/tests/Unit/TestCase.php b/tests/Unit/TestCase.php index e6666e0..5223c74 100644 --- a/tests/Unit/TestCase.php +++ b/tests/Unit/TestCase.php @@ -7,6 +7,7 @@ namespace Tests\Unit\Playground\Test; use Illuminate\Foundation\Testing\DatabaseTransactions; +use Playground\ServiceProvider as PlaygroundServiceProvider; use Playground\Test\OrchestraTestCase; use Playground\Test\ServiceProvider; @@ -20,19 +21,8 @@ class TestCase extends OrchestraTestCase protected function getPackageProviders($app) { return [ + PlaygroundServiceProvider::class, ServiceProvider::class, ]; } - - /** - * Set up the environment. - * - * @param \Illuminate\Foundation\Application $app - */ - protected function getEnvironmentSetUp($app) - { - $app['config']->set('auth.providers.users.model', 'Playground\\Test\\Models\\User'); - $app['config']->set('auth.testing.password', 'password'); - $app['config']->set('auth.testing.hashed', false); - } }