diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 79a630891..af06931ac 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -39,7 +39,6 @@ use Bavix\Wallet\Services\CommonServiceLegacy; use Bavix\Wallet\Services\MetaServiceLegacy; use Bavix\Wallet\Services\TaxServiceInterface; - use Bavix\Wallet\Services\WalletServiceLegacy; override(\app(0), map([ // internal.assembler @@ -93,7 +92,6 @@ // lagacy.services CommonServiceLegacy::class => CommonServiceLegacy::class, MetaServiceLegacy::class => MetaServiceLegacy::class, - WalletServiceLegacy::class => WalletServiceLegacy::class, ])); } diff --git a/changelog.md b/changelog.md index 4426598be..84becdaf4 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Transaction support. +- Now, within the transaction, the wallet has its own balance state. + +### Updated +- Due to the state within transactions, I was able to speed up the computation up to 25 times for complex transfers. + +### Removed +- class `WalletServiceLegacy` + ## [7.0.0] - 2021-11-25 ### Updated - Optimization of the `payFreeCart` and `payFree` request. Now the package does not update the repository. But there is no point in updating it, because the client does not pay anything. diff --git a/composer.json b/composer.json index 7c8b4782a..caca81a94 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,8 @@ "vimeo/psalm": "^4.12" }, "suggest": { - "bavix/laravel-wallet-swap": "Addition to the laravel-wallet library for quick setting of exchange rates" + "bavix/laravel-wallet-swap": "Addition to the laravel-wallet library for quick setting of exchange rates", + "bavix/laravel-wallet-warmup": "Addition to the laravel-wallet library for refresh balance wallets" }, "autoload": { "psr-4": { diff --git a/config/config.php b/config/config.php index 2a4bd2851..cd334abd3 100644 --- a/config/config.php +++ b/config/config.php @@ -33,6 +33,7 @@ use Bavix\Wallet\Services\ExchangeService; use Bavix\Wallet\Services\PrepareService; use Bavix\Wallet\Services\PurchaseService; +use Bavix\Wallet\Services\RegulatorService; use Bavix\Wallet\Services\TaxService; return [ @@ -78,6 +79,7 @@ 'atomic' => AtomicService::class, 'basket' => BasketService::class, 'bookkeeper' => BookkeeperService::class, + 'regulator' => RegulatorService::class, 'cast' => CastService::class, 'consistency' => ConsistencyService::class, 'discount' => DiscountService::class, diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 9014ae9de..ed56ea5c2 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -28,7 +28,12 @@ - [Refund](refund) - [Gift](gift) - [Cart](cart) - + +- Transactions + + - [Transaction](transaction) + - [Race condition](race-condition) + - Additions - [Wallet Swap](laravel-wallet-swap) diff --git a/docs/race-condition.md b/docs/race-condition.md new file mode 100644 index 000000000..06a1d7a5f --- /dev/null +++ b/docs/race-condition.md @@ -0,0 +1,37 @@ +## Race Condition + +A common issue in the issue is about race conditions. + +If you have not yet imported the config into the project, then you need to do this. +```bash +php artisan vendor:publish --tag=laravel-wallet-config +``` + +Previously, there was a vacuum package, but now it is a part of the core. You just need to configure the lock service and the cache service in the package configuration `wallet.php`. + +```php + /** + * A system for dealing with race conditions. + */ + 'lock' => [ + 'driver' => 'array', + 'seconds' => 1, + ], +``` + +To enable the fight against race conditions, you need to select a provider that supports work with locks. I recommend `redis`. + +There is a setting for storing the state of the wallet, I recommend choosing `redis` here too. + +```php + /** + * Storage of the state of the balance of wallets. + */ + 'cache' => ['driver' => 'array'], +``` + +You need `redis-server` and `php-redis`. + +Redis is recommended but not required. You can choose whatever the [framework](https://laravel.com/docs/8.x/cache#introduction) offers you. + +It worked! diff --git a/docs/transaction.md b/docs/transaction.md new file mode 100644 index 000000000..771a4ad57 --- /dev/null +++ b/docs/transaction.md @@ -0,0 +1,37 @@ +## Transaction + +Sometimes you need to execute many simple queries. You want to keep the data atomic. To do this, you need `laravel-wallet` v7.1+. + +It is necessary to write off the amount from the balance and raise the ad in the search. What happens if the service for raising an ad fails? We wrote off the money, but did not raise the ad. Received reputational losses. We can imagine the opposite situation, we first raise the ad in the search, but it does not work to write off the money. There are not enough funds. This functionality will help to solve all this. We monitor ONLY the state of the wallet, the rest falls on the developer. Let's take an unsuccessful lift, for example. + +```php +use Bavix\Wallet\Internal\Service\DatabaseServiceInterface; + +/** @var object $businessLogicService */ +/** @var \Bavix\Wallet\Models\Wallet $payer */ +$payer->balanceInt; // 9999 +app(DatabaseServiceInterface::class)->transaction(statuc function () use ($payer) { + $payer->withdraw(1000); // 8999 + $businessLogicService->doingMagic($payer); // throws an exception +}); // rollback payer balance + +$payer->balanceInt; // 9999 +``` + +Now let's look at the successful raising of the ad. + +```php +use Bavix\Wallet\Internal\Service\DatabaseServiceInterface; + +/** @var object $businessLogicService */ +/** @var \Bavix\Wallet\Models\Wallet $payer */ +$payer->balanceInt; // 9999 +app(DatabaseServiceInterface::class)->transaction(statuc function () use ($payer) { + $payer->withdraw(1000); // 8999 + $businessLogicService->doingMagic($payer); // successfully +}); // commit payer balance + +$payer->balanceInt; // 8999 +``` + +It worked! diff --git a/rector.php b/rector.php index 5f074391b..83cb68547 100644 --- a/rector.php +++ b/rector.php @@ -5,6 +5,7 @@ use Rector\Core\Configuration\Option; use Rector\Laravel\Set\LaravelSetList; use Rector\Php74\Rector\Property\TypedPropertyRector; +use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\SetList; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; @@ -17,6 +18,7 @@ ]); // Define what rule sets will be applied + $containerConfigurator->import(PHPUnitSetList::PHPUNIT_91); $containerConfigurator->import(LaravelSetList::LARAVEL_60); $containerConfigurator->import(SetList::DEAD_CODE); $containerConfigurator->import(SetList::PHP_74); diff --git a/src/Internal/Service/DatabaseService.php b/src/Internal/Service/DatabaseService.php index 5d475fec1..21d50dae5 100644 --- a/src/Internal/Service/DatabaseService.php +++ b/src/Internal/Service/DatabaseService.php @@ -6,7 +6,7 @@ use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; use Bavix\Wallet\Internal\Exceptions\TransactionFailedException; -use Closure; +use Bavix\Wallet\Services\RegulatorServiceInterface; use Illuminate\Config\Repository as ConfigRepository; use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionResolverInterface; @@ -15,12 +15,15 @@ final class DatabaseService implements DatabaseServiceInterface { + private RegulatorServiceInterface $regulatorService; private ConnectionInterface $connection; public function __construct( ConnectionResolverInterface $connectionResolver, + RegulatorServiceInterface $regulatorService, ConfigRepository $config ) { + $this->regulatorService = $regulatorService; $this->connection = $connectionResolver->connection( $config->get('wallet.database.connection') ); @@ -40,10 +43,25 @@ public function transaction(callable $callback) return $callback(); } - return $this->connection->transaction(Closure::fromCallable($callback)); + $this->regulatorService->purge(); + + return $this->connection->transaction(function () use ($callback) { + $result = $callback(); + if ($result === false || (is_countable($result) && count($result) === 0)) { + $this->regulatorService->purge(); + } else { + $this->regulatorService->approve(); + } + + return $result; + }); } catch (RecordsNotFoundException|ExceptionInterface $exception) { + $this->regulatorService->purge(); + throw $exception; } catch (Throwable $throwable) { + $this->regulatorService->purge(); + throw new TransactionFailedException( 'Transaction failed', ExceptionInterface::TRANSACTION_FAILED, diff --git a/src/Internal/Service/StorageService.php b/src/Internal/Service/StorageService.php index 3162adaea..0a7ec04fe 100644 --- a/src/Internal/Service/StorageService.php +++ b/src/Internal/Service/StorageService.php @@ -7,43 +7,38 @@ use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; use Bavix\Wallet\Internal\Exceptions\LockProviderNotFoundException; use Bavix\Wallet\Internal\Exceptions\RecordNotFoundException; -use Illuminate\Cache\CacheManager; -use Illuminate\Config\Repository as ConfigRepository; use Illuminate\Contracts\Cache\Repository as CacheRepository; final class StorageService implements StorageServiceInterface { private LockServiceInterface $lockService; private MathServiceInterface $mathService; - private CacheRepository $cache; + private CacheRepository $cacheRepository; public function __construct( - CacheManager $cacheManager, - ConfigRepository $config, LockServiceInterface $lockService, - MathServiceInterface $mathService + MathServiceInterface $mathService, + CacheRepository $cacheRepository ) { + $this->cacheRepository = $cacheRepository; $this->mathService = $mathService; $this->lockService = $lockService; - $this->cache = $cacheManager->driver( - $config->get('wallet.cache.driver', 'array') - ); } public function flush(): bool { - return $this->cache->clear(); + return $this->cacheRepository->clear(); } public function missing(string $key): bool { - return $this->cache->forget($key); + return $this->cacheRepository->forget($key); } /** @throws RecordNotFoundException */ public function get(string $key): string { - $value = $this->cache->get($key); + $value = $this->cacheRepository->get($key); if ($value === null) { throw new RecordNotFoundException( 'The repository did not find the object', @@ -54,9 +49,10 @@ public function get(string $key): string return $this->mathService->round($value); } + /** @param float|int|string $value */ public function sync(string $key, $value): bool { - return $this->cache->set($key, $value); + return $this->cacheRepository->set($key, $value); } /** diff --git a/src/Models/Wallet.php b/src/Models/Wallet.php index ef78f5d1d..32078f016 100644 --- a/src/Models/Wallet.php +++ b/src/Models/Wallet.php @@ -13,8 +13,9 @@ use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; use Bavix\Wallet\Internal\Exceptions\LockProviderNotFoundException; use Bavix\Wallet\Internal\Exceptions\TransactionFailedException; -use Bavix\Wallet\Internal\Service\DatabaseServiceInterface; -use Bavix\Wallet\Services\WalletServiceLegacy; +use Bavix\Wallet\Internal\Service\MathServiceInterface; +use Bavix\Wallet\Services\AtomicServiceInterface; +use Bavix\Wallet\Services\RegulatorServiceInterface; use Bavix\Wallet\Traits\CanConfirm; use Bavix\Wallet\Traits\CanExchange; use Bavix\Wallet\Traits\CanPayFloat; @@ -107,9 +108,15 @@ public function setNameAttribute(string $name): void */ public function refreshBalance(): bool { - return app(DatabaseServiceInterface::class)->transaction( - fn () => app(WalletServiceLegacy::class)->refresh($this) - ); + return app(AtomicServiceInterface::class)->block($this, function () { + $whatIs = $this->balance; + $balance = $this->getAvailableBalanceAttribute(); + if (app(MathServiceInterface::class)->compare($whatIs, $balance) === 0) { + return true; + } + + return app(RegulatorServiceInterface::class)->sync($this, $balance); + }); } /** @codeCoverageIgnore */ @@ -125,7 +132,7 @@ public function getOriginalBalanceAttribute(): string /** * @return float|int */ - public function getAvailableBalance() + public function getAvailableBalanceAttribute() { return $this->transactions() ->where('wallet_id', $this->getKey()) @@ -134,6 +141,18 @@ public function getAvailableBalance() ; } + /** + * @deprecated + * @see getAvailableBalanceAttribute + * @codeCoverageIgnore + * + * @return float|int + */ + public function getAvailableBalance() + { + return $this->getAvailableBalanceAttribute(); + } + public function holder(): MorphTo { return $this->morphTo(); diff --git a/src/Services/CastService.php b/src/Services/CastService.php index ffc9a6b33..66dd03fb8 100644 --- a/src/Services/CastService.php +++ b/src/Services/CastService.php @@ -19,8 +19,8 @@ public function getWallet(Wallet $object, bool $save = true): WalletModel assert($wallet instanceof WalletModel); } - if ($save) { - $wallet->exists or $wallet->save(); + if ($save && !$wallet->exists) { + $wallet->save(); } return $wallet; diff --git a/src/Services/CommonServiceLegacy.php b/src/Services/CommonServiceLegacy.php index 5200e6850..8ef410f65 100644 --- a/src/Services/CommonServiceLegacy.php +++ b/src/Services/CommonServiceLegacy.php @@ -15,7 +15,6 @@ use Bavix\Wallet\Internal\Service\DatabaseServiceInterface; use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; -use Bavix\Wallet\Models\Wallet as WalletModel; use Illuminate\Database\RecordsNotFoundException; /** @deprecated */ @@ -26,24 +25,24 @@ final class CommonServiceLegacy private DatabaseServiceInterface $databaseService; private AssistantServiceInterface $assistantService; private PrepareServiceInterface $prepareService; - private BookkeeperServiceInterface $bookkeeper; + private RegulatorServiceInterface $regulatorService; private TransferDtoAssemblerInterface $transferDtoAssembler; public function __construct( CastServiceInterface $castService, - BookkeeperServiceInterface $bookkeeper, AssistantServiceInterface $satisfyService, DatabaseServiceInterface $databaseService, PrepareServiceInterface $prepareService, TransferDtoAssemblerInterface $transferDtoAssembler, + RegulatorServiceInterface $regulatorService, AtmServiceInterface $atmService ) { $this->atmService = $atmService; $this->castService = $castService; - $this->bookkeeper = $bookkeeper; $this->assistantService = $satisfyService; $this->databaseService = $databaseService; $this->prepareService = $prepareService; + $this->regulatorService = $regulatorService; $this->transferDtoAssembler = $transferDtoAssembler; } @@ -116,41 +115,6 @@ public function applyTransfers(array $objects): array }); } - /** - * @param int|string $amount - * - * @deprecated - * - * @throws LockProviderNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface - */ - public function addBalance(Wallet $wallet, $amount): bool - { - return $this->databaseService->transaction(function () use ($wallet, $amount) { - /** @var WalletModel $wallet */ - $walletObject = $this->castService->getWallet($wallet); - $balance = $this->bookkeeper->increase($walletObject, $amount); - $result = 0; - - try { - $result = $walletObject->newQuery() - ->whereKey($walletObject->getKey()) - ->update(['balance' => $balance]) - ; - - $walletObject->fill(['balance' => $balance])->syncOriginalAttribute('balance'); - } finally { - if ($result === 0) { - $this->bookkeeper->missing($walletObject); - } - } - - return (bool) $result; - }); - } - /** * @param float|int|string $amount * @@ -196,10 +160,7 @@ public function applyTransactions(array $wallets, array $objects): array $object = $this->castService->getWallet($wallet); assert((int) $object->getKey() === $walletId); - $balance = $this->bookkeeper->increase($object, $total); - - $object->newQuery()->whereKey($object->getKey())->update(['balance' => $balance]); // ?qN - $object->fill(['balance' => $balance])->syncOriginalAttribute('balance'); + $this->regulatorService->increase($object, $total); } return $transactions; diff --git a/src/Services/RegulatorService.php b/src/Services/RegulatorService.php new file mode 100644 index 000000000..2da3cc3ec --- /dev/null +++ b/src/Services/RegulatorService.php @@ -0,0 +1,124 @@ +idempotentKey = $uuidFactoryService->uuid4(); + $this->bookkeeperService = $bookkeeperService; + $this->storageService = $storageService; + $this->mathService = $mathService; + } + + public function missing(Wallet $wallet): bool + { + unset($this->wallets[$wallet->uuid]); + + return $this->storageService->missing($this->getKey($wallet->uuid)); + } + + public function diff(Wallet $wallet): string + { + try { + return $this->mathService->round($this->storageService->get($this->getKey($wallet->uuid))); + } catch (RecordNotFoundException $exception) { + return '0'; + } + } + + public function amount(Wallet $wallet): string + { + return $this->mathService->round( + $this->mathService->add($this->bookkeeperService->amount($wallet), $this->diff($wallet)) + ); + } + + /** @param float|int|string $value */ + public function sync(Wallet $wallet, $value): bool + { + $this->persist($wallet); + + return $this->storageService->sync( + $this->getKey($wallet->uuid), + $this->mathService->round( + $this->mathService->negative($this->mathService->sub($this->amount($wallet), $value)) + ) + ); + } + + /** @param float|int|string $value */ + public function increase(Wallet $wallet, $value): string + { + $this->persist($wallet); + + try { + $this->storageService->increase($this->getKey($wallet->uuid), $value); + } catch (RecordNotFoundException $exception) { + $value = $this->mathService->round($value); + $this->storageService->sync($this->getKey($wallet->uuid), $value); + } + + return $this->amount($wallet); + } + + /** @param float|int|string $value */ + public function decrease(Wallet $wallet, $value): string + { + return $this->increase($wallet, $this->mathService->negative($value)); + } + + public function approve(): void + { + foreach ($this->wallets as $wallet) { + $diffValue = $this->diff($wallet); + if ($this->mathService->compare($diffValue, 0) === 0) { + continue; + } + + $balance = $this->bookkeeperService->increase($wallet, $diffValue); + $wallet->newQuery()->whereKey($wallet->getKey())->update(['balance' => $balance]); // ?qN + $wallet->fill(['balance' => $balance])->syncOriginalAttribute('balance'); + } + + $this->purge(); + } + + public function purge(): void + { + foreach ($this->wallets as $wallet) { + $this->missing($wallet); + } + } + + private function persist(Wallet $wallet): void + { + $this->wallets[$wallet->uuid] = $wallet; + } + + private function getKey(string $uuid): string + { + return $this->idempotentKey.'::'.$uuid; + } +} diff --git a/src/Services/RegulatorServiceInterface.php b/src/Services/RegulatorServiceInterface.php new file mode 100644 index 000000000..956ec2b7c --- /dev/null +++ b/src/Services/RegulatorServiceInterface.php @@ -0,0 +1,29 @@ +math = $math; - $this->bookkeeper = $bookkeeper; - $this->atomicService = $atomicService; - } - - /** - * @throws LockProviderNotFoundException - * @throws RecordsNotFoundException - * @throws TransactionFailedException - * @throws ExceptionInterface - * - * @see WalletModel::refreshBalance() - * @deprecated - */ - public function refresh(WalletModel $wallet): bool - { - return $this->atomicService->block($wallet, function () use ($wallet) { - $whatIs = $wallet->balance; - $balance = $wallet->getAvailableBalance(); - if ($this->math->compare($whatIs, $balance) === 0) { - return true; - } - - $wallet->balance = (string) $balance; - - return $wallet->save() && $this->bookkeeper->sync($wallet, $balance); - }); - } -} diff --git a/src/Traits/CanConfirm.php b/src/Traits/CanConfirm.php index 6872ca36b..e12910957 100644 --- a/src/Traits/CanConfirm.php +++ b/src/Traits/CanConfirm.php @@ -18,8 +18,8 @@ use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Services\AtomicServiceInterface; use Bavix\Wallet\Services\CastServiceInterface; -use Bavix\Wallet\Services\CommonServiceLegacy; use Bavix\Wallet\Services\ConsistencyServiceInterface; +use Bavix\Wallet\Services\RegulatorServiceInterface; use Illuminate\Database\RecordsNotFoundException; trait CanConfirm @@ -77,14 +77,9 @@ public function resetConfirm(Transaction $transaction): bool } $wallet = app(CastServiceInterface::class)->getWallet($this); - $mathService = app(MathServiceInterface::class); - $negativeAmount = $mathService->negative($transaction->amount); + app(RegulatorServiceInterface::class)->decrease($wallet, $transaction->amount); - return $transaction->update(['confirmed' => false]) && - // update balance - app(CommonServiceLegacy::class) - ->addBalance($wallet, $negativeAmount) - ; + return $transaction->update(['confirmed' => false]); }); } @@ -124,11 +119,9 @@ public function forceConfirm(Transaction $transaction): bool ); } - return $transaction->update(['confirmed' => true]) && - // update balance - app(CommonServiceLegacy::class) - ->addBalance($wallet, $transaction->amount) - ; + app(RegulatorServiceInterface::class)->increase($wallet, $transaction->amount); + + return $transaction->update(['confirmed' => true]); }); } } diff --git a/src/Traits/HasWallet.php b/src/Traits/HasWallet.php index 16170096f..797d92a18 100644 --- a/src/Traits/HasWallet.php +++ b/src/Traits/HasWallet.php @@ -17,10 +17,10 @@ use Bavix\Wallet\Models\Transfer; use Bavix\Wallet\Models\Wallet as WalletModel; use Bavix\Wallet\Services\AtomicServiceInterface; -use Bavix\Wallet\Services\BookkeeperServiceInterface; use Bavix\Wallet\Services\CastServiceInterface; use Bavix\Wallet\Services\CommonServiceLegacy; use Bavix\Wallet\Services\ConsistencyServiceInterface; +use Bavix\Wallet\Services\RegulatorServiceInterface; use function config; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\RecordsNotFoundException; @@ -83,7 +83,7 @@ public function deposit($amount, ?array $meta = null, bool $confirmed = true): T public function getBalanceAttribute() { /** @var Wallet $this */ - return app(BookkeeperServiceInterface::class)->amount( + return app(RegulatorServiceInterface::class)->amount( app(CastServiceInterface::class)->getWallet($this) ); } diff --git a/src/WalletServiceProvider.php b/src/WalletServiceProvider.php index c67f21c88..f22ae9adb 100644 --- a/src/WalletServiceProvider.php +++ b/src/WalletServiceProvider.php @@ -65,12 +65,14 @@ use Bavix\Wallet\Services\PrepareServiceInterface; use Bavix\Wallet\Services\PurchaseService; use Bavix\Wallet\Services\PurchaseServiceInterface; +use Bavix\Wallet\Services\RegulatorService; +use Bavix\Wallet\Services\RegulatorServiceInterface; use Bavix\Wallet\Services\TaxService; use Bavix\Wallet\Services\TaxServiceInterface; -use Bavix\Wallet\Services\WalletServiceLegacy; use function config; use function dirname; use function function_exists; +use Illuminate\Cache\CacheManager; use Illuminate\Support\ServiceProvider; final class WalletServiceProvider extends ServiceProvider @@ -118,6 +120,8 @@ public function register(): void $configure = config('wallet', []); + $this->contextBinding($configure['cache'] ?? []); + $this->internal($configure['internal'] ?? []); $this->services($configure['services'] ?? []); $this->legacySingleton(); // without configuration @@ -129,7 +133,7 @@ public function register(): void $this->bindObjects($configure); } - public function repositories(array $configure): void + private function repositories(array $configure): void { $this->app->singleton( TransactionRepositoryInterface::class, @@ -150,13 +154,40 @@ private function shouldMigrate(): bool return WalletConfigure::isRunsMigrations(); } + /** @codeCoverageIgnore */ + private function contextBinding(array $bookkeeperStore): void + { + $this->app->when(BookkeeperServiceInterface::class) + ->needs(StorageServiceInterface::class) + ->give(fn () => $this->app->make( + StorageServiceInterface::class, + [ + 'cacheRepository' => $this->app->make(CacheManager::class) + ->driver($bookkeeperStore['driver'] ?? 'array'), + ], + )) + ; + + $this->app->when(RegulatorServiceInterface::class) + ->needs(StorageServiceInterface::class) + ->give(fn () => $this->app->make( + StorageServiceInterface::class, + [ + 'cacheRepository' => $this->app->make(CacheManager::class) + ->driver('array'), + ], + )) + ; + } + private function internal(array $configure): void { + $this->app->bind(StorageServiceInterface::class, $configure['storage'] ?? StorageService::class); + $this->app->singleton(DatabaseServiceInterface::class, $configure['database'] ?? DatabaseService::class); $this->app->singleton(JsonServiceInterface::class, $configure['json'] ?? JsonService::class); $this->app->singleton(LockServiceInterface::class, $configure['lock'] ?? LockService::class); $this->app->singleton(MathServiceInterface::class, $configure['math'] ?? MathService::class); - $this->app->singleton(StorageServiceInterface::class, $configure['storage'] ?? StorageService::class); $this->app->singleton(TranslatorServiceInterface::class, $configure['translator'] ?? TranslatorService::class); $this->app->singleton(UuidFactoryServiceInterface::class, $configure['uuid'] ?? UuidFactoryService::class); } @@ -168,6 +199,7 @@ private function services(array $configure): void $this->app->singleton(AtomicServiceInterface::class, $configure['atomic'] ?? AtomicService::class); $this->app->singleton(BasketServiceInterface::class, $configure['basket'] ?? BasketService::class); $this->app->singleton(BookkeeperServiceInterface::class, $configure['bookkeeper'] ?? BookkeeperService::class); + $this->app->singleton(RegulatorServiceInterface::class, $configure['regulator'] ?? RegulatorService::class); $this->app->singleton(CastServiceInterface::class, $configure['cast'] ?? CastService::class); $this->app->singleton(ConsistencyServiceInterface::class, $configure['consistency'] ?? ConsistencyService::class); $this->app->singleton(DiscountServiceInterface::class, $configure['discount'] ?? DiscountService::class); @@ -226,7 +258,6 @@ private function transformers(array $configure): void private function legacySingleton(): void { $this->app->singleton(CommonServiceLegacy::class); - $this->app->singleton(WalletServiceLegacy::class); $this->app->singleton(MetaServiceLegacy::class); } diff --git a/tests/Infra/TestCase.php b/tests/Infra/TestCase.php index e606220ca..0c31da1be 100644 --- a/tests/Infra/TestCase.php +++ b/tests/Infra/TestCase.php @@ -11,6 +11,7 @@ use Illuminate\Config\Repository; use Illuminate\Foundation\Application; use Illuminate\Foundation\Testing\RefreshDatabase; +use Illuminate\Support\Facades\DB; use Orchestra\Testbench\TestCase as OrchestraTestCase; /** @@ -20,6 +21,12 @@ class TestCase extends OrchestraTestCase { use RefreshDatabase; + protected function setUp(): void + { + parent::setUp(); + DB::transactionLevel() && DB::rollBack(); + } + public function expectExceptionMessageStrict(string $message): void { $this->expectExceptionMessageMatches("~^{$message}$~"); diff --git a/tests/Units/Domain/BalanceTest.php b/tests/Units/Domain/BalanceTest.php index 6709178a8..e08cb213e 100644 --- a/tests/Units/Domain/BalanceTest.php +++ b/tests/Units/Domain/BalanceTest.php @@ -7,11 +7,10 @@ use function app; use Bavix\Wallet\Models\Wallet; use Bavix\Wallet\Services\BookkeeperServiceInterface; -use Bavix\Wallet\Services\CommonServiceLegacy; +use Bavix\Wallet\Services\RegulatorServiceInterface; use Bavix\Wallet\Test\Infra\Factories\BuyerFactory; use Bavix\Wallet\Test\Infra\Models\Buyer; use Bavix\Wallet\Test\Infra\TestCase; -use Illuminate\Database\SQLiteConnection; use PDOException; use PHPUnit\Framework\MockObject\MockObject; @@ -91,31 +90,39 @@ public function testSimple(): void $wallet->deposit(1000); self::assertSame(1000, $wallet->balanceInt); - $result = app(CommonServiceLegacy::class)->addBalance($wallet, 100); - self::assertTrue($result); + $regulator = app(RegulatorServiceInterface::class); + $result = $regulator->increase($wallet, 100); - self::assertSame($wallet->balanceInt, 1100); + self::assertSame(100, (int) $regulator->diff($wallet)); + self::assertSame(1100, (int) $regulator->amount($wallet)); + self::assertSame(1100, (int) $result); + + self::assertSame(1100, $wallet->balanceInt); self::assertTrue($wallet->refreshBalance()); - self::assertSame($wallet->balanceInt, 1000); + self::assertSame(0, (int) $regulator->diff($wallet)); + self::assertSame(1000, (int) $regulator->amount($wallet)); + self::assertSame(1000, $wallet->balanceInt); $key = $wallet->getKey(); self::assertTrue($wallet->delete()); self::assertFalse($wallet->exists); self::assertSame($wallet->getKey(), $key); - $result = app(CommonServiceLegacy::class)->addBalance($wallet, 100); - self::assertTrue($result); // automatic create default wallet + $result = app(RegulatorServiceInterface::class)->increase($wallet, 100); + + // databases that do not support fk will not delete data... need to help them + $wallet->transactions()->where('wallet_id', $key)->delete(); - $wallet->refreshBalance(); - $balance = 0; - if ($wallet->getConnection() instanceof SQLiteConnection) { - $balance = 1000; - } + self::assertFalse($wallet->exists); + self::assertSame(1100, (int) $result); + + $wallet->refreshBalance(); // automatic create default wallet + self::assertTrue($wallet->exists); - self::assertSame($wallet->balanceInt, (int) $balance); + self::assertSame(0, $wallet->balanceInt); $wallet->deposit(1); - self::assertSame($wallet->balanceInt, (int) $balance + 1); + self::assertSame(1, $wallet->balanceInt); } /** @@ -180,83 +187,4 @@ public function testEqualWallet(): void self::assertSame($wallet->getKey(), $wallet->wallet->wallet->getKey()); self::assertSame($wallet->getKey(), $wallet->wallet->wallet->wallet->getKey()); } - - /** - * @see https://github.com/bavix/laravel-wallet/issues/49 - */ - public function testForceUpdate(): void - { - /** @var Buyer $buyer */ - $buyer = BuyerFactory::new()->create(); - $wallet = $buyer->wallet; - - self::assertSame(0, $wallet->balanceInt); - - $wallet->deposit(1000); - self::assertSame(1000, $wallet->balanceInt); - - Wallet::whereKey($buyer->wallet->getKey()) - ->update(['balance' => 10]) - ; - - /** - * Create a state when the cache is empty. - * For example, something went wrong and your database has incorrect data. - * Unfortunately, the library will work with what is. - * But there is an opportunity to recount the balance. - * - * Here is an example: - */ - app(BookkeeperServiceInterface::class)->missing($buyer->wallet); - self::assertSame(1000, (int) $wallet->getRawOriginal('balance')); - - /** - * We load the model from the base and our balance is 10. - */ - $wallet->refresh(); - self::assertSame(10, $wallet->balanceInt); - self::assertSame(10, (int) $wallet->getRawOriginal('balance')); - - /** - * Now we fill the cache with relevant data (PS, the data inside the model will be updated). - */ - $wallet->refreshBalance(); - self::assertSame(1000, $wallet->balanceInt); - self::assertSame(1000, (int) $wallet->getRawOriginal('balance')); - } - - public function testFailUpdate(): void - { - /** @var Buyer $buyer */ - $buyer = BuyerFactory::new()->create(); - self::assertFalse($buyer->relationLoaded('wallet')); - $wallet = $buyer->wallet; - - self::assertFalse($wallet->exists); - self::assertSame(0, $wallet->balanceInt); - self::assertTrue($wallet->exists); - - /** @var MockObject|Wallet $mockQuery */ - $mockQuery = $this->createMock(\get_class($wallet->newQuery())); - $mockQuery->method('whereKey')->willReturn($mockQuery); - $mockQuery->method('update')->willReturn(0); - - /** @var MockObject|Wallet $mockWallet */ - $mockWallet = $this->createMock(\get_class($wallet)); - $mockWallet->method('newQuery')->willReturn($mockQuery); - $mockWallet->method('getKey')->willReturn($wallet->getKey()); - $mockWallet->method('fill')->willReturn($mockWallet); - $mockWallet->method('syncOriginalAttribute')->willReturn($mockWallet); - $mockWallet->method('__get')->with('uuid')->willReturn($wallet->uuid); - - $bookkeeper = app(BookkeeperServiceInterface::class); - $bookkeeper->sync($wallet, 100500); // init - - $result = app(CommonServiceLegacy::class) - ->addBalance($mockWallet, 100) - ; - - self::assertFalse($result); - self::assertSame('0', $bookkeeper->amount($wallet)); - } } diff --git a/tests/Units/Domain/BasketTest.php b/tests/Units/Domain/BasketTest.php index 9f23bf802..73bf01e9b 100644 --- a/tests/Units/Domain/BasketTest.php +++ b/tests/Units/Domain/BasketTest.php @@ -26,9 +26,11 @@ public function testCount(): void $items = $basket->items(); self::assertNotFalse(current($items)); + self::assertSame(0, key($items)); self::assertSame(24, current($items)->count()); self::assertNotFalse(next($items)); self::assertSame(26, current($items)->count()); + self::assertSame(1, key($items)); } public function testMeta(): void diff --git a/tests/Units/Domain/CartTest.php b/tests/Units/Domain/CartTest.php index 7891616f0..147e1165a 100644 --- a/tests/Units/Domain/CartTest.php +++ b/tests/Units/Domain/CartTest.php @@ -201,6 +201,9 @@ public function testModelNotFoundException(): void } self::assertCount($total, $cart->getItems()); + self::assertCount(count($products) - 1, $cart->getBasketDto()->items()); + self::assertCount($total, $cart->getBasketDto()->cursor()); + self::assertSame($total, $cart->getBasketDto()->total()); $transfers = $buyer->payCart($cart); self::assertCount($total, $transfers); @@ -254,6 +257,7 @@ public function testBoughtGoods(): void public function testWithdrawal(): void { $transactionLevel = Buyer::query()->getConnection()->transactionLevel(); + self::assertSame(0, $transactionLevel); /** * @var Buyer $buyer diff --git a/tests/Units/Domain/ConfirmTest.php b/tests/Units/Domain/ConfirmTest.php index 1b423ffa2..b3302b365 100644 --- a/tests/Units/Domain/ConfirmTest.php +++ b/tests/Units/Domain/ConfirmTest.php @@ -8,6 +8,8 @@ use Bavix\Wallet\Exceptions\UnconfirmedInvalid; use Bavix\Wallet\Exceptions\WalletOwnerInvalid; use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; +use Bavix\Wallet\Services\BookkeeperServiceInterface; +use Bavix\Wallet\Services\RegulatorServiceInterface; use Bavix\Wallet\Test\Infra\Factories\BuyerFactory; use Bavix\Wallet\Test\Infra\Factories\UserConfirmFactory; use Bavix\Wallet\Test\Infra\Models\Buyer; @@ -33,6 +35,9 @@ public function testSimple(): void self::assertFalse($transaction->confirmed); $wallet->confirm($transaction); + self::assertSame($transaction->amountInt, (int) app(BookkeeperServiceInterface::class)->amount($wallet)); + self::assertSame($transaction->amountInt, (int) app(RegulatorServiceInterface::class)->amount($wallet)); + self::assertSame(0, (int) app(RegulatorServiceInterface::class)->diff($wallet)); self::assertSame($transaction->amountInt, $wallet->balanceInt); self::assertTrue($transaction->confirmed); } @@ -68,6 +73,9 @@ public function testSafeResetConfirm(): void self::assertTrue($transaction->confirmed); $wallet->safeResetConfirm($transaction); + self::assertSame(0, (int) app(BookkeeperServiceInterface::class)->amount($wallet)); + self::assertSame(0, (int) app(RegulatorServiceInterface::class)->amount($wallet)); + self::assertSame(0, (int) app(RegulatorServiceInterface::class)->diff($wallet)); self::assertSame(0, $wallet->balanceInt); self::assertFalse($transaction->confirmed); } diff --git a/tests/Units/Domain/MultiWalletTest.php b/tests/Units/Domain/MultiWalletTest.php index 6a6467717..d0ac3cf85 100644 --- a/tests/Units/Domain/MultiWalletTest.php +++ b/tests/Units/Domain/MultiWalletTest.php @@ -6,17 +6,22 @@ use Bavix\Wallet\Exceptions\AmountInvalid; use Bavix\Wallet\Exceptions\BalanceIsEmpty; +use Bavix\Wallet\Exceptions\InsufficientFunds; use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; use Bavix\Wallet\Internal\Exceptions\ModelNotFoundException; +use Bavix\Wallet\Internal\Service\DatabaseServiceInterface; use Bavix\Wallet\Internal\Service\UuidFactoryServiceInterface; use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Models\Transfer; +use Bavix\Wallet\Services\BookkeeperServiceInterface; +use Bavix\Wallet\Services\RegulatorServiceInterface; use Bavix\Wallet\Test\Infra\Factories\ItemFactory; use Bavix\Wallet\Test\Infra\Factories\UserCashierFactory; use Bavix\Wallet\Test\Infra\Factories\UserMultiFactory; use Bavix\Wallet\Test\Infra\Models\Item; use Bavix\Wallet\Test\Infra\Models\UserCashier; use Bavix\Wallet\Test\Infra\Models\UserMulti; +use Bavix\Wallet\Test\Infra\PackageModels\Wallet; use Bavix\Wallet\Test\Infra\TestCase; use function compact; use Illuminate\Database\QueryException; @@ -522,4 +527,52 @@ public function testDecimalPlaces(): void $user->deposit(1_000_000_000); self::assertSame(1000., (float) $wallet->balanceFloat); } + + public function testMultiWalletTransactionState(): void + { + /** @var UserMulti $user */ + $user = UserMultiFactory::new()->create(); + + /** @var Wallet[] $wallets */ + $wallets = []; + foreach (range(1, 10) as $item) { + $wallets[] = $user->createWallet(['name' => 'index'.$item]); + } + + self::assertCount(10, $wallets); + foreach ($wallets as $wallet) { + self::assertSame(0, (int) app(RegulatorServiceInterface::class)->diff($wallet)); + } + + $funds = null; + + try { + app(DatabaseServiceInterface::class)->transaction(function () use ($wallets) { + foreach ($wallets as $key => $wallet) { + $wallet->deposit(1000 + $key); // 1000 + [0...9] + $wallet->withdraw(100); + $wallet->deposit(50); + + $value = 950 + $key; + self::assertSame($value, $wallet->balanceInt); + self::assertSame($value, (int) app(RegulatorServiceInterface::class)->amount($wallet)); + self::assertSame(0, (int) app(BookkeeperServiceInterface::class)->amount($wallet)); + } + + $wallet = reset($wallets); + self::assertIsObject($wallet); + + $wallet->withdraw(1000); // failed + }); + } catch (InsufficientFunds $funds) { + self::assertSame(ExceptionInterface::INSUFFICIENT_FUNDS, $funds->getCode()); + } + + self::assertNotNull($funds); + foreach ($wallets as $wallet) { + self::assertSame(0, $wallet->balanceInt); + self::assertSame(0, (int) app(RegulatorServiceInterface::class)->diff($wallet)); + self::assertSame(0, (int) app(BookkeeperServiceInterface::class)->amount($wallet)); + } + } } diff --git a/tests/Units/Domain/StateTest.php b/tests/Units/Domain/StateTest.php new file mode 100644 index 000000000..16f9bf520 --- /dev/null +++ b/tests/Units/Domain/StateTest.php @@ -0,0 +1,145 @@ +create(); + $wallet = $buyer->wallet; + + self::assertSame(0, $wallet->balanceInt); + + $wallet->deposit(1000); + self::assertSame(1000, $wallet->balanceInt); + self::assertSame(0, (int) app(RegulatorServiceInterface::class)->diff($wallet)); + + Wallet::whereKey($buyer->wallet->getKey()) + ->update(['balance' => 10]) + ; + + /** + * Create a state when the cache is empty. + * For example, something went wrong and your database has incorrect data. + * Unfortunately, the library will work with what is. + * But there is an opportunity to recount the balance. + * + * Here is an example: + */ + app(BookkeeperServiceInterface::class)->missing($buyer->wallet); + self::assertSame(1000, (int) $wallet->getRawOriginal('balance')); + + /** + * We load the model from the base and our balance is 10. + */ + $wallet->refresh(); + self::assertSame(10, $wallet->balanceInt); + self::assertSame(10, (int) $wallet->getRawOriginal('balance')); + + /** + * Now we fill the cache with relevant data (PS, the data inside the model will be updated). + */ + $wallet->refreshBalance(); + self::assertSame(1000, $wallet->balanceInt); + self::assertSame(1000, (int) $wallet->getRawOriginal('balance')); + } + + public function testTransactionRollback(): void + { + /** @var Buyer $buyer */ + $buyer = BuyerFactory::new()->create(); + self::assertFalse($buyer->relationLoaded('wallet')); + $wallet = $buyer->wallet; + + self::assertFalse($wallet->exists); + self::assertSame(0, $wallet->balanceInt); + self::assertTrue($wallet->exists); + + $bookkeeper = app(BookkeeperServiceInterface::class); + $regulator = app(RegulatorServiceInterface::class); + + $wallet->deposit(1000); + self::assertSame(0, (int) $regulator->diff($wallet)); + self::assertSame(1000, (int) $regulator->amount($wallet)); + self::assertSame(1000, (int) $bookkeeper->amount($wallet)); + self::assertSame(1000, $wallet->balanceInt); + + app(DatabaseServiceInterface::class)->transaction(function () use ($wallet, $regulator, $bookkeeper) { + $wallet->deposit(10000); + self::assertSame(10000, (int) $regulator->diff($wallet)); + self::assertSame(11000, (int) $regulator->amount($wallet)); + self::assertSame(1000, (int) $bookkeeper->amount($wallet)); + + return false; // rollback + }); + + self::assertSame(0, (int) $regulator->diff($wallet)); + self::assertSame(1000, (int) $regulator->amount($wallet)); + self::assertSame(1000, (int) $bookkeeper->amount($wallet)); + self::assertSame(1000, $wallet->balanceInt); + } + + public function testRefreshInTransaction(): void + { + /** @var Buyer $buyer */ + $buyer = BuyerFactory::new()->create(); + $buyer->deposit(10000); + + $bookkeeper = app(BookkeeperServiceInterface::class); + $regulator = app(RegulatorServiceInterface::class); + + $bookkeeper->increase($buyer->wallet, 100); + self::assertSame(10100, $buyer->balanceInt); + + app(DatabaseServiceInterface::class)->transaction(function () use ($bookkeeper, $regulator, $buyer) { + self::assertTrue($buyer->wallet->refreshBalance()); + self::assertSame(-100, (int) $regulator->diff($buyer->wallet)); + self::assertSame(10100, (int) $bookkeeper->amount($buyer->wallet)); + self::assertSame(10000, $buyer->balanceInt); // bookkeeper.amount+regulator.diff + + return false; // rollback. cancel refreshBalance + }); + + self::assertSame(0, (int) $regulator->diff($buyer->wallet)); + self::assertSame(10100, (int) $bookkeeper->amount($buyer->wallet)); + self::assertSame(10100, $buyer->balanceInt); + + app(DatabaseServiceInterface::class)->transaction(function () use ($bookkeeper, $regulator, $buyer) { + self::assertTrue($buyer->wallet->refreshBalance()); + self::assertSame(-100, (int) $regulator->diff($buyer->wallet)); + self::assertSame(10100, (int) $bookkeeper->amount($buyer->wallet)); + self::assertSame(10000, $buyer->balanceInt); // bookkeeper.amount+regulator.diff + + return []; // if count() === 0 then rollback. cancel refreshBalance + }); + + self::assertSame(0, (int) $regulator->diff($buyer->wallet)); + self::assertSame(10100, (int) $bookkeeper->amount($buyer->wallet)); + self::assertSame(10100, $buyer->balanceInt); + + self::assertTrue($buyer->wallet->refreshBalance()); + + self::assertSame(0, (int) $regulator->diff($buyer->wallet)); + self::assertSame(10000, (int) $bookkeeper->amount($buyer->wallet)); + self::assertSame(10000, $buyer->balanceInt); + } +} diff --git a/tests/Units/Domain/WalletFloatTest.php b/tests/Units/Domain/WalletFloatTest.php index 2a548f1a4..ea461ba0a 100644 --- a/tests/Units/Domain/WalletFloatTest.php +++ b/tests/Units/Domain/WalletFloatTest.php @@ -7,6 +7,7 @@ use Bavix\Wallet\Exceptions\AmountInvalid; use Bavix\Wallet\Exceptions\BalanceIsEmpty; use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; +use Bavix\Wallet\Internal\Service\DatabaseServiceInterface; use Bavix\Wallet\Internal\Service\MathServiceInterface; use Bavix\Wallet\Models\Transaction; use Bavix\Wallet\Test\Infra\Factories\UserFloatFactory; @@ -284,9 +285,12 @@ public function testBitcoin(): void $math = app(MathServiceInterface::class); - for ($i = 0; $i < 256; ++$i) { - $user->depositFloat('0.00000001'); // Satoshi - } + // optimize + app(DatabaseServiceInterface::class)->transaction(function () use ($user) { + for ($i = 0; $i < 256; ++$i) { + $user->depositFloat('0.00000001'); // Satoshi + } + }); self::assertSame($user->balance, '256'.str_repeat('0', 32 - 8)); self::assertSame(0, $math->compare($user->balanceFloat, '0.00000256')); diff --git a/tests/Units/Domain/WalletTest.php b/tests/Units/Domain/WalletTest.php index f72bbfb85..2e86d5083 100644 --- a/tests/Units/Domain/WalletTest.php +++ b/tests/Units/Domain/WalletTest.php @@ -8,11 +8,16 @@ use Bavix\Wallet\Exceptions\BalanceIsEmpty; use Bavix\Wallet\Exceptions\InsufficientFunds; use Bavix\Wallet\Internal\Exceptions\ExceptionInterface; +use Bavix\Wallet\Internal\Exceptions\TransactionFailedException; +use Bavix\Wallet\Internal\Service\DatabaseServiceInterface; use Bavix\Wallet\Models\Transaction; +use Bavix\Wallet\Services\RegulatorServiceInterface; use Bavix\Wallet\Test\Infra\Factories\UserFactory; use Bavix\Wallet\Test\Infra\Models\User; use Bavix\Wallet\Test\Infra\TestCase; use Illuminate\Database\Eloquent\Collection; +use RuntimeException; +use Throwable; /** * @internal @@ -288,4 +293,32 @@ public function testRecalculate(): void $user->withdraw($user->balanceInt); self::assertSame(0, $user->balanceInt); } + + public function testCrash(): void + { + /** @var User $user */ + $user = UserFactory::new()->create(); + $user->deposit(10000); + self::assertSame(10000, $user->balanceInt); + + try { + app(DatabaseServiceInterface::class)->transaction(static function () use ($user) { + self::assertSame(0, (int) app(RegulatorServiceInterface::class)->diff($user->wallet)); + $user->withdraw(10000); + self::assertSame(-10000, (int) app(RegulatorServiceInterface::class)->diff($user->wallet)); + self::assertSame(0, (int) app(RegulatorServiceInterface::class)->amount($user->wallet)); + + throw new RuntimeException('hello world'); + }); + } catch (Throwable $throwable) { + self::assertInstanceOf(TransactionFailedException::class, $throwable); + self::assertNotNull($throwable->getPrevious()); + + self::assertInstanceOf(RuntimeException::class, $throwable->getPrevious()); + self::assertSame('hello world', $throwable->getPrevious()->getMessage()); + } + + self::assertSame(10000, $user->balanceInt); + self::assertSame(0, (int) app(RegulatorServiceInterface::class)->diff($user->wallet)); + } } diff --git a/tests/Units/Service/SingletonTest.php b/tests/Units/Service/SingletonTest.php index 6dadbaedc..40710d5d2 100644 --- a/tests/Units/Service/SingletonTest.php +++ b/tests/Units/Service/SingletonTest.php @@ -8,7 +8,6 @@ use Bavix\Wallet\Internal\Service\MathServiceInterface; use Bavix\Wallet\Objects\Cart; use Bavix\Wallet\Services\CommonServiceLegacy; -use Bavix\Wallet\Services\WalletServiceLegacy; use Bavix\Wallet\Test\Infra\PackageModels\Transaction; use Bavix\Wallet\Test\Infra\PackageModels\Transfer; use Bavix\Wallet\Test\Infra\PackageModels\Wallet; @@ -49,11 +48,6 @@ public function testCommonService(): void self::assertSame($this->getRefId(CommonServiceLegacy::class), $this->getRefId(CommonServiceLegacy::class)); } - public function testWalletService(): void - { - self::assertSame($this->getRefId(WalletServiceLegacy::class), $this->getRefId(WalletServiceLegacy::class)); - } - public function testDatabaseService(): void { self::assertSame($this->getRefId(DatabaseServiceInterface::class), $this->getRefId(DatabaseServiceInterface::class)); diff --git a/tests/migrations/2014_10_12_000000_create_users_table.php b/tests/migrations/2014_10_12_000000_create_users_table.php index 9a087255f..6ec977bdd 100644 --- a/tests/migrations/2014_10_12_000000_create_users_table.php +++ b/tests/migrations/2014_10_12_000000_create_users_table.php @@ -16,7 +16,7 @@ public function up() Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); - $table->string('email')->unique(); + $table->string('email'); $table->timestamps(); }); }