From ee5daafed3d8de27baa90b9b8024da545115fe94 Mon Sep 17 00:00:00 2001 From: Jasper Tey Date: Fri, 15 Nov 2024 18:51:09 -0500 Subject: [PATCH] WIP: Re-working the autoloading internals. --- composer.json | 2 +- routes/testing.php | 24 +++ src/DomainManager.php | 6 +- src/Facades/Autoload.php | 25 +++ src/Facades/DDD.php | 2 +- src/LaravelDDDServiceProvider.php | 27 ++-- .../{Autoloader.php => AutoloadManager.php} | 113 ++++++++++---- tests/Autoload/CommandTest.php | 143 ++++++++++-------- tests/Autoload/FactoryTest.php | 64 ++++++-- tests/Autoload/IgnoreTest.php | 45 +++--- tests/Autoload/PolicyTest.php | 91 ++++++++--- tests/Autoload/ProviderTest.php | 138 +++++++++-------- tests/Support/AutoloaderTest.php | 6 +- tests/TestCase.php | 2 - 14 files changed, 456 insertions(+), 232 deletions(-) create mode 100644 routes/testing.php create mode 100644 src/Facades/Autoload.php rename src/Support/{Autoloader.php => AutoloadManager.php} (78%) diff --git a/composer.json b/composer.json index 7991560..2344d67 100644 --- a/composer.json +++ b/composer.json @@ -80,7 +80,7 @@ "Lunarstorm\\LaravelDDD\\LaravelDDDServiceProvider" ], "aliases": { - "LaravelDDD": "Lunarstorm\\LaravelDDD\\Facades\\LaravelDDD" + "DDD": "Lunarstorm\\LaravelDDD\\Facades\\DDD" } } }, diff --git a/routes/testing.php b/routes/testing.php new file mode 100644 index 0000000..667d8d9 --- /dev/null +++ b/routes/testing.php @@ -0,0 +1,24 @@ +middleware(['web']) + ->as('ddd.') + ->group(function () { + Route::get('/', function () { + return response('home'); + })->name('home'); + + Route::get('/config', function () { + return response(config('ddd')); + })->name('config'); + + Route::get('/autoload', function () { + return response([ + 'providers' => Autoload::getRegisteredProviders(), + 'commands' => Autoload::getRegisteredCommands(), + ]); + })->name('autoload'); + }); diff --git a/src/DomainManager.php b/src/DomainManager.php index b0f084f..2eb2ea3 100755 --- a/src/DomainManager.php +++ b/src/DomainManager.php @@ -2,7 +2,7 @@ namespace Lunarstorm\LaravelDDD; -use Lunarstorm\LaravelDDD\Support\Autoloader; +use Lunarstorm\LaravelDDD\Support\AutoloadManager; use Lunarstorm\LaravelDDD\Support\GeneratorBlueprint; use Lunarstorm\LaravelDDD\Support\Path; @@ -43,9 +43,9 @@ public function __construct() $this->commandContext = null; } - public function autoloader(): Autoloader + public function autoloader(): AutoloadManager { - return app(Autoloader::class); + return app(AutoloadManager::class); } public function composer(): ComposerManager diff --git a/src/Facades/Autoload.php b/src/Facades/Autoload.php new file mode 100644 index 0000000..055f86d --- /dev/null +++ b/src/Facades/Autoload.php @@ -0,0 +1,25 @@ +app->scoped(Autoloader::class, function () { - return new Autoloader; + $this->app->scoped(AutoloadManager::class, function () { + return new AutoloadManager; }); $this->app->scoped(ComposerManager::class, function () { @@ -34,7 +33,7 @@ public function configurePackage(Package $package): void }); $this->app->bind('ddd', DomainManager::class); - $this->app->bind('ddd.autoloader', Autoloader::class); + $this->app->bind('ddd.autoloader', AutoloadManager::class); $this->app->bind('ddd.config', ConfigManager::class); $this->app->bind('ddd.composer', ComposerManager::class); $this->app->bind('ddd.stubs', StubManager::class); @@ -92,6 +91,10 @@ public function configurePackage(Package $package): void $package->hasCommand(Commands\DomainInterfaceMakeCommand::class); $package->hasCommand(Commands\DomainTraitMakeCommand::class); } + + if ($this->app->runningUnitTests()) { + $package->hasRoutes(['testing']); + } } protected function laravelVersion($value) @@ -111,21 +114,11 @@ protected function registerMigrations() return new Commands\Migration\DomainMigrateMakeCommand($creator, $composer); }); - // $this->app->when(MigrationCreator::class) - // ->needs('$customStubPath') - // ->give(function ($app) { - // return $app->basePath('stubs'); - // }); - $this->loadMigrationsFrom(DomainMigration::paths()); } public function packageBooted() { - // $this->publishes([ - // $this->package->basePath('/../stubs') => $this->app->basePath("stubs/{$this->package->shortName()}"), - // ], "{$this->package->shortName()}-stubs"); - if ($this->app->runningInConsole() && method_exists($this, 'optimizes')) { $this->optimizes( optimize: 'ddd:optimize', @@ -134,7 +127,7 @@ public function packageBooted() ); } - DDD::autoloader()->boot(); + Autoload::boot(); } public function packageRegistered() diff --git a/src/Support/Autoloader.php b/src/Support/AutoloadManager.php similarity index 78% rename from src/Support/Autoloader.php rename to src/Support/AutoloadManager.php index 479adf5..9f16ff8 100644 --- a/src/Support/Autoloader.php +++ b/src/Support/AutoloadManager.php @@ -20,7 +20,7 @@ use Symfony\Component\Finder\SplFileInfo; use Throwable; -class Autoloader +class AutoloadManager { use Conditionable; @@ -30,7 +30,13 @@ class Autoloader protected array $registeredProviders = []; - protected bool $isBooted = false; + protected array $resolvedPolicies = []; + + protected array $resolvedFactories = []; + + protected bool $booted = false; + + protected bool $consoleBooted = false; public function __construct() { @@ -43,31 +49,44 @@ public function boot() return $this; } - // if ($this->isBooted) { - // return $this; - // } - $this + ->flush() ->when(config('ddd.autoload.providers') === true, fn () => $this->handleProviders()) ->when(app()->runningInConsole() && config('ddd.autoload.commands') === true, fn () => $this->handleCommands()) ->when(config('ddd.autoload.policies') === true, fn () => $this->handlePolicies()) - ->when(config('ddd.autoload.factories') === true, fn () => $this->handleFactories()); + ->when(config('ddd.autoload.factories') === true, fn () => $this->handleFactories()) + ->run(); - if (app()->runningInConsole()) { - ConsoleApplication::starting(function ($artisan) { - foreach ($this->registeredCommands as $command) { - $artisan->resolve($command); - } - }); + $this->booted = true; + } + + public function isBooted(): bool + { + return $this->booted; + } + + public function isConsoleBooted(): bool + { + return $this->consoleBooted; + } + + protected function flush() + { + foreach ($this->registeredProviders as $provider) { + app()->forgetInstance($provider); } - $this->isBooted = true; + $this->registeredProviders = []; + + $this->registeredCommands = []; + + $this->resolvedPolicies = []; + + $this->resolvedFactories = []; return $this; } - public function run() {} - protected function normalizePaths($path): array { return collect($path) @@ -97,6 +116,10 @@ protected function handleProviders() ? DomainCache::get('domain-providers') : $this->discoverProviders(); + foreach ($this->registeredProviders as $provider) { + app()->forgetInstance($provider); + } + $this->registeredProviders = []; foreach ($providers as $provider) { @@ -117,7 +140,25 @@ protected function handleCommands() foreach ($commands as $command) { $this->registeredCommands[$command] = $command; - $this->registerCommand($command); + } + + return $this; + } + + protected function run() + { + foreach ($this->registeredProviders as $provider) { + app()->register($provider); + } + + if (app()->runningInConsole() && ! $this->isConsoleBooted()) { + ConsoleApplication::starting(function ($artisan) { + foreach ($this->registeredCommands as $command) { + $artisan->resolve($command); + } + }); + + $this->consoleBooted = true; } return $this; @@ -133,21 +174,31 @@ public function getRegisteredProviders(): array return $this->registeredProviders; } - protected function registerCommand($class) + public function getResolvedPolicies(): array { - // ConsoleApplication::starting(function ($artisan) use ($class) { - // dump('resolving command', $class, $this->registeredCommands); - // $artisan->resolve($class); - // }); + return $this->resolvedPolicies; + } + + public function getResolvedFactories(): array + { + return $this->resolvedFactories; } protected function handlePolicies() { - Gate::guessPolicyNamesUsing(static function (string $class): array|string { + Gate::guessPolicyNamesUsing(function (string $class): array|string { + if (array_key_exists($class, $this->resolvedPolicies)) { + return $this->resolvedPolicies[$class]; + } + if ($model = DomainObject::fromClass($class, 'model')) { - return (new Domain($model->domain)) + $resolved = (new Domain($model->domain)) ->object('policy', "{$model->name}Policy") ->fullyQualifiedName; + + $this->resolvedPolicies[$class] = $resolved; + + return $resolved; } $classDirname = str_replace('/', '\\', dirname(str_replace('\\', '/', $class))); @@ -169,7 +220,13 @@ protected function handlePolicies() protected function handleFactories() { Factory::guessFactoryNamesUsing(function (string $modelName) { + if (array_key_exists($modelName, $this->resolvedFactories)) { + return $this->resolvedFactories[$modelName]; + } + if ($factoryName = DomainFactory::resolveFactoryName($modelName)) { + $this->resolvedFactories[$modelName] = $factoryName; + return $factoryName; } @@ -253,16 +310,16 @@ protected function discoverCommands(): array ->toArray(); } - public function cacheProviders() + public function cacheCommands() { - DomainCache::set('domain-providers', $this->discoverProviders()); + DomainCache::set('domain-commands', $this->discoverCommands()); return $this; } - public function cacheCommands() + public function cacheProviders() { - DomainCache::set('domain-commands', $this->discoverCommands()); + DomainCache::set('domain-providers', $this->discoverProviders()); return $this; } diff --git a/tests/Autoload/CommandTest.php b/tests/Autoload/CommandTest.php index e446820..e57bf73 100644 --- a/tests/Autoload/CommandTest.php +++ b/tests/Autoload/CommandTest.php @@ -1,102 +1,123 @@ commands = [ + 'invoice:deliver' => 'Domain\Invoicing\Commands\InvoiceDeliver', + 'log:prune' => 'Infrastructure\Commands\LogPrune', + 'application:sync' => 'Application\Commands\ApplicationSync', + ]; + + $this->setupTestApplication(); + DomainCache::clear(); +}); + afterEach(function () { DomainCache::clear(); }); -describe('without autoload', function () { - beforeEach(function () { - DomainCache::clear(); +describe('when ddd.autoload.commands = false', function () { + it('skips handling commands', function () { + config()->set('ddd.autoload.commands', false); - $this->afterApplicationRefreshed(function () { - $this->setupTestApplication(); - app('ddd.autoloader')->boot(); + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handleCommands'); }); + + $mock->boot(); + + $artisanCommands = collect(Artisan::all()); + + expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); }); - it('does not register the command', function ($className, $command) { - $this->refreshApplicationWithConfig([ - 'ddd.autoload.commands' => false, - ]); - - expect(class_exists($className))->toBeTrue(); - expect(fn () => Artisan::call($command))->toThrow(CommandNotFoundException::class); - })->with([ - ['Domain\Invoicing\Commands\InvoiceDeliver', 'invoice:deliver'], - ['Infrastructure\Commands\LogPrune', 'log:prune'], - ['Application\Commands\ApplicationSync', 'application:sync'], - ]); + it('does not register the commands', function () { + config()->set('ddd.autoload.commands', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handleCommands'); + }); + + $mock->boot(); + + expect($mock->getRegisteredCommands())->toBeEmpty(); + + $artisanCommands = collect(Artisan::all()); + + expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); + }); }); -describe('with autoload', function () { - beforeEach(function () { - DomainCache::clear(); +describe('when ddd.autoload.commands = true', function () { + it('registers the commands', function () { + config()->set('ddd.autoload.commands', true); - $this->afterApplicationRefreshed(function () { - $this->setupTestApplication(); - app('ddd.autoloader')->boot(); + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - }); - it('registers existing commands', function ($className, $command, $output) { - $this->refreshApplicationWithConfig([ - 'ddd.autoload.commands' => true, - ]); + $mock->boot(); - expect(class_exists($className))->toBeTrue(); + expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); - expect(collect(Artisan::all())) - ->has($command) - ->toBeTrue(); + $artisanCommands = collect(Artisan::all()); - Artisan::call($command); - expect(Artisan::output())->toContain($output); - })->with([ - ['Domain\Invoicing\Commands\InvoiceDeliver', 'invoice:deliver', 'Invoice delivered!'], - ['Infrastructure\Commands\LogPrune', 'log:prune', 'System logs pruned!'], - ['Application\Commands\ApplicationSync', 'application:sync', 'Application state synced!'], - ]); + expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); + }); }); describe('caching', function () { it('remembers the last cached state', function () { - $this->afterApplicationRefreshed(function () { - $this->setupTestApplication(); - DomainCache::set('domain-commands', []); - app('ddd.autoloader')->boot(); + DomainCache::set('domain-commands', []); + + config()->set('ddd.autoload.commands', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.commands' => true, - ]); + $mock->boot(); + + expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing([]); + + $artisanCommands = collect(Artisan::all()); + + expect($artisanCommands)->not->toHaveKeys(array_keys($this->commands)); // commands should not be recognized due to cached empty-state - expect(fn () => Artisan::call('invoice:deliver'))->toThrow(CommandNotFoundException::class); - expect(fn () => Artisan::call('log:prune'))->toThrow(CommandNotFoundException::class); - expect(fn () => Artisan::call('application:sync'))->toThrow(CommandNotFoundException::class); + foreach ($this->commands as $command => $class) { + expect(fn () => Artisan::call($command))->toThrow(CommandNotFoundException::class); + } }); it('can bust the cache', function () { - $this->afterApplicationRefreshed(function () { - $this->setupTestApplication(); - DomainCache::set('domain-commands', []); - DomainCache::clear(); - app('ddd.autoloader')->boot(); + DomainCache::set('domain-commands', []); + DomainCache::clear(); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.commands' => true, - ]); + $mock->boot(); + + expect(array_values($mock->getRegisteredCommands()))->toEqualCanonicalizing(array_values($this->commands)); + + $artisanCommands = collect(Artisan::all()); + + expect($artisanCommands)->toHaveKeys(array_keys($this->commands)); - $this->artisan('invoice:deliver')->assertSuccessful(); - $this->artisan('log:prune')->assertSuccessful(); - $this->artisan('application:sync')->assertSuccessful(); + foreach ($this->commands as $command => $class) { + $this->artisan($command)->assertSuccessful(); + } }); }); diff --git a/tests/Autoload/FactoryTest.php b/tests/Autoload/FactoryTest.php index 45efa92..4d67b7f 100644 --- a/tests/Autoload/FactoryTest.php +++ b/tests/Autoload/FactoryTest.php @@ -1,26 +1,45 @@ setupTestApplication(); + DomainCache::clear(); }); -describe('autoload enabled', function () { - beforeEach(function () { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); +afterEach(function () { + DomainCache::clear(); +}); + +describe('when ddd.autoload.factories = true', function () { + it('handles the factories', function () { + config()->set('ddd.autoload.factories', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldReceive('handleFactories')->once(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.factories' => true, - ]); + $mock->boot(); }); it('can resolve domain factory', function ($modelClass, $expectedFactoryClass) { + config()->set('ddd.autoload.factories', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); + }); + + $mock->boot(); + expect($modelClass::factory())->toBeInstanceOf($expectedFactoryClass); })->with([ // VanillaModel is a vanilla eloquent model in the domain layer @@ -37,6 +56,10 @@ ]); it('gracefully falls back for non-domain factories', function () { + config()->set('ddd.autoload.factories', true); + + Autoload::boot(); + Artisan::call('make:model RegularModel -f'); $modelClass = 'App\Models\RegularModel'; @@ -46,21 +69,32 @@ expect($modelClass::factory()) ->toBeInstanceOf('Database\Factories\RegularModelFactory'); }); -})->skip(); +}); + +describe('when ddd.autoload.factories = false', function () { + it('skips handling factories', function () { + config()->set('ddd.autoload.factories', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handleFactories'); + }); + + $mock->boot(); + }); -describe('autoload disabled', function () { it('cannot resolve factories that rely on autoloading', function ($modelClass) { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + config()->set('ddd.autoload.factories', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.factories' => false, - ]); + $mock->boot(); expect(fn () => $modelClass::factory())->toThrow(Error::class); })->with([ ['Domain\Invoicing\Models\VanillaModel'], ['Domain\Internal\Reporting\Models\Report'], ]); -})->skip(); +}); diff --git a/tests/Autoload/IgnoreTest.php b/tests/Autoload/IgnoreTest.php index 0b2fafe..18e1d81 100644 --- a/tests/Autoload/IgnoreTest.php +++ b/tests/Autoload/IgnoreTest.php @@ -11,19 +11,32 @@ uses(BootsTestApplication::class); beforeEach(function () { + $this->providers = [ + 'Domain\Invoicing\Providers\InvoiceServiceProvider', + 'Application\Providers\ApplicationServiceProvider', + 'Infrastructure\Providers\InfrastructureServiceProvider', + ]; + + $this->commands = [ + 'invoice:deliver' => 'Domain\Invoicing\Commands\InvoiceDeliver', + 'log:prune' => 'Infrastructure\Commands\LogPrune', + 'application:sync' => 'Application\Commands\ApplicationSync', + ]; + $this->setupTestApplication(); -})->skip(); + DomainCache::clear(); +}); + +afterEach(function () { + DomainCache::clear(); +}); it('can ignore folders when autoloading', function () { Artisan::call('ddd:optimize'); $expected = [ - 'Domain\Invoicing\Providers\InvoiceServiceProvider', - 'Domain\Invoicing\Commands\InvoiceDeliver', - 'Application\Providers\ApplicationServiceProvider', - 'Application\Commands\ApplicationSync', - 'Infrastructure\Providers\InfrastructureServiceProvider', - 'Infrastructure\Commands\LogPrune', + ...array_values($this->providers), + ...array_values($this->commands), ]; $cached = [ @@ -38,9 +51,7 @@ Artisan::call('ddd:optimize'); $expected = [ - 'Domain\Invoicing\Providers\InvoiceServiceProvider', - 'Application\Providers\ApplicationServiceProvider', - 'Infrastructure\Providers\InfrastructureServiceProvider', + ...array_values($this->providers), ]; $cached = [ @@ -55,9 +66,7 @@ Artisan::call('ddd:optimize'); $expected = [ - 'Domain\Invoicing\Commands\InvoiceDeliver', - 'Application\Commands\ApplicationSync', - 'Infrastructure\Commands\LogPrune', + ...array_values($this->commands), ]; $cached = [ @@ -65,19 +74,15 @@ ...DomainCache::get('domain-commands'), ]; - expect($cached)->toEqual($expected); + expect($cached)->toEqualCanonicalizing($expected); }); it('can register a custom autoload filter', function () { Artisan::call('ddd:optimize'); $expected = [ - 'Domain\Invoicing\Providers\InvoiceServiceProvider', - 'Domain\Invoicing\Commands\InvoiceDeliver', - 'Application\Providers\ApplicationServiceProvider', - 'Application\Commands\ApplicationSync', - 'Infrastructure\Providers\InfrastructureServiceProvider', - 'Infrastructure\Commands\LogPrune', + ...array_values($this->providers), + ...array_values($this->commands), ]; $cached = [ diff --git a/tests/Autoload/PolicyTest.php b/tests/Autoload/PolicyTest.php index 5483148..8fd521f 100644 --- a/tests/Autoload/PolicyTest.php +++ b/tests/Autoload/PolicyTest.php @@ -1,26 +1,83 @@ policies = [ + 'Domain\Invoicing\Models\Invoice' => 'Domain\Invoicing\Policies\InvoicePolicy', + 'Infrastructure\Models\AppSession' => 'Infrastructure\Policies\AppSessionPolicy', + 'Application\Models\Login' => 'Application\Policies\LoginPolicy', + ]; + $this->setupTestApplication(); -})->skip(); - -it('can autoload policy', function ($class, $expectedPolicy) { - expect(class_exists($class))->toBeTrue(); - expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); -})->with([ - ['Domain\Invoicing\Models\Invoice', 'Domain\Invoicing\Policies\InvoicePolicy'], - ['Infrastructure\Models\AppSession', 'Infrastructure\Policies\AppSessionPolicy'], - ['Application\Models\Login', 'Application\Policies\LoginPolicy'], -]); - -it('gracefully falls back for non-ddd policies', function ($class, $expectedPolicy) { - expect(class_exists($class))->toBeTrue(); - expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); -})->with([ - ['App\Models\Post', 'App\Policies\PostPolicy'], -]); + DomainCache::clear(); +}); + +afterEach(function () { + DomainCache::clear(); +}); + +describe('when ddd.autoload.policies = false', function () { + it('skips handling policies', function () { + config()->set('ddd.autoload.policies', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handlePolicies'); + }); + + $mock->boot(); + }); +}); + +describe('when ddd.autoload.policies = true', function () { + it('handles policies', function () { + config()->set('ddd.autoload.policies', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldReceive('handlePolicies')->once(); + }); + + $mock->boot(); + }); + + it('can resolve the policies', function () { + config()->set('ddd.autoload.policies', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); + }); + + $mock->boot(); + + foreach ($this->policies as $class => $expectedPolicy) { + expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); + // expect($mock->getResolvedPolicies())->toHaveKey($class); + } + }); + + it('gracefully falls back for non-ddd policies', function ($class, $expectedPolicy) { + config()->set('ddd.autoload.policies', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); + }); + + $mock->boot(); + + $resolvedPolicies = $mock->getResolvedPolicies(); + + expect(class_exists($class))->toBeTrue(); + expect(Gate::getPolicyFor($class))->toBeInstanceOf($expectedPolicy); + expect($resolvedPolicies)->not->toHaveKey($class); + })->with([ + ['App\Models\Post', 'App\Policies\PostPolicy'], + ]); +}); diff --git a/tests/Autoload/ProviderTest.php b/tests/Autoload/ProviderTest.php index 01d76a7..78b4490 100644 --- a/tests/Autoload/ProviderTest.php +++ b/tests/Autoload/ProviderTest.php @@ -1,11 +1,19 @@ providers = [ + 'Domain\Invoicing\Providers\InvoiceServiceProvider', + 'Application\Providers\ApplicationServiceProvider', + 'Infrastructure\Providers\InfrastructureServiceProvider', + ]; + $this->setupTestApplication(); DomainCache::clear(); }); @@ -14,101 +22,103 @@ DomainCache::clear(); }); -describe('without autoload', function () { - it('does not register the provider', function ($binding) { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); - }); +describe('when ddd.autoload.providers = false', function () { + it('skips handling providers', function () { + config()->set('ddd.autoload.providers', false); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => false, - ]); - - expect(fn () => app($binding))->toThrow(Exception::class); - })->with([ - ['invoicing'], - ['application-layer'], - ['infrastructure-layer'], - ]); -})->skip(); - -describe('with autoload', function () { - it('registers the provider in domain layer', function () { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldNotReceive('handleProviders'); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock->boot(); - expect(app('invoicing'))->toEqual('invoicing-singleton'); - $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeNull() + ); }); - it('registers the provider in application layer', function () { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + it('does not register the providers', function () { + config()->set('ddd.autoload.providers', false); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock->boot(); + + expect($mock->getRegisteredProviders())->toBeEmpty(); + + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeNull() + ); + }); +}); + +describe('when ddd.autoload.providers = true', function () { + it('handles the providers', function () { + config()->set('ddd.autoload.providers', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods() + ->shouldReceive('handleProviders')->once(); + }); - expect(app('application-layer'))->toEqual('application-layer-singleton'); - $this->artisan('application:sync')->expectsOutputToContain('application-secret'); + $mock->boot(); }); - it('registers the provider in custom layer', function () { - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + it('registers the providers', function () { + config()->set('ddd.autoload.providers', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock->boot(); + + expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); - expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); - $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) + ); }); -})->skip(); +}); describe('caching', function () { it('remembers the last cached state', function () { DomainCache::set('domain-providers', []); - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); + config()->set('ddd.autoload.providers', true); + + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); }); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock->boot(); - expect(fn () => app('invoicing'))->toThrow(Exception::class); - expect(fn () => app('application-layer'))->toThrow(Exception::class); - expect(fn () => app('infrastructure-layer'))->toThrow(Exception::class); + expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing([]); + + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeNull() + ); }); it('can bust the cache', function () { DomainCache::set('domain-providers', []); DomainCache::clear(); - $this->afterApplicationRefreshed(function () { - app('ddd.autoloader')->boot(); - }); + config()->set('ddd.autoload.providers', true); - $this->refreshApplicationWithConfig([ - 'ddd.autoload.providers' => true, - ]); + $mock = $this->partialMock(AutoloadManager::class, function (MockInterface $mock) { + $mock->shouldAllowMockingProtectedMethods(); + }); - expect(app('invoicing'))->toEqual('invoicing-singleton'); - $this->artisan('invoice:deliver')->expectsOutputToContain('invoice-secret'); + $mock->boot(); - expect(app('application-layer'))->toEqual('application-layer-singleton'); - $this->artisan('application:sync')->expectsOutputToContain('application-secret'); + expect(array_values($mock->getRegisteredProviders()))->toEqualCanonicalizing($this->providers); - expect(app('infrastructure-layer'))->toEqual('infrastructure-layer-singleton'); - $this->artisan('log:prune')->expectsOutputToContain('infrastructure-secret'); + collect($this->providers)->each( + fn ($provider) => expect(app()->getProvider($provider))->toBeInstanceOf($provider) + ); }); -})->skip(); +}); diff --git a/tests/Support/AutoloaderTest.php b/tests/Support/AutoloaderTest.php index 2ee1ddf..a39deab 100644 --- a/tests/Support/AutoloaderTest.php +++ b/tests/Support/AutoloaderTest.php @@ -1,13 +1,13 @@ setupTestApplication(); }); it('can run', function () { - $autoloader = new Autoloader; + $autoloader = new AutoloadManager; $autoloader->boot(); })->throwsNoExceptions(); @@ -39,7 +39,7 @@ }); it('can discover paths to all layers', function () { - $autoloader = new Autoloader; + $autoloader = new AutoloadManager; $expected = [ app()->basePath('src/Domain'), diff --git a/tests/TestCase.php b/tests/TestCase.php index da96a0c..b634255 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -43,8 +43,6 @@ public static function resetConfig() static::$configValues = []; } - protected function defineConfigBeforeEnvironment() {} - protected function defineEnvironment($app) { if (in_array(BootsTestApplication::class, class_uses_recursive($this))) {