diff --git a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php index d7f4ff51fe..650e4885f5 100644 --- a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php +++ b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php @@ -70,10 +70,11 @@ class OtherCurrenciesCorrections extends Command public function __construct() { parent::__construct(); - $this->count = 0; - $this->accountRepos = app(AccountRepositoryInterface::class); - $this->currencyRepos = app(CurrencyRepositoryInterface::class); - $this->journalRepos = app(JournalRepositoryInterface::class); + $this->count = 0; + $this->accountCurrencies = []; + $this->accountRepos = app(AccountRepositoryInterface::class); + $this->currencyRepos = app(CurrencyRepositoryInterface::class); + $this->journalRepos = app(JournalRepositoryInterface::class); } /** @@ -83,9 +84,6 @@ class OtherCurrenciesCorrections extends Command */ public function handle(): int { - $this->accountCurrencies = []; - - $start = microtime(true); // @codeCoverageIgnoreStart if ($this->isExecuted() && true !== $this->option('force')) { @@ -98,12 +96,7 @@ class OtherCurrenciesCorrections extends Command $this->updateOtherJournalsCurrencies(); $this->markAsExecuted(); - if (0 === $this->count) { - $this->line('All transactions are correct.'); - } - if (0 !== $this->count) { - $this->line(sprintf('Verified %d transaction(s) and journal(s).', $this->count)); - } + $this->line(sprintf('Verified %d transaction(s) and journal(s).', $this->count)); $end = round(microtime(true) - $start, 2); $this->info(sprintf('Verified and fixed transaction currencies in %s seconds.', $end)); @@ -119,7 +112,7 @@ class OtherCurrenciesCorrections extends Command { $accountId = $account->id; if (isset($this->accountCurrencies[$accountId]) && 0 === $this->accountCurrencies[$accountId]) { - return null; + return null; // @codeCoverageIgnore } if (isset($this->accountCurrencies[$accountId]) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { return $this->accountCurrencies[$accountId]; @@ -127,9 +120,11 @@ class OtherCurrenciesCorrections extends Command $currencyId = (int)$this->accountRepos->getMetaValue($account, 'currency_id'); $result = $this->currencyRepos->findNull($currencyId); if (null === $result) { + // @codeCoverageIgnoreStart $this->accountCurrencies[$accountId] = 0; return null; + // @codeCoverageIgnoreEnd } $this->accountCurrencies[$accountId] = $result; @@ -138,24 +133,6 @@ class OtherCurrenciesCorrections extends Command } - /** - * @param TransactionJournal $journal - * - * @return Transaction|null - */ - private function getFirstAssetTransaction(TransactionJournal $journal): ?Transaction - { - $result = $journal->transactions->first( - static function (Transaction $transaction) { - // type can also be liability. - return AccountType::ASSET === $transaction->account->accountType->type; - } - ); - - return $result; - } - - /** * @return bool */ @@ -182,22 +159,31 @@ class OtherCurrenciesCorrections extends Command */ private function updateJournalCurrency(TransactionJournal $journal): void { + $this->accountRepos->setUser($journal->user); + $this->journalRepos->setUser($journal->user); + $this->currencyRepos->setUser($journal->user); + $leadTransaction = $this->getLeadTransaction($journal); if (null === $leadTransaction) { + // @codeCoverageIgnoreStart $this->error(sprintf('Could not reliably determine which transaction is in the lead for transaction journal #%d.', $journal->id)); return; + // @codeCoverageIgnoreEnd } /** @var Account $account */ $account = $leadTransaction->account; $currency = $this->getCurrency($account); if (null === $currency) { + // @codeCoverageIgnoreStart $this->error(sprintf('Account #%d ("%s") has no currency preference, so transaction journal #%d can\'t be corrected', $account->id, $account->name, $journal->id)); + $this->count++; return; + // @codeCoverageIgnoreEnd } // fix each transaction: $journal->transactions->each( @@ -205,7 +191,6 @@ class OtherCurrenciesCorrections extends Command if (null === $transaction->transaction_currency_id) { $transaction->transaction_currency_id = $currency->id; $transaction->save(); - //$this->count++; } // when mismatch in transaction: @@ -214,7 +199,6 @@ class OtherCurrenciesCorrections extends Command $transaction->foreign_amount = $transaction->amount; $transaction->transaction_currency_id = $currency->id; $transaction->save(); - //$this->count++; } } ); @@ -231,8 +215,6 @@ class OtherCurrenciesCorrections extends Command * Both source and destination must match the respective currency preference of the related asset account. * So FF3 must verify all transactions. * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ private function updateOtherJournalsCurrencies(): void { diff --git a/app/Console/Commands/Upgrade/TransactionIdentifier.php b/app/Console/Commands/Upgrade/TransactionIdentifier.php index c08651c06b..9e124b9ead 100644 --- a/app/Console/Commands/Upgrade/TransactionIdentifier.php +++ b/app/Console/Commands/Upgrade/TransactionIdentifier.php @@ -21,9 +21,9 @@ namespace FireflyIII\Console\Commands\Upgrade; -use DB; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Console\Command; use Illuminate\Database\QueryException; use Log; @@ -48,6 +48,19 @@ class TransactionIdentifier extends Command */ protected $signature = 'firefly-iii:transaction-identifiers {--F|force : Force the execution of this command.}'; + /** @var JournalRepositoryInterface */ + private $journalRepository; + + /** @var int */ + private $count; + + public function __construct() + { + parent::__construct(); + $this->journalRepository = app(JournalRepositoryInterface::class); + $this->count = 0; + } + /** * This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier * to easier to match to their counterpart. When a journal is split, it has two or three transactions: -3, -4 and -5 for example. @@ -61,7 +74,8 @@ class TransactionIdentifier extends Command */ public function handle(): int { - $start = microtime(true); + $start = microtime(true); + // @codeCoverageIgnoreStart if ($this->isExecuted() && true !== $this->option('force')) { $this->warn('This command has already been executed.'); @@ -72,24 +86,22 @@ class TransactionIdentifier extends Command if (!Schema::hasTable('transaction_journals')) { return 0; } - $subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('transaction_journals.deleted_at') - ->whereNull('transactions.deleted_at') - ->groupBy(['transaction_journals.id']) - ->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]); - /** @noinspection PhpStrictTypeCheckingInspection */ - $result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived')) - ->mergeBindings($subQuery->getQuery()) - ->where('t_count', '>', 2) - ->select(['id', 't_count']); - $journalIds = array_unique($result->pluck('id')->toArray()); - $count= 0; - foreach ($journalIds as $journalId) { - $this->updateJournalIdentifiers((int)$journalId); - $count++; + // @codeCoverageIgnoreEnd + $journals = $this->journalRepository->getSplitJournals(); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->updateJournalIdentifiers($journal); } + + if (0 === $this->count) { + $this->line('All split journal transaction identifiers are correct.'); + } + if (0 !== $this->count) { + $this->line(sprintf('Fixed %d split journal transaction identifier(s).', $this->count)); + } + $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified and fixed %d transaction identifiers in %s seconds.', $count, $end)); + $this->info(sprintf('Verified and fixed transaction identifiers in %s seconds.', $end)); $this->markAsExecuted(); return 0; @@ -117,47 +129,63 @@ class TransactionIdentifier extends Command } /** - * grab all positive transactions from this journal that are not deleted. for each one, grab the negative opposing one + * Grab all positive transactions from this journal that are not deleted. for each one, grab the negative opposing one * which has 0 as an identifier and give it the same identifier. * - * @param int $journalId + * @param TransactionJournal $transactionJournal */ - private function updateJournalIdentifiers(int $journalId): void + private function updateJournalIdentifiers(TransactionJournal $transactionJournal): void { $identifier = 0; - $processed = []; - $transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get(); + $exclude = []; // transactions already processed. + $transactions = $transactionJournal->transactions()->where('amount', '>', 0)->get(); + /** @var Transaction $transaction */ foreach ($transactions as $transaction) { - // find opposing: - $amount = bcmul((string)$transaction->amount, '-1'); - - try { - /** @var Transaction $opposing */ - $opposing = Transaction::where('transaction_journal_id', $journalId) - ->where('amount', $amount)->where('identifier', '=', 0) - ->whereNotIn('id', $processed) - ->first(); - } catch (QueryException $e) { - Log::error($e->getMessage()); - $this->error('Firefly III could not find the "identifier" field in the "transactions" table.'); - $this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version'))); - $this->error('Please run "php artisan migrate" to add this field to the table.'); - $this->info('Then, run "php artisan firefly:upgrade-database" to try again.'); - - return; - } + $opposing = $this->findOpposing($transaction, $exclude); if (null !== $opposing) { // give both a new identifier: $transaction->identifier = $identifier; $opposing->identifier = $identifier; $transaction->save(); $opposing->save(); - $processed[] = $transaction->id; - $processed[] = $opposing->id; + $exclude[] = $transaction->id; + $exclude[] = $opposing->id; + $this->count++; } ++$identifier; } } + + /** + * @param Transaction $transaction + * @param array $exclude + * @return Transaction|null + */ + private function findOpposing(Transaction $transaction, array $exclude): ?Transaction + { + // find opposing: + $amount = bcmul((string)$transaction->amount, '-1'); + + try { + /** @var Transaction $opposing */ + $opposing = Transaction::where('transaction_journal_id', $transaction->transaction_journal_id) + ->where('amount', $amount)->where('identifier', '=', 0) + ->whereNotIn('id', $exclude) + ->first(); + // @codeCoverageIgnoreStart + } catch (QueryException $e) { + Log::error($e->getMessage()); + $this->error('Firefly III could not find the "identifier" field in the "transactions" table.'); + $this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version'))); + $this->error('Please run "php artisan migrate" to add this field to the table.'); + $this->info('Then, run "php artisan firefly:upgrade-database" to try again.'); + + return null; + } + // @codeCoverageIgnoreEnd + + return $opposing; + } } diff --git a/app/Console/Commands/Upgrade/UpgradeDatabase.php b/app/Console/Commands/Upgrade/UpgradeDatabase.php index 05352bba01..2eec509c3b 100644 --- a/app/Console/Commands/Upgrade/UpgradeDatabase.php +++ b/app/Console/Commands/Upgrade/UpgradeDatabase.php @@ -28,6 +28,7 @@ use Illuminate\Console\Command; /** * Class UpgradeDatabase + * @codeCoverageIgnore */ class UpgradeDatabase extends Command { @@ -62,6 +63,7 @@ class UpgradeDatabase extends Command public function handle(): int { $commands = [ + // there are 11 upgrade commands. 'firefly-iii:transaction-identifiers', 'firefly-iii:account-currencies', 'firefly-iii:transfer-currencies', diff --git a/app/Http/Controllers/System/InstallController.php b/app/Http/Controllers/System/InstallController.php index 66ab269804..a5fb706957 100644 --- a/app/Http/Controllers/System/InstallController.php +++ b/app/Http/Controllers/System/InstallController.php @@ -65,12 +65,12 @@ class InstallController extends Controller { // empty on purpose. $this->upgradeCommands = [ - // there are x initial commands + // there are 3 initial commands 'migrate' => ['--seed' => true, '--force' => true], 'firefly-iii:decrypt-all' => [], 'generate-keys' => [], // an exception :( - // there are 10 upgrade commands. + // there are 11 upgrade commands. 'firefly-iii:transaction-identifiers' => [], 'firefly-iii:account-currencies' => [], 'firefly-iii:transfer-currencies' => [], diff --git a/tests/TestCase.php b/tests/TestCase.php index 68af6f98cf..33829c5bbe 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -149,6 +149,18 @@ abstract class TestCase extends BaseTestCase return $this->getRandomAccount(AccountType::EXPENSE, null); } + /** + * @return Account + */ + public function getRandomInitialBalance(): Account + { + return $this->getRandomAccount(AccountType::INITIAL_BALANCE, null); + } + + public function getRandomReconciliation(): Account { + return $this->getRandomAccount(AccountType::RECONCILIATION, null); + } + /** * @return Account */ diff --git a/tests/Unit/Console/Commands/Upgrade/OtherCurrenciesCorrectionsTest.php b/tests/Unit/Console/Commands/Upgrade/OtherCurrenciesCorrectionsTest.php new file mode 100644 index 0000000000..e3ecbdb76d --- /dev/null +++ b/tests/Unit/Console/Commands/Upgrade/OtherCurrenciesCorrectionsTest.php @@ -0,0 +1,263 @@ +. + */ + +namespace Tests\Unit\Console\Commands\Upgrade; + + +use FireflyConfig; +use FireflyIII\Models\Configuration; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use Illuminate\Support\Collection; +use Log; +use Mockery; +use Tests\TestCase; + +/** + * Class OtherCurrenciesCorrectionsTest + */ +class OtherCurrenciesCorrectionsTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::info(sprintf('Now in %s.', get_class($this))); + } + + /** + * Basic test. Assume nothing is wrong. Submit a withdrawal and a deposit. + * + * @covers \FireflyIII\Console\Commands\Upgrade\OtherCurrenciesCorrections + */ + public function testHandle(): void + { + // mock classes: + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $withdrawal = $this->getRandomWithdrawal(); + $deposit = $this->getRandomDeposit(); + $euro = $this->getEuro(); + + // collect all journals: + $journalRepos->shouldReceive('setUser')->atLeast()->once(); + $journalRepos->shouldReceive('getAllJournals') + ->atLeast()->once() + ->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]]) + ->andReturn(new Collection([$withdrawal, $deposit])); + + // account repos + $accountRepos->shouldReceive('setUser')->atLeast()->once(); + $accountRepos->shouldReceive('getMetaValue')->atLeast()->once() + ->withArgs([Mockery::any(), 'currency_id'])->andReturn('1'); + + // collect currency + $currencyRepos->shouldReceive('setUser')->atLeast()->once(); + $currencyRepos->shouldReceive('findNull')->atLeast()->once() + ->withArgs([1])->andReturn($euro); + + // configuration + $false = new Configuration; + $false->data = false; + FireflyConfig::shouldReceive('get')->withArgs(['4780_other_currencies', false])->andReturn($false); + FireflyConfig::shouldReceive('set')->withArgs(['4780_other_currencies', true]); + + // assume all is well. + $this->artisan('firefly-iii:other-currencies') + ->expectsOutput('Verified 2 transaction(s) and journal(s).') + ->assertExitCode(0); + } + + /** + * Basic test. Assume nothing is wrong. Submit an opening balance. + * Also fixes transaction currency ID. + * + * @covers \FireflyIII\Console\Commands\Upgrade\OtherCurrenciesCorrections + */ + public function testHandleOB(): void + { + $type = TransactionType::where('type', TransactionType::OPENING_BALANCE)->first(); + $asset = $this->getRandomAsset(); + $initial = $this->getRandomInitialBalance(); + $journal = TransactionJournal::create( + [ + 'user_id' => 1, + 'transaction_currency_id' => 1, + 'transaction_type_id' => $type->id, + 'description' => 'Test', + 'tag_count' => 0, + 'date' => '2019-01-01', + ] + ); + $one = Transaction::create( + [ + 'transaction_journal_id' => $journal->id, + 'account_id' => $asset->id, + 'amount' => '-10', + 'identifier' => 1, + ] + ); + $two = Transaction::create( + [ + 'transaction_journal_id' => $journal->id, + 'account_id' => $initial->id, + 'amount' => '10', + 'identifier' => 1, + ] + ); + + + // mock classes: + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $euro = $this->getEuro(); + + // collect all journals: + $journalRepos->shouldReceive('setUser')->atLeast()->once(); + $journalRepos->shouldReceive('getAllJournals') + ->atLeast()->once() + ->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]]) + ->andReturn(new Collection([$journal])); + + // account repos + $accountRepos->shouldReceive('setUser')->atLeast()->once(); + $accountRepos->shouldReceive('getMetaValue')->atLeast()->once() + ->withArgs([Mockery::any(), 'currency_id'])->andReturn('1'); + + // collect currency + $currencyRepos->shouldReceive('setUser')->atLeast()->once(); + $currencyRepos->shouldReceive('findNull')->atLeast()->once() + ->withArgs([1])->andReturn($euro); + + // configuration + $false = new Configuration; + $false->data = false; + FireflyConfig::shouldReceive('get')->withArgs(['4780_other_currencies', false])->andReturn($false); + FireflyConfig::shouldReceive('set')->withArgs(['4780_other_currencies', true]); + + $this->artisan('firefly-iii:other-currencies') + ->expectsOutput('Verified 1 transaction(s) and journal(s).') + ->assertExitCode(0); + + // assume currency has been fixed for both transactions: + $this->assertCount(1, Transaction::where('id', $one->id)->where('transaction_currency_id', $euro->id)->get()); + $this->assertCount(1, Transaction::where('id', $two->id)->where('transaction_currency_id', $euro->id)->get()); + + $one->forceDelete(); + $two->forceDelete(); + $journal->forceDelete(); + } + + /** + * Basic test. Assume nothing is wrong. Submit an opening balance. + * Also fixes bad transaction currency ID. + * + * @covers \FireflyIII\Console\Commands\Upgrade\OtherCurrenciesCorrections + */ + public function testHandleReconciliation(): void + { + $type = TransactionType::where('type', TransactionType::RECONCILIATION)->first(); + $asset = $this->getRandomAsset(); + $reconciliation = $this->getRandomReconciliation(); + $journal = TransactionJournal::create( + [ + 'user_id' => 1, + 'transaction_currency_id' => 1, + 'transaction_type_id' => $type->id, + 'description' => 'Test', + 'tag_count' => 0, + 'date' => '2019-01-01', + ] + ); + $one = Transaction::create( + [ + 'transaction_journal_id' => $journal->id, + 'account_id' => $asset->id, + 'amount' => '-10', + 'identifier' => 1, + 'transaction_currency_id' => 2, + ] + ); + $two = Transaction::create( + [ + 'transaction_journal_id' => $journal->id, + 'account_id' => $reconciliation->id, + 'amount' => '10', + 'identifier' => 1, + 'transaction_currency_id' => 2, + ] + ); + + + // mock classes: + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $euro = $this->getEuro(); + + // collect all journals: + $journalRepos->shouldReceive('setUser')->atLeast()->once(); + $journalRepos->shouldReceive('getAllJournals') + ->atLeast()->once() + ->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]]) + ->andReturn(new Collection([$journal])); + + // account repos + $accountRepos->shouldReceive('setUser')->atLeast()->once(); + $accountRepos->shouldReceive('getMetaValue')->atLeast()->once() + ->withArgs([Mockery::any(), 'currency_id'])->andReturn('1'); + + // collect currency + $currencyRepos->shouldReceive('setUser')->atLeast()->once(); + $currencyRepos->shouldReceive('findNull')->atLeast()->once() + ->withArgs([1])->andReturn($euro); + + // configuration + $false = new Configuration; + $false->data = false; + FireflyConfig::shouldReceive('get')->withArgs(['4780_other_currencies', false])->andReturn($false); + FireflyConfig::shouldReceive('set')->withArgs(['4780_other_currencies', true]); + + $this->artisan('firefly-iii:other-currencies') + ->expectsOutput('Verified 1 transaction(s) and journal(s).') + ->assertExitCode(0); + + // assume currency has been fixed for both transactions: + $this->assertCount(1, Transaction::where('id', $one->id)->where('transaction_currency_id', $euro->id)->get()); + $this->assertCount(1, Transaction::where('id', $two->id)->where('transaction_currency_id', $euro->id)->get()); + $this->assertCount(1, Transaction::where('id', $one->id)->where('foreign_currency_id', 2)->get()); + $this->assertCount(1, Transaction::where('id', $two->id)->where('foreign_currency_id', 2)->get()); + + $one->forceDelete(); + $two->forceDelete(); + $journal->forceDelete(); + } + + +} \ No newline at end of file diff --git a/tests/Unit/Console/Commands/Upgrade/TransactionIdentifierTest.php b/tests/Unit/Console/Commands/Upgrade/TransactionIdentifierTest.php new file mode 100644 index 0000000000..b1c8ef1daf --- /dev/null +++ b/tests/Unit/Console/Commands/Upgrade/TransactionIdentifierTest.php @@ -0,0 +1,152 @@ +. + */ + +namespace Tests\Unit\Console\Commands\Upgrade; + + +use FireflyConfig; +use FireflyIII\Models\Configuration; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use Illuminate\Support\Collection; +use Log; +use Tests\TestCase; + +/** + * Class TransactionIdentifierTest + */ +class TransactionIdentifierTest extends TestCase +{ + /** + * + */ + public function setUp(): void + { + parent::setUp(); + Log::info(sprintf('Now in %s.', get_class($this))); + } + + /** + * Basic test. Assume nothing is wrong. + * + * @covers \FireflyIII\Console\Commands\Upgrade\TransactionIdentifier + */ + public function testHandle(): void + { + // mock classes: + $journalRepos = $this->mock(JournalRepositoryInterface::class); + + // commands: + $journalRepos->shouldReceive('getSplitJournals')->andReturn(new Collection) + ->atLeast()->once(); + + // configuration + $false = new Configuration; + $false->data = false; + FireflyConfig::shouldReceive('get')->withArgs(['4780_transaction_identifier', false])->andReturn($false); + FireflyConfig::shouldReceive('set')->withArgs(['4780_transaction_identifier', true]); + + // assume all is well. + $this->artisan('firefly-iii:transaction-identifiers') + ->expectsOutput('All split journal transaction identifiers are correct.') + ->assertExitCode(0); + + } + + /** + * Basic test. Assume nothing is wrong. + * + * @covers \FireflyIII\Console\Commands\Upgrade\TransactionIdentifier + */ + public function testHandleSplit(): void + { + // create a split journal: + $asset = $this->getRandomAsset(); + $expense = $this->getRandomExpense(); + $journal = TransactionJournal::create( + [ + 'user_id' => 1, + 'transaction_currency_id' => 1, + 'transaction_type_id' => 1, + 'description' => 'Test', + 'tag_count' => 0, + 'date' => '2019-01-01', + ] + ); + $one = Transaction::create( + [ + 'transaction_journal_id' => $journal->id, + 'account_id' => $asset->id, + 'amount' => '-10', + 'identifier' => 0, + ] + ); + $two = Transaction::create( + [ + 'transaction_journal_id' => $journal->id, + 'account_id' => $expense->id, + 'amount' => '10', + 'identifier' => 0, + ] + ); + $three = Transaction::create( + [ + 'transaction_journal_id' => $journal->id, + 'account_id' => $asset->id, + 'amount' => '-12', + 'identifier' => 0, + ] + ); + $four = Transaction::create( + [ + 'transaction_journal_id' => $journal->id, + 'account_id' => $expense->id, + 'amount' => '12', + 'identifier' => 0, + ] + ); + + // mock classes: + $journalRepos = $this->mock(JournalRepositoryInterface::class); + + // commands: + $journalRepos->shouldReceive('getSplitJournals')->andReturn(new Collection([$journal])) + ->atLeast()->once(); + + // configuration + $false = new Configuration; + $false->data = false; + FireflyConfig::shouldReceive('get')->withArgs(['4780_transaction_identifier', false])->andReturn($false); + FireflyConfig::shouldReceive('set')->withArgs(['4780_transaction_identifier', true]); + + // assume all is well. + $this->artisan('firefly-iii:transaction-identifiers') + ->expectsOutput('Fixed 2 split journal transaction identifier(s).') + ->assertExitCode(0); + + // see results: + $this->assertCount(1, Transaction::where('id', $one->id)->where('identifier', 0)->get()); + $this->assertCount(1, Transaction::where('id', $three->id)->where('identifier', 1)->get()); + + } + +} \ No newline at end of file