diff --git a/.deploy/docker/entrypoint.sh b/.deploy/docker/entrypoint.sh
index 656eaf6310..807f580500 100755
--- a/.deploy/docker/entrypoint.sh
+++ b/.deploy/docker/entrypoint.sh
@@ -59,7 +59,7 @@ fi
#env $(grep -v "^\#" .env | xargs)
php artisan cache:clear
php artisan migrate --seed
-php artisan firefly:decrypt-all
+php artisan firefly-iii:decrypt-all
# upgrade database commands:
php artisan firefly-iii:transaction-identifiers
diff --git a/app/Api/V1/Controllers/Chart/AccountController.php b/app/Api/V1/Controllers/Chart/AccountController.php
index bd0fa96832..964e21c3ca 100644
--- a/app/Api/V1/Controllers/Chart/AccountController.php
+++ b/app/Api/V1/Controllers/Chart/AccountController.php
@@ -27,7 +27,6 @@ namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRequest;
-use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
@@ -74,7 +73,6 @@ class AccountController extends Controller
* @param DateRequest $request
*
* @return JsonResponse
- * @throws FireflyException
*/
public function expenseOverview(DateRequest $request): JsonResponse
{
@@ -161,7 +159,6 @@ class AccountController extends Controller
* @param DateRequest $request
*
* @return JsonResponse
- * @throws FireflyException
*/
public function overview(DateRequest $request): JsonResponse
{
@@ -224,7 +221,6 @@ class AccountController extends Controller
* @param DateRequest $request
*
* @return JsonResponse
- * @throws FireflyException
*/
public function revenueOverview(DateRequest $request): JsonResponse
{
diff --git a/app/Api/V1/Controllers/Chart/CategoryController.php b/app/Api/V1/Controllers/Chart/CategoryController.php
index 2a1366306c..fd1d4d08a1 100644
--- a/app/Api/V1/Controllers/Chart/CategoryController.php
+++ b/app/Api/V1/Controllers/Chart/CategoryController.php
@@ -27,11 +27,9 @@ namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRequest;
-use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
use Illuminate\Support\Collection;
/**
@@ -66,7 +64,6 @@ class CategoryController extends Controller
* @param DateRequest $request
*
* @return JsonResponse
- * @throws FireflyException
*/
public function overview(DateRequest $request): JsonResponse
{
diff --git a/app/Api/V1/Controllers/CurrencyController.php b/app/Api/V1/Controllers/CurrencyController.php
index 845d9efe8a..04e96231cb 100644
--- a/app/Api/V1/Controllers/CurrencyController.php
+++ b/app/Api/V1/Controllers/CurrencyController.php
@@ -128,7 +128,7 @@ class CurrencyController extends Controller
// filter list on currency preference:
$collection = $unfiltered->filter(
- function (Account $account) use ($currency, $accountRepository) {
+ static function (Account $account) use ($currency, $accountRepository) {
$currencyId = (int)$accountRepository->getMetaValue($account, 'currency_id');
return $currencyId === $currency->id;
@@ -229,7 +229,7 @@ class CurrencyController extends Controller
// filter and paginate list:
$collection = $unfiltered->filter(
- function (Bill $bill) use ($currency) {
+ static function (Bill $bill) use ($currency) {
return $bill->transaction_currency_id === $currency->id;
}
);
@@ -505,7 +505,7 @@ class CurrencyController extends Controller
// filter selection
$collection = $unfiltered->filter(
- function (Recurrence $recurrence) use ($currency) {
+ static function (Recurrence $recurrence) use ($currency) {
/** @var RecurrenceTransaction $transaction */
foreach ($recurrence->recurrenceTransactions as $transaction) {
if ($transaction->transaction_currency_id === $currency->id || $transaction->foreign_currency_id === $currency->id) {
@@ -560,7 +560,7 @@ class CurrencyController extends Controller
$unfiltered = $repository->getAll();
$collection = $unfiltered->filter(
- function (Rule $rule) use ($currency) {
+ static function (Rule $rule) use ($currency) {
/** @var RuleTrigger $trigger */
foreach ($rule->ruleTriggers as $trigger) {
if ('currency_is' === $trigger->trigger_type && $currency->name === $trigger->trigger_value) {
diff --git a/app/Api/V1/Controllers/PreferenceController.php b/app/Api/V1/Controllers/PreferenceController.php
index ab01633602..7811875b1a 100644
--- a/app/Api/V1/Controllers/PreferenceController.php
+++ b/app/Api/V1/Controllers/PreferenceController.php
@@ -52,7 +52,7 @@ class PreferenceController extends Controller
{
parent::__construct();
$this->middleware(
- function ($request, $next) {
+ static function ($request, $next) {
/** @var User $user */
$user = auth()->user();
$repository = app(AccountRepositoryInterface::class);
diff --git a/app/Api/V1/Controllers/RuleGroupController.php b/app/Api/V1/Controllers/RuleGroupController.php
index 797d17b84b..2f5e22000a 100644
--- a/app/Api/V1/Controllers/RuleGroupController.php
+++ b/app/Api/V1/Controllers/RuleGroupController.php
@@ -258,7 +258,8 @@ class RuleGroupController extends Controller
$matcher->setTriggeredLimit($parameters['trigger_limit']);
$matcher->setAccounts($parameters['accounts']);
- $result = $matcher->findTransactionsByRule();
+ $result = $matcher->findTransactionsByRule();
+ /** @noinspection AdditionOperationOnArraysInspection */
$matchingTransactions = $result + $matchingTransactions;
}
diff --git a/app/Api/V1/Controllers/SummaryController.php b/app/Api/V1/Controllers/SummaryController.php
index fd848cbef5..a3fea2543e 100644
--- a/app/Api/V1/Controllers/SummaryController.php
+++ b/app/Api/V1/Controllers/SummaryController.php
@@ -28,7 +28,6 @@ namespace FireflyIII\Api\V1\Controllers;
use Carbon\Carbon;
use Exception;
use FireflyIII\Api\V1\Requests\DateRequest;
-use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Models\Account;
@@ -41,7 +40,6 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Request;
use Illuminate\Support\Collection;
/**
@@ -86,10 +84,9 @@ class SummaryController extends Controller
}
/**
- * @param Request $request
+ * @param DateRequest $request
*
* @return JsonResponse
- * @throws FireflyException
* @throws Exception
*/
public function basic(DateRequest $request): JsonResponse
diff --git a/app/Api/V1/Controllers/TagController.php b/app/Api/V1/Controllers/TagController.php
index 2182fa90c9..3246337835 100644
--- a/app/Api/V1/Controllers/TagController.php
+++ b/app/Api/V1/Controllers/TagController.php
@@ -26,7 +26,6 @@ namespace FireflyIII\Api\V1\Controllers;
use Carbon\Carbon;
use FireflyIII\Api\V1\Requests\DateRequest;
use FireflyIII\Api\V1\Requests\TagRequest;
-use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
@@ -79,7 +78,6 @@ class TagController extends Controller
* @param DateRequest $request
*
* @return JsonResponse
- * @throws FireflyException
*/
public function cloud(DateRequest $request): JsonResponse
{
diff --git a/app/Api/V1/Controllers/TransactionLinkController.php b/app/Api/V1/Controllers/TransactionLinkController.php
index e0d5cf38ed..65dfe22a42 100644
--- a/app/Api/V1/Controllers/TransactionLinkController.php
+++ b/app/Api/V1/Controllers/TransactionLinkController.php
@@ -105,7 +105,7 @@ class TransactionLinkController extends Controller
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
// read type from URI
- $name = $request->get('name') ?? null;
+ $name = $request->get('name');
// types to get, page size:
$pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
diff --git a/app/Api/V1/Requests/AccountStoreRequest.php b/app/Api/V1/Requests/AccountStoreRequest.php
index a21b510554..ae7b739142 100644
--- a/app/Api/V1/Requests/AccountStoreRequest.php
+++ b/app/Api/V1/Requests/AccountStoreRequest.php
@@ -56,7 +56,7 @@ class AccountStoreRequest extends Request
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
$rules = [
'name' => 'required|min:1|uniqueAccountForUser',
- 'type' => sprintf('in:%s', $types),
+ 'type' => 'required|' . sprintf('in:%s', $types),
'iban' => 'iban|nullable',
'bic' => 'bic|nullable',
'account_number' => 'between:1,255|nullable|uniqueAccountNumberForUser',
diff --git a/app/Api/V1/Requests/BillRequest.php b/app/Api/V1/Requests/BillRequest.php
index 2e9e84274d..a8c86c00f3 100644
--- a/app/Api/V1/Requests/BillRequest.php
+++ b/app/Api/V1/Requests/BillRequest.php
@@ -119,7 +119,7 @@ class BillRequest extends Request
public function withValidator(Validator $validator): void
{
$validator->after(
- function (Validator $validator) {
+ static function (Validator $validator) {
$data = $validator->getData();
$min = (float)($data['amount_min'] ?? 0);
$max = (float)($data['amount_max'] ?? 0);
diff --git a/app/Api/V1/Requests/RuleGroupTestRequest.php b/app/Api/V1/Requests/RuleGroupTestRequest.php
index 22d0492b8b..1b3e9a4c75 100644
--- a/app/Api/V1/Requests/RuleGroupTestRequest.php
+++ b/app/Api/V1/Requests/RuleGroupTestRequest.php
@@ -123,6 +123,7 @@ class RuleGroupTestRequest extends Request
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int)$accountId);
if ($this->validAccount($account)) {
+ /** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account);
}
diff --git a/app/Api/V1/Requests/RuleTestRequest.php b/app/Api/V1/Requests/RuleTestRequest.php
index 85c4d83b42..44ab7c8114 100644
--- a/app/Api/V1/Requests/RuleTestRequest.php
+++ b/app/Api/V1/Requests/RuleTestRequest.php
@@ -123,6 +123,7 @@ class RuleTestRequest extends Request
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int)$accountId);
if ($this->validAccount($account)) {
+ /** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account);
}
diff --git a/app/Api/V1/Requests/RuleTriggerRequest.php b/app/Api/V1/Requests/RuleTriggerRequest.php
index bff9a3a7dd..570232751d 100644
--- a/app/Api/V1/Requests/RuleTriggerRequest.php
+++ b/app/Api/V1/Requests/RuleTriggerRequest.php
@@ -98,6 +98,7 @@ class RuleTriggerRequest extends Request
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int)$accountId);
if ($this->validAccount($account)) {
+ /** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account);
}
diff --git a/app/Console/Commands/Correction/CorrectDatabase.php b/app/Console/Commands/Correction/CorrectDatabase.php
index 212fa9bb6e..6bcb3dc278 100644
--- a/app/Console/Commands/Correction/CorrectDatabase.php
+++ b/app/Console/Commands/Correction/CorrectDatabase.php
@@ -30,6 +30,7 @@ use Schema;
/**
* Class CorrectDatabase
+ * @codeCoverageIgnore
*/
class CorrectDatabase extends Command
{
@@ -38,7 +39,7 @@ class CorrectDatabase extends Command
*
* @var string
*/
- protected $description = 'Will correct the integrity of your database, of necessary.';
+ protected $description = 'Will correct the integrity of your database, if necessary.';
/**
* The name and signature of the console command.
*
diff --git a/app/Console/Commands/Correction/CreateAccessTokens.php b/app/Console/Commands/Correction/CreateAccessTokens.php
index f14632553f..9d2ce023dd 100644
--- a/app/Console/Commands/Correction/CreateAccessTokens.php
+++ b/app/Console/Commands/Correction/CreateAccessTokens.php
@@ -22,6 +22,7 @@
namespace FireflyIII\Console\Commands\Correction;
use Exception;
+use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Console\Command;
@@ -35,7 +36,7 @@ class CreateAccessTokens extends Command
*
* @var string
*/
- protected $description = 'Creates user access tokens.';
+ protected $description = 'Creates user access tokens which are used for command line access to personal data.';
/**
* The name and signature of the console command.
*
@@ -51,9 +52,13 @@ class CreateAccessTokens extends Command
*/
public function handle(): int
{
+ // make repository:
+ /** @var UserRepositoryInterface $repository */
+ $repository = app(UserRepositoryInterface::class);
+
$start = microtime(true);
$count = 0;
- $users = User::get();
+ $users= $repository->all();
/** @var User $user */
foreach ($users as $user) {
$pref = app('preferences')->getForUser($user, 'access_token', null);
diff --git a/app/Console/Commands/Correction/CreateLinkTypes.php b/app/Console/Commands/Correction/CreateLinkTypes.php
index 6cc158af3e..dfb9ade9b6 100644
--- a/app/Console/Commands/Correction/CreateLinkTypes.php
+++ b/app/Console/Commands/Correction/CreateLinkTypes.php
@@ -58,12 +58,13 @@ class CreateLinkTypes extends Command
'Reimbursement' => ['(partially) reimburses', 'is (partially) reimbursed by'],
];
foreach ($set as $name => $values) {
- $link = LinkType::where('name', $name)->where('outward', $values[0])->where('inward', $values[1])->first();
+ $link = LinkType::where('name', $name)
+ ->first();
if (null === $link) {
$link = new LinkType;
$link->name = $name;
- $link->outward = $values[0];
$link->inward = $values[1];
+ $link->outward = $values[0];
++$count;
$this->line(sprintf('Created missing link type "%s"', $name));
}
diff --git a/app/Console/Commands/Correction/DeleteEmptyGroups.php b/app/Console/Commands/Correction/DeleteEmptyGroups.php
index 98887142e9..4db1945866 100644
--- a/app/Console/Commands/Correction/DeleteEmptyGroups.php
+++ b/app/Console/Commands/Correction/DeleteEmptyGroups.php
@@ -59,7 +59,7 @@ class DeleteEmptyGroups extends Command
$this->info('No empty transaction groups.');
}
if ($count > 0) {
- $this->info(sprintf('Deleted %d empty transaction groups.', $count));
+ $this->info(sprintf('Deleted %d empty transaction group(s).', $count));
TransactionGroup::whereNull('deleted_at')->whereNotIn('id', $groups)->delete();
}
$end = round(microtime(true) - $start, 2);
diff --git a/app/Console/Commands/Correction/DeleteEmptyJournals.php b/app/Console/Commands/Correction/DeleteEmptyJournals.php
index dad50592ef..004c1f91ea 100644
--- a/app/Console/Commands/Correction/DeleteEmptyJournals.php
+++ b/app/Console/Commands/Correction/DeleteEmptyJournals.php
@@ -72,9 +72,12 @@ class DeleteEmptyJournals extends Command
foreach ($set as $entry) {
try {
TransactionJournal::find($entry->id)->delete();
+ // @codeCoverageIgnoreStart
} catch (Exception $e) {
Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
}
+ // @codeCoverageIgnoreEnd
+
$this->info(sprintf('Deleted empty transaction journal #%d', $entry->id));
++$count;
}
@@ -90,15 +93,6 @@ class DeleteEmptyJournals extends Command
*/
private function deleteUnevenJournals(): void
{
- /**
- * select count(transactions.transaction_journal_id) as the_count, transactions.transaction_journal_id from transactions
- *
- * where transactions.deleted_at is null
- *
- * group by transactions.transaction_journal_id
- * having the_count in ()
- */
-
$set = Transaction
::whereNull('deleted_at')
->having('the_count', '!=', '2')
@@ -111,9 +105,12 @@ class DeleteEmptyJournals extends Command
// uneven number, delete journal and transactions:
try {
TransactionJournal::find((int)$row->transaction_journal_id)->delete();
+ // @codeCoverageIgnoreStart
} catch(Exception $e) {
Log::info(sprintf('Could not delete journal: %s', $e->getMessage()));
}
+ // @codeCoverageIgnoreEnd
+
Transaction::where('transaction_journal_id', (int)$row->transaction_journal_id)->delete();
$this->info(sprintf('Deleted transaction journal #%d because it had an uneven number of transactions.', $row->transaction_journal_id));
$total++;
diff --git a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php
index 1cd267b9c8..7b433ba3f4 100644
--- a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php
+++ b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php
@@ -25,8 +25,8 @@ use Exception;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
-use stdClass;
use Log;
+use stdClass;
/**
* Deletes transactions where the journal has been deleted.
@@ -81,17 +81,20 @@ class DeleteOrphanedTransactions extends Command
if ($journal) {
try {
$journal->delete();
+ // @codeCoverageIgnoreStart
} catch (Exception $e) {
- Log::info(sprintf('Could not delete transaction %s', $e->getMessage()));
+ Log::info(sprintf('Could not delete journal %s', $e->getMessage()));
}
+ // @codeCoverageIgnoreEnd
}
Transaction::where('transaction_journal_id', (int)$transaction->transaction_journal_id)->delete();
$this->line(
- sprintf('Deleted transaction journal #%d because account #%d was already deleted.', $transaction->transaction_journal_id, $transaction->account_id)
+ sprintf('Deleted transaction journal #%d because account #%d was already deleted.',
+ $transaction->transaction_journal_id, $transaction->account_id)
);
$count++;
}
- if(0===$count) {
+ if (0 === $count) {
$this->info('No orphaned accounts.');
}
}
diff --git a/app/Console/Commands/Correction/DeleteZeroAmount.php b/app/Console/Commands/Correction/DeleteZeroAmount.php
index 6c062f74de..e55b413f1d 100644
--- a/app/Console/Commands/Correction/DeleteZeroAmount.php
+++ b/app/Console/Commands/Correction/DeleteZeroAmount.php
@@ -47,7 +47,6 @@ class DeleteZeroAmount extends Command
/**
* Execute the console command.
- * @throws Exception
* @return int
*/
public function handle(): int
@@ -60,7 +59,13 @@ class DeleteZeroAmount extends Command
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$this->info(sprintf('Deleted transaction journal #%d because the amount is zero (0.00).', $journal->id));
- $journal->delete();
+ try {
+ $journal->delete();
+ // @codeCoverageIgnoreStart
+ } catch (Exception $e) {
+ $this->line($e->getMessage());
+ }
+ // @codeCoverageIgnoreEnd
Transaction::where('transaction_journal_id', $journal->id)->delete();
}
if (0 === $journals->count()) {
diff --git a/app/Console/Commands/Correction/EnableCurrencies.php b/app/Console/Commands/Correction/EnableCurrencies.php
index e4f7bfd395..acfb43f05a 100644
--- a/app/Console/Commands/Correction/EnableCurrencies.php
+++ b/app/Console/Commands/Correction/EnableCurrencies.php
@@ -86,9 +86,13 @@ class EnableCurrencies extends Command
$found = array_unique($found);
$this->info(sprintf('%d different currencies are currently in use.', count($found)));
+
$disabled = TransactionCurrency::whereIn('id', $found)->where('enabled', false)->count();
if ($disabled > 0) {
- $this->info(sprintf('%d were still disabled. This has been corrected.', $disabled));
+ $this->info(sprintf('%d were (was) still disabled. This has been corrected.', $disabled));
+ }
+ if (0 === $disabled) {
+ $this->info('All currencies are correctly enabled or disabled.');
}
TransactionCurrency::whereIn('id', $found)->update(['enabled' => true]);
diff --git a/app/Console/Commands/Correction/FixAccountTypes.php b/app/Console/Commands/Correction/FixAccountTypes.php
index c776e74d8c..ed936a1900 100644
--- a/app/Console/Commands/Correction/FixAccountTypes.php
+++ b/app/Console/Commands/Correction/FixAccountTypes.php
@@ -52,82 +52,16 @@ class FixAccountTypes extends Command
private $factory;
/** @var array */
private $fixable;
+ /** @var int */
+ private $count;
/**
- * @param TransactionJournal $journal
- * @param string $type
- * @param Transaction $source
- * @param Transaction $dest
- * @throws FireflyException
+ * FixAccountTypes constructor.
*/
- public function fixJournal(TransactionJournal $journal, string $type, Transaction $source, Transaction $dest): void
+ public function __construct()
{
- // variables:
- $combination = sprintf('%s%s%s', $type, $source->account->accountType->type, $dest->account->accountType->type);
-
- switch ($combination) {
- case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN):
- case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT):
- case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE):
- // from an asset to a liability should be a withdrawal:
- $withdrawal = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();
- $journal->transactionType()->associate($withdrawal);
- $journal->save();
- $this->info(sprintf('Converted transaction #%d from a transfer to a withdrawal', $journal->id));
- // check it again:
- $this->inspectJournal($journal);
- break;
- case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET):
- case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET):
- case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET):
- // from a liability to an asset should be a deposit.
- $deposit = TransactionType::whereType(TransactionType::DEPOSIT)->first();
- $journal->transactionType()->associate($deposit);
- $journal->save();
- $this->info(sprintf('Converted transaction #%d from a transfer to a deposit', $journal->id));
- // check it again:
- $this->inspectJournal($journal);
-
- break;
- case sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE):
- // withdrawals with a revenue account as destination instead of an expense account.
- $this->factory->setUser($journal->user);
- $result = $this->factory->findOrCreate($source->account->name, AccountType::EXPENSE);
- $dest->account()->associate($result);
- $dest->save();
- $this->info(
- sprintf(
- 'Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").', $journal->id,
- $source->account->id, $source->account->name,
- $result->id, $result->name
- )
- );
- $this->inspectJournal($journal);
- break;
- case sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET):
- // deposits with an expense account as source instead of a revenue account.
- // find revenue account.
- $this->factory->setUser($journal->user);
- $result = $this->factory->findOrCreate($source->account->name, AccountType::REVENUE);
- $source->account()->associate($result);
- $source->save();
- $this->info(
- sprintf(
- 'Transaction journal #%d, source account changed from #%d ("%s") to #%d ("%s").', $journal->id,
- $source->account->id, $source->account->name,
- $result->id, $result->name
- )
- );
- $this->inspectJournal($journal);
- break;
- break;
- default:
- $this->info(sprintf('The source account of %s #%d cannot be of type "%s".', $type, $journal->id, $source->account->accountType->type));
- $this->info(sprintf('The destination account of %s #%d cannot be of type "%s".', $type, $journal->id, $dest->account->accountType->type));
- break;
-
- }
-
+ parent::__construct();
+ $this->count = 0;
}
/**
@@ -159,19 +93,103 @@ class FixAccountTypes extends Command
$this->expected = config('firefly.source_dests');
- $journals = TransactionJournal
-
- ::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get();
+ $journals = TransactionJournal::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get();
foreach ($journals as $journal) {
$this->inspectJournal($journal);
}
+ if (0 === $this->count) {
+ $this->info('All account types are OK!');
+ }
+ if (0 !== $this->count) {
+ $this->info(sprintf('Acted on %d transaction(s)!', $this->count));
+ }
$end = round(microtime(true) - $start, 2);
- $this->info(sprintf('Verified account types in %s seconds', $end));
+ $this->info(sprintf('Verifying account types took %s seconds', $end));
return 0;
}
+ /**
+ * @param TransactionJournal $journal
+ * @param string $type
+ * @param Transaction $source
+ * @param Transaction $dest
+ * @throws FireflyException
+ */
+ private function fixJournal(TransactionJournal $journal, string $type, Transaction $source, Transaction $dest): void
+ {
+ $this->count++;
+ // variables:
+ $combination = sprintf('%s%s%s', $type, $source->account->accountType->type, $dest->account->accountType->type);
+
+ switch ($combination) {
+ case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN):
+ case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT):
+ case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE):
+ // from an asset to a liability should be a withdrawal:
+ $withdrawal = TransactionType::whereType(TransactionType::WITHDRAWAL)->first();
+ $journal->transactionType()->associate($withdrawal);
+ $journal->save();
+ $this->info(sprintf('Converted transaction #%d from a transfer to a withdrawal.', $journal->id));
+ // check it again:
+ $this->inspectJournal($journal);
+ break;
+ case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET):
+ case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET):
+ case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET):
+ // from a liability to an asset should be a deposit.
+ $deposit = TransactionType::whereType(TransactionType::DEPOSIT)->first();
+ $journal->transactionType()->associate($deposit);
+ $journal->save();
+ $this->info(sprintf('Converted transaction #%d from a transfer to a deposit.', $journal->id));
+ // check it again:
+ $this->inspectJournal($journal);
+
+ break;
+ case sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE):
+ // withdrawals with a revenue account as destination instead of an expense account.
+ $this->factory->setUser($journal->user);
+ $oldDest = $dest->account;
+ $result = $this->factory->findOrCreate($dest->account->name, AccountType::EXPENSE);
+ $dest->account()->associate($result);
+ $dest->save();
+ $this->info(
+ sprintf(
+ 'Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").', $journal->id,
+ $oldDest->id, $oldDest->name,
+ $result->id, $result->name
+ )
+ );
+ $this->inspectJournal($journal);
+ break;
+ case sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET):
+ // deposits with an expense account as source instead of a revenue account.
+ // find revenue account.
+ $this->factory->setUser($journal->user);
+ $result = $this->factory->findOrCreate($source->account->name, AccountType::REVENUE);
+ $oldSource = $dest->account;
+ $source->account()->associate($result);
+ $source->save();
+ $this->info(
+ sprintf(
+ 'Transaction journal #%d, source account changed from #%d ("%s") to #%d ("%s").', $journal->id,
+ $oldSource->id, $oldSource->name,
+ $result->id, $result->name
+ )
+ );
+ $this->inspectJournal($journal);
+ break;
+ default:
+ $this->info(sprintf('The source account of %s #%d cannot be of type "%s".', $type, $journal->id, $source->account->accountType->type));
+ $this->info(sprintf('The destination account of %s #%d cannot be of type "%s".', $type, $journal->id, $dest->account->accountType->type));
+
+ break;
+
+ }
+
+ }
+
/**
* @param TransactionJournal $journal
*
@@ -201,7 +219,7 @@ class FixAccountTypes extends Command
{
$count = $journal->transactions()->count();
if (2 !== $count) {
- $this->info(sprintf('Cannot inspect transaction journal #%d because it has %d transactions instead of 2.', $journal->id, $count));
+ $this->info(sprintf('Cannot inspect transaction journal #%d because it has %d transaction(s) instead of 2.', $journal->id, $count));
return;
}
@@ -213,9 +231,11 @@ class FixAccountTypes extends Command
$destAccount = $destTransaction->account;
$destAccountType = $destAccount->accountType->type;
if (!isset($this->expected[$type])) {
+ // @codeCoverageIgnoreStart
$this->info(sprintf('No source/destination info for transaction type %s.', $type));
return;
+ // @codeCoverageIgnoreEnd
}
if (!isset($this->expected[$type][$sourceAccountType])) {
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
diff --git a/app/Console/Commands/Correction/FixPiggies.php b/app/Console/Commands/Correction/FixPiggies.php
index c7701747b5..5cfcd81571 100644
--- a/app/Console/Commands/Correction/FixPiggies.php
+++ b/app/Console/Commands/Correction/FixPiggies.php
@@ -46,6 +46,9 @@ class FixPiggies extends Command
*/
protected $signature = 'firefly-iii:fix-piggies';
+ /** @var int */
+ private $count;
+
/**
* Execute the console command.
*
@@ -53,29 +56,43 @@ class FixPiggies extends Command
*/
public function handle(): int
{
- $start = microtime(true);
- $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
- $set->each(
- function (PiggyBankEvent $event) {
- if (null === $event->transaction_journal_id) {
- return true;
- }
- /** @var TransactionJournal $journal */
- $journal = $event->transactionJournal()->first();
- if (null === $journal) {
- return true;
- }
+ $this->count = 0;
+ $start = microtime(true);
+ $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
- $type = $journal->transactionType->type;
- if (TransactionType::TRANSFER !== $type) {
- $event->transaction_journal_id = null;
- $event->save();
- $this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
- }
+ /** @var PiggyBankEvent $event */
+ foreach ($set as $event) {
- return true;
+ if (null === $event->transaction_journal_id) {
+ continue;
}
- );
+ /** @var TransactionJournal $journal */
+ $journal = $event->transactionJournal;
+ // @codeCoverageIgnoreStart
+ if (null === $journal) {
+ $event->transaction_journal_id = null;
+ $event->save();
+ $this->count++;
+ continue;
+ }
+ // @codeCoverageIgnoreEnd
+
+ $type = $journal->transactionType->type;
+ if (TransactionType::TRANSFER !== $type) {
+ $event->transaction_journal_id = null;
+ $event->save();
+ $this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
+ $this->count++;
+ continue;
+ }
+ }
+ if (0 === $this->count) {
+ $this->line('All piggy bank events are correct.');
+ }
+ if (0 !== $this->count) {
+ $this->line(sprintf('Fixed %d piggy bank event(s).', $this->count));
+ }
+
$end = round(microtime(true) - $start, 2);
$this->line(sprintf('Verified the content of %d piggy bank events in %s seconds.', $set->count(), $end));
diff --git a/app/Console/Commands/Correction/FixUnevenAmount.php b/app/Console/Commands/Correction/FixUnevenAmount.php
index 5a915cc457..7b72aeef8b 100644
--- a/app/Console/Commands/Correction/FixUnevenAmount.php
+++ b/app/Console/Commands/Correction/FixUnevenAmount.php
@@ -22,6 +22,7 @@
namespace FireflyIII\Console\Commands\Correction;
use DB;
+use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command;
@@ -84,7 +85,7 @@ class FixUnevenAmount extends Command
// one of the transactions is bad.
$journal = TransactionJournal::find($param);
if (!$journal) {
- return;
+ return; // @codeCoverageIgnore
}
/** @var Transaction $source */
$source = $journal->transactions()->where('amount', '<', 0)->first();
@@ -92,11 +93,11 @@ class FixUnevenAmount extends Command
// fix amount of destination:
/** @var Transaction $destination */
- $destination = $journal->transactions()->where('amount', '>', 0)->first();
+ $destination = $journal->transactions()->where('amount', '>', 0)->first();
$destination->amount = $amount;
$destination->save();
- $this->line(sprintf('Corrected amount in transaction journal #%d', $param));
-
+ $message = sprintf('Corrected amount in transaction journal #%d', $param);
+ $this->line($message);
}
}
diff --git a/app/Console/Commands/Correction/RenameMetaFields.php b/app/Console/Commands/Correction/RenameMetaFields.php
index 4b4656d613..5758376a9c 100644
--- a/app/Console/Commands/Correction/RenameMetaFields.php
+++ b/app/Console/Commands/Correction/RenameMetaFields.php
@@ -42,6 +42,9 @@ class RenameMetaFields extends Command
*/
protected $signature = 'firefly-iii:rename-meta-fields';
+ /** @var int */
+ private $count;
+
/**
* Execute the console command.
*
@@ -49,7 +52,8 @@ class RenameMetaFields extends Command
*/
public function handle(): int
{
- $start = microtime(true);
+ $this->count = 0;
+ $start = microtime(true);
$changes = [
'original-source' => 'original_source',
@@ -67,6 +71,12 @@ class RenameMetaFields extends Command
foreach ($changes as $original => $update) {
$this->rename($original, $update);
}
+ if (0 === $this->count) {
+ $this->line('All meta fields are correct.');
+ }
+ if (0 !== $this->count) {
+ $this->line(sprintf('Renamed %d meta field(s).', $this->count));
+ }
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Renamed meta fields in %s seconds', $end));
@@ -80,8 +90,9 @@ class RenameMetaFields extends Command
*/
private function rename(string $original, string $update): void
{
- DB::table('journal_meta')
- ->where('name', '=', $original)
- ->update(['name' => $update]);
+ $count = DB::table('journal_meta')
+ ->where('name', '=', $original)
+ ->update(['name' => $update]);
+ $this->count += $count;
}
}
diff --git a/app/Console/Commands/Correction/TransferBudgets.php b/app/Console/Commands/Correction/TransferBudgets.php
index 9b44827024..8598acc6fc 100644
--- a/app/Console/Commands/Correction/TransferBudgets.php
+++ b/app/Console/Commands/Correction/TransferBudgets.php
@@ -66,6 +66,9 @@ class TransferBudgets extends Command
if (0 === $count) {
$this->info('No invalid budget/journal entries.');
}
+ if(0 !== $count) {
+ $this->line(sprintf('Corrected %d invalid budget/journal entries (entry).', $count));
+ }
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified budget/journals in %s seconds.', $end));
diff --git a/app/Console/Commands/DecryptDatabase.php b/app/Console/Commands/DecryptDatabase.php
index 21cd995db6..437226fe2e 100644
--- a/app/Console/Commands/DecryptDatabase.php
+++ b/app/Console/Commands/DecryptDatabase.php
@@ -49,15 +49,15 @@ class DecryptDatabase extends Command
*
* @var string
*/
- protected $signature = 'firefly:decrypt-all';
+ protected $signature = 'firefly-iii:decrypt-all';
/**
* Execute the console command.
*
- * @return mixed
+ * @return int
* @throws FireflyException
*/
- public function handle()
+ public function handle(): int
{
$this->line('Going to decrypt the database.');
$tables = [
@@ -151,7 +151,7 @@ class DecryptDatabase extends Command
$value = Crypt::decrypt($value);
} catch (DecryptException $e) {
if ('The MAC is invalid.' === $e->getMessage()) {
- throw new FireflyException($e->getMessage());
+ throw new FireflyException($e->getMessage()); // @codeCoverageIgnore
}
Log::debug(sprintf('Could not decrypt. %s', $e->getMessage()));
}
diff --git a/app/Console/Commands/Import/CreateCSVImport.php b/app/Console/Commands/Import/CreateCSVImport.php
index cae2732402..ad64c742db 100644
--- a/app/Console/Commands/Import/CreateCSVImport.php
+++ b/app/Console/Commands/Import/CreateCSVImport.php
@@ -28,18 +28,17 @@ namespace FireflyIII\Console\Commands\Import;
use Exception;
use FireflyIII\Console\Commands\VerifiesAccessToken;
use FireflyIII\Exceptions\FireflyException;
-use FireflyIII\Import\Prerequisites\PrerequisitesInterface;
use FireflyIII\Import\Routine\RoutineInterface;
use FireflyIII\Import\Storage\ImportArrayStorage;
+use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
+use FireflyIII\User;
use Illuminate\Console\Command;
use Log;
/**
* Class CreateCSVImport.
- *
- * @codeCoverageIgnore
*/
class CreateCSVImport extends Command
{
@@ -50,7 +49,6 @@ class CreateCSVImport extends Command
* @var string
*/
protected $description = 'Use this command to create a new CSV file import.';
-
/**
* The name and signature of the console command.
*
@@ -62,169 +60,96 @@ class CreateCSVImport extends Command
{configuration? : The configuration file to use for the import.}
{--user=1 : The user ID that the import should import for.}
{--token= : The user\'s access token.}';
+ /** @var UserRepositoryInterface */
+ private $userRepository;
+ /** @var ImportJobRepositoryInterface */
+ private $importRepository;
+ /** @var ImportJob */
+ private $importJob;
+
+ /**
+ * CreateCSVImport constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->userRepository = app(UserRepositoryInterface::class);
+ $this->importRepository = app(ImportJobRepositoryInterface::class);
+
+ }
/**
* Run the command.
- *
- * @throws FireflyException
*/
public function handle(): int
{
+ // @codeCoverageIgnoreStart
if (!$this->verifyAccessToken()) {
$this->errorLine('Invalid access token.');
return 1;
}
- /** @var UserRepositoryInterface $userRepository */
- $userRepository = app(UserRepositoryInterface::class);
- $file = (string)$this->argument('file');
- $configuration = (string)$this->argument('configuration');
- $user = $userRepository->findNull((int)$this->option('user'));
- $cwd = getcwd();
- $configurationData = [];
-
- if (null === $user) {
- $this->errorLine('User is NULL.');
-
- return 1;
- }
if (!$this->validArguments()) {
$this->errorLine('Invalid arguments.');
return 1;
}
- if ('' !== $configuration) {
- $configurationData = json_decode(file_get_contents($configuration), true);
- if (null === $configurationData) {
- $this->errorLine(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
+ // @codeCoverageIgnoreEnd
+ /** @var User $user */
+ $user = $this->userRepository->findNull((int)$this->option('user'));
+ $file = (string)$this->argument('file');
+ $configuration = (string)$this->argument('configuration');
- return 1;
- }
+ $this->importRepository->setUser($user);
+
+ $configurationData = json_decode(file_get_contents($configuration), true);
+ $this->importJob = $this->importRepository->create('file');
+
+
+ // inform user (and log it)
+ $this->infoLine(sprintf('Import file : %s', $file));
+ $this->infoLine(sprintf('Configuration file : %s', $configuration));
+ $this->infoLine(sprintf('User : #%d (%s)', $user->id, $user->email));
+ $this->infoLine(sprintf('Job : %s', $this->importJob->key));
+
+ try {
+ $this->storeFile($file);
+ } catch (FireflyException $e) {
+ $this->errorLine($e->getMessage());
+
+ return 1;
}
-
- $this->infoLine(sprintf('Going to create a job to import file: %s', $file));
- $this->infoLine(sprintf('Using configuration file: %s', $configuration));
- $this->infoLine(sprintf('Import into user: #%d (%s)', $user->id, $user->email));
-
- /** @var ImportJobRepositoryInterface $jobRepository */
- $jobRepository = app(ImportJobRepositoryInterface::class);
- $jobRepository->setUser($user);
- $importJob = $jobRepository->create('file');
- $this->infoLine(sprintf('Created job "%s"', $importJob->key));
-
- // make sure that job has no prerequisites.
- if ((bool)config('import.has_prereq.csv')) {
- // make prerequisites thing.
- $class = (string)config('import.prerequisites.csv');
- if (!class_exists($class)) {
- throw new FireflyException('No class to handle prerequisites for CSV.'); // @codeCoverageIgnore
- }
- /** @var PrerequisitesInterface $object */
- $object = app($class);
- $object->setUser($user);
- if (!$object->isComplete()) {
- $this->errorLine('CSV Import provider has prerequisites that can only be filled in using the browser.');
-
- return 1;
- }
- }
-
- // store file as attachment.
- if ('' !== $file) {
- $messages = $jobRepository->storeCLIUpload($importJob, 'import_file', $file);
- if ($messages->count() > 0) {
- $this->errorLine($messages->first());
-
- return 1;
- }
- $this->infoLine('File content saved.');
- }
-
- $this->infoLine('Job configuration saved.');
- $jobRepository->setConfiguration($importJob, $configurationData);
- $jobRepository->setStatus($importJob, 'ready_to_run');
-
+ // job is ready to go
+ $this->importRepository->setConfiguration($this->importJob, $configurationData);
+ $this->importRepository->setStatus($this->importJob, 'ready_to_run');
$this->infoLine('The import routine has started. The process is not visible. Please wait.');
Log::debug('Go for import!');
- // run it!
- $className = config('import.routine.file');
- if (null === $className || !class_exists($className)) {
- // @codeCoverageIgnoreStart
- $this->errorLine('No routine for file provider.');
-
- return 1;
- // @codeCoverageIgnoreEnd
- }
// keep repeating this call until job lands on "provider_finished"
- $valid = ['provider_finished'];
- $count = 0;
- while (!in_array($importJob->status, $valid, true) && $count < 6) {
- Log::debug(sprintf('Now in loop #%d.', $count + 1));
- /** @var RoutineInterface $routine */
- $routine = app($className);
- $routine->setImportJob($importJob);
- try {
- $routine->run();
- } catch (FireflyException|Exception $e) {
- $message = 'The import routine crashed: ' . $e->getMessage();
- Log::error($message);
- Log::error($e->getTraceAsString());
+ try {
+ $this->processFile();
+ } catch (FireflyException $e) {
+ $this->errorLine($e->getMessage());
- // set job errored out:
- $jobRepository->setStatus($importJob, 'error');
- $this->errorLine($message);
-
- return 1;
- }
- $count++;
+ return 1;
}
- if ('provider_finished' === $importJob->status) {
- $this->infoLine('Import has finished. Please wait for storage of data.');
- // set job to be storing data:
- $jobRepository->setStatus($importJob, 'storing_data');
- /** @var ImportArrayStorage $storage */
- $storage = app(ImportArrayStorage::class);
- $storage->setImportJob($importJob);
+ // then store data:
+ try {
+ $this->storeData();
+ } catch (FireflyException $e) {
+ $this->errorLine($e->getMessage());
- try {
- $storage->store();
- } catch (FireflyException|Exception $e) {
- $message = 'The import routine crashed: ' . $e->getMessage();
- Log::error($message);
- Log::error($e->getTraceAsString());
-
- // set job errored out:
- $jobRepository->setStatus($importJob, 'error');
- $this->errorLine($message);
-
- return 1;
- }
- // set storage to be finished:
- $jobRepository->setStatus($importJob, 'storage_finished');
+ return 1;
}
// give feedback:
- $this->infoLine('Job has finished.');
- if (null !== $importJob->tag) {
- $this->infoLine(sprintf('%d transaction(s) have been imported.', $importJob->tag->transactionJournals->count()));
- $this->infoLine(sprintf('You can find your transactions under tag "%s"', $importJob->tag->tag));
- }
+ $this->giveFeedback();
- if (null === $importJob->tag) {
- $this->errorLine('No transactions have been imported :(.');
- }
- if (count($importJob->errors) > 0) {
- $this->infoLine(sprintf('%d error(s) occurred:', count($importJob->errors)));
- foreach ($importJob->errors as $err) {
- $this->errorLine('- ' . $err);
- }
- }
// clear cache for user:
app('preferences')->setForUser($user, 'lastActivity', microtime());
@@ -234,6 +159,7 @@ class CreateCSVImport extends Command
/**
* @param string $message
* @param array|null $data
+ * @codeCoverageIgnore
*/
private function errorLine(string $message, array $data = null): void
{
@@ -245,6 +171,7 @@ class CreateCSVImport extends Command
/**
* @param string $message
* @param array $data
+ * @codeCoverageIgnore
*/
private function infoLine(string $message, array $data = null): void
{
@@ -257,6 +184,7 @@ class CreateCSVImport extends Command
*
* @noinspection MultipleReturnStatementsInspection
* @return bool
+ * @codeCoverageIgnore
*/
private function validArguments(): bool
{
@@ -283,6 +211,119 @@ class CreateCSVImport extends Command
return false;
}
+ $configurationData = json_decode(file_get_contents($configuration), true);
+ if (null === $configurationData) {
+ $this->errorLine(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
+
+ return false;
+ }
+
return true;
}
+
+ /**
+ * Store the supplied file as an attachment to this job.
+ *
+ * @param string $file
+ * @throws FireflyException
+ */
+ private function storeFile(string $file): void
+ {
+ // store file as attachment.
+ if ('' !== $file) {
+ $messages = $this->importRepository->storeCLIUpload($this->importJob, 'import_file', $file);
+ if ($messages->count() > 0) {
+ throw new FireflyException($messages->first());
+ }
+ }
+ }
+
+ /**
+ * Keep repeating import call until job lands on "provider_finished".
+ *
+ * @throws FireflyException
+ */
+ private function processFile(): void
+ {
+ $className = config('import.routine.file');
+ $valid = ['provider_finished'];
+ $count = 0;
+
+ while (!in_array($this->importJob->status, $valid, true) && $count < 6) {
+ Log::debug(sprintf('Now in loop #%d.', $count + 1));
+ /** @var RoutineInterface $routine */
+ $routine = app($className);
+ $routine->setImportJob($this->importJob);
+ try {
+ $routine->run();
+ } catch (FireflyException|Exception $e) {
+ $message = 'The import routine crashed: ' . $e->getMessage();
+ Log::error($message);
+ Log::error($e->getTraceAsString());
+
+ // set job errored out:
+ $this->importRepository->setStatus($this->importJob, 'error');
+ throw new FireflyException($message);
+ }
+ $count++;
+ }
+ $this->importRepository->setStatus($this->importJob, 'provider_finished');
+ $this->importJob->status = 'provider_finished';
+ }
+
+ /**
+ *
+ * @throws FireflyException
+ */
+ private function storeData(): void
+ {
+ if ('provider_finished' === $this->importJob->status) {
+ $this->infoLine('Import has finished. Please wait for storage of data.');
+ // set job to be storing data:
+ $this->importRepository->setStatus($this->importJob, 'storing_data');
+
+ /** @var ImportArrayStorage $storage */
+ $storage = app(ImportArrayStorage::class);
+ $storage->setImportJob($this->importJob);
+
+ try {
+ $storage->store();
+ } catch (FireflyException|Exception $e) {
+ $message = 'The import routine crashed: ' . $e->getMessage();
+ Log::error($message);
+ Log::error($e->getTraceAsString());
+
+ // set job errored out:
+ $this->importRepository->setStatus($this->importJob, 'error');
+ throw new FireflyException($message);
+
+ }
+ // set storage to be finished:
+ $this->importRepository->setStatus($this->importJob, 'storage_finished');
+ }
+ }
+
+ /**
+ *
+ */
+ private function giveFeedback(): void
+ {
+ $this->infoLine('Job has finished.');
+
+
+ if (null !== $this->importJob->tag) {
+ $this->infoLine(sprintf('%d transaction(s) have been imported.', $this->importJob->tag->transactionJournals->count()));
+ $this->infoLine(sprintf('You can find your transactions under tag "%s"', $this->importJob->tag->tag));
+ }
+
+ if (null === $this->importJob->tag) {
+ $this->errorLine('No transactions have been imported :(.');
+ }
+ if (count($this->importJob->errors) > 0) {
+ $this->infoLine(sprintf('%d error(s) occurred:', count($this->importJob->errors)));
+ foreach ($this->importJob->errors as $err) {
+ $this->errorLine('- ' . $err);
+ }
+ }
+ }
}
diff --git a/app/Console/Commands/Integrity/ReportEmptyObjects.php b/app/Console/Commands/Integrity/ReportEmptyObjects.php
index ec5dc922ec..69ab4b8848 100644
--- a/app/Console/Commands/Integrity/ReportEmptyObjects.php
+++ b/app/Console/Commands/Integrity/ReportEmptyObjects.php
@@ -80,9 +80,8 @@ class ReportEmptyObjects extends Command
/** @var stdClass $entry */
foreach ($set as $entry) {
- $name = $entry->name;
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.';
- $line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $name);
+ $line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $entry->name);
$this->line($line);
}
}
@@ -125,13 +124,12 @@ class ReportEmptyObjects extends Command
/** @var stdClass $entry */
foreach ($set as $entry) {
- $objName = $entry->name;
$line = sprintf(
'User #%d (%s) has budget #%d ("%s") which has no transaction journals.',
$entry->user_id,
$entry->email,
$entry->id,
- $objName
+ $entry->name
);
$this->line($line);
}
@@ -151,14 +149,12 @@ class ReportEmptyObjects extends Command
/** @var stdClass $entry */
foreach ($set as $entry) {
- $objName = $entry->name;
-
$line = sprintf(
'User #%d (%s) has category #%d ("%s") which has no transaction journals.',
$entry->user_id,
$entry->email,
$entry->id,
- $objName
+ $entry->name
);
$this->line($line);
}
@@ -178,14 +174,13 @@ class ReportEmptyObjects extends Command
/** @var stdClass $entry */
foreach ($set as $entry) {
- $objName = $entry->tag;
$line = sprintf(
'User #%d (%s) has tag #%d ("%s") which has no transaction journals.',
$entry->user_id,
$entry->email,
$entry->id,
- $objName
+ $entry->tag
);
$this->line($line);
}
diff --git a/app/Console/Commands/Integrity/ReportIntegrity.php b/app/Console/Commands/Integrity/ReportIntegrity.php
index 83ca43a7c7..541418c67e 100644
--- a/app/Console/Commands/Integrity/ReportIntegrity.php
+++ b/app/Console/Commands/Integrity/ReportIntegrity.php
@@ -30,6 +30,7 @@ use Artisan;
/**
* Class ReportIntegrity
+ * @codeCoverageIgnore
*/
class ReportIntegrity extends Command
{
diff --git a/app/Console/Commands/Tools/ApplyRules.php b/app/Console/Commands/Tools/ApplyRules.php
index 6e7f92157d..3eb4019a85 100644
--- a/app/Console/Commands/Tools/ApplyRules.php
+++ b/app/Console/Commands/Tools/ApplyRules.php
@@ -75,8 +75,6 @@ class ApplyRules extends Command
private $acceptedAccounts;
/** @var Carbon */
private $endDate;
- /** @var Collection */
- private $results;
/** @var array */
private $ruleGroupSelection;
/** @var array */
@@ -106,7 +104,6 @@ class ApplyRules extends Command
$this->accounts = new Collection;
$this->ruleSelection = [];
$this->ruleGroupSelection = [];
- $this->results = new Collection;
$this->ruleRepository = app(RuleRepositoryInterface::class);
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
$this->acceptedAccounts = [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE];
@@ -121,11 +118,14 @@ class ApplyRules extends Command
*/
public function handle(): int
{
+ // @codeCoverageIgnoreStart
if (!$this->verifyAccessToken()) {
$this->error('Invalid access token.');
return 1;
}
+ // @codeCoverageIgnoreEnd
+
// set user:
$this->ruleRepository->setUser($this->getUser());
$this->ruleGroupRepository->setUser($this->getUser());
@@ -141,24 +141,8 @@ class ApplyRules extends Command
$this->grabAllRules();
// loop all groups and rules and indicate if they're included:
- $count = 0;
- $rulesToApply = [];
- /** @var RuleGroup $group */
- foreach ($this->groups as $group) {
- /** @var Rule $rule */
- foreach ($group->rules as $rule) {
- // if in rule selection, or group in selection or all rules, it's included.
- $test = $this->includeRule($rule, $group);
- if (true === $test) {
- Log::debug(sprintf('Will include rule #%d "%s"', $rule->id, $rule->title));
- $count++;
- $rulesToApply[] = $rule->id;
- }
- if (false === $test) {
- Log::debug(sprintf('Will not include rule #%d "%s"', $rule->id, $rule->title));
- }
- }
- }
+ $rulesToApply = $this->getRulesToApply();
+ $count = count($rulesToApply);
if (0 === $count) {
$this->error('No rules or rule groups have been included.');
$this->warn('Make a selection using:');
@@ -167,7 +151,6 @@ class ApplyRules extends Command
$this->warn(' --all_rules');
}
- // get transactions from asset accounts.
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->getUser());
@@ -176,7 +159,7 @@ class ApplyRules extends Command
$journals = $collector->getExtractedJournals();
// start running rules.
- $this->line(sprintf('Will apply %d rules to %d transactions.', $count, count($journals)));
+ $this->line(sprintf('Will apply %d rule(s) to %d transaction(s).', $count, count($journals)));
// start looping.
/** @var RuleEngine $ruleEngine */
@@ -191,6 +174,7 @@ class ApplyRules extends Command
Log::debug('Start of new journal.');
$ruleEngine->processJournalArray($journal);
Log::debug('Done with all rules for this group + done with journal.');
+ /** @noinspection DisconnectedForeachInstructionInspection */
$bar->advance();
}
$this->line('');
@@ -212,16 +196,10 @@ class ApplyRules extends Command
}
// verify rule groups.
- $result = $this->verifyInputRuleGroups();
- if (false === $result) {
- return $result;
- }
+ $this->verifyInputRuleGroups();
// verify rules.
- $result = $this->verifyInputRules();
- if (false === $result) {
- return $result;
- }
+ $this->verifyInputRules();
$this->verifyInputDates();
@@ -243,11 +221,13 @@ class ApplyRules extends Command
$finalList = new Collection;
$accountList = explode(',', $accountString);
+ // @codeCoverageIgnoreStart
if (0 === count($accountList)) {
$this->error('Please use the --accounts option to indicate the accounts to apply rules to.');
return false;
}
+ // @codeCoverageIgnoreEnd
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
@@ -284,11 +264,12 @@ class ApplyRules extends Command
return true;
}
$ruleGroupList = explode(',', $ruleGroupString);
-
+ // @codeCoverageIgnoreStart
if (0 === count($ruleGroupList)) {
// can be empty.
return true;
}
+ // @codeCoverageIgnoreEnd
foreach ($ruleGroupList as $ruleGroupId) {
$ruleGroup = $this->ruleGroupRepository->find((int)$ruleGroupId);
if ($ruleGroup->active) {
@@ -314,11 +295,14 @@ class ApplyRules extends Command
}
$ruleList = explode(',', $ruleString);
+ // @codeCoverageIgnoreStart
if (0 === count($ruleList)) {
// can be empty.
return true;
}
+ // @codeCoverageIgnoreEnd
+
foreach ($ruleList as $ruleId) {
$rule = $this->ruleRepository->find((int)$ruleId);
if (null !== $rule && $rule->active) {
@@ -383,4 +367,27 @@ class ApplyRules extends Command
in_array($rule->id, $this->ruleSelection, true) ||
$this->allRules;
}
+
+ /**
+ * @return array
+ */
+ private function getRulesToApply(): array
+ {
+ $rulesToApply = [];
+ /** @var RuleGroup $group */
+ foreach ($this->groups as $group) {
+ $rules = $this->ruleGroupRepository->getActiveStoreRules($group);
+ /** @var Rule $rule */
+ foreach ($rules as $rule) {
+ // if in rule selection, or group in selection or all rules, it's included.
+ $test = $this->includeRule($rule, $group);
+ if (true === $test) {
+ Log::debug(sprintf('Will include rule #%d "%s"', $rule->id, $rule->title));
+ $rulesToApply[] = $rule->id;
+ }
+ }
+ }
+
+ return $rulesToApply;
+ }
}
diff --git a/app/Console/Commands/Upgrade/AccountCurrencies.php b/app/Console/Commands/Upgrade/AccountCurrencies.php
index 4843162430..14ee979839 100644
--- a/app/Console/Commands/Upgrade/AccountCurrencies.php
+++ b/app/Console/Commands/Upgrade/AccountCurrencies.php
@@ -24,8 +24,10 @@ namespace FireflyIII\Console\Commands\Upgrade;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
+use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
+use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Console\Command;
use Log;
@@ -48,9 +50,23 @@ class AccountCurrencies extends Command
* @var string
*/
protected $signature = 'firefly-iii:account-currencies {--F|force : Force the execution of this command.}';
-
/** @var AccountRepositoryInterface */
- private $repository;
+ private $accountRepos;
+ /** @var UserRepositoryInterface */
+ private $userRepos;
+ /** @var int */
+ private $count;
+
+ /**
+ * AccountCurrencies constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->accountRepos = app(AccountRepositoryInterface::class);
+ $this->userRepos = app(UserRepositoryInterface::class);
+ $this->count = 0;
+ }
/**
* Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account.
@@ -59,8 +75,7 @@ class AccountCurrencies extends Command
*/
public function handle(): int
{
- $this->repository = app(AccountRepositoryInterface::class);
- $start = microtime(true);
+ $start = microtime(true);
if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.');
@@ -69,11 +84,17 @@ class AccountCurrencies extends Command
Log::debug('Now in updateAccountCurrencies()');
$this->updateAccountCurrencies();
+ if (0 === $this->count) {
+ $this->line('All accounts are OK.');
+ }
+ if (0 !== $this->count) {
+ $this->line(sprintf('Corrected %d account(s).', $this->count));
+ }
+
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified and fixed account currencies in %s seconds.', $end));
$this->markAsExecuted();
-
return 0;
}
@@ -100,41 +121,53 @@ class AccountCurrencies extends Command
}
/**
- * @param Account $account
+ * @param Account $account
* @param TransactionCurrency $currency
*/
private function updateAccount(Account $account, TransactionCurrency $currency): void
{
- $this->repository->setUser($account->user);
-
- $accountCurrency = (int)$this->repository->getMetaValue($account, 'currency_id');
- $openingBalance = $account->getOpeningBalance();
- $obCurrency = (int)$openingBalance->transaction_currency_id;
+ $this->accountRepos->setUser($account->user);
+ $accountCurrency = (int)$this->accountRepos->getMetaValue($account, 'currency_id');
+ $openingBalance = $this->accountRepos->getOpeningBalance($account);
+ $obCurrency = 0;
+ if (null !== $openingBalance) {
+ $obCurrency = (int)$openingBalance->transaction_currency_id;
+ }
// both 0? set to default currency:
if (0 === $accountCurrency && 0 === $obCurrency) {
AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete();
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $currency->id]);
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $currency->code));
-
+ $this->count++;
return;
}
// account is set to 0, opening balance is not?
if (0 === $accountCurrency && $obCurrency > 0) {
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]);
- $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $currency->code));
+ $this->line(sprintf('Account #%d ("%s") now has a currency setting (#%d).', $account->id, $account->name, $obCurrency));
+ $this->count++;
return;
}
+
// do not match and opening balance id is not null.
- if ($accountCurrency !== $obCurrency && $openingBalance->id > 0) {
+ if ($accountCurrency !== $obCurrency && null !== $openingBalance) {
// update opening balance:
$openingBalance->transaction_currency_id = $accountCurrency;
$openingBalance->save();
+ $openingBalance->transactions->each(
+ static function (Transaction $transaction) use ($accountCurrency) {
+ $transaction->transaction_currency_id = $accountCurrency;
+ $transaction->save();
+ });
$this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
+ $this->count++;
+
+ return;
}
}
@@ -143,24 +176,21 @@ class AccountCurrencies extends Command
*/
private function updateAccountCurrencies(): void
{
-
+ $users = $this->userRepos->all();
$defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR');
- $users = User::get();
foreach ($users as $user) {
$this->updateCurrenciesForUser($user, $defaultCurrencyCode);
}
}
/**
- * @param User $user
+ * @param User $user
* @param string $systemCurrencyCode
*/
private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void
{
- $accounts = $user->accounts()
- ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
- ->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])
- ->get(['accounts.*']);
+ $this->accountRepos->setUser($user);
+ $accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
// get user's currency preference:
$defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data;
diff --git a/app/Console/Commands/Upgrade/BackToJournals.php b/app/Console/Commands/Upgrade/BackToJournals.php
index 35ee45512d..5610e1751b 100644
--- a/app/Console/Commands/Upgrade/BackToJournals.php
+++ b/app/Console/Commands/Upgrade/BackToJournals.php
@@ -54,6 +54,7 @@ class BackToJournals extends Command
*/
public function handle(): int
{
+ // @codeCoverageIgnoreStart
$start = microtime(true);
if (!$this->isMigrated()) {
$this->error('Please run firefly-iii:migrate-to-groups first.');
@@ -66,6 +67,7 @@ class BackToJournals extends Command
if (true === $this->option('force')) {
$this->warn('Forcing the command.');
}
+ // @codeCoverageIgnoreEnd
$this->migrateAll();
$end = round(microtime(true) - $start, 2);
@@ -82,8 +84,9 @@ class BackToJournals extends Command
{
$transactions = DB::table('budget_transaction')->distinct()->get(['transaction_id'])->pluck('transaction_id')->toArray();
- return DB::table('transactions')->whereIn('transactions.id', $transactions)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray(
- );
+ return DB::table('transactions')
+ ->whereIn('transactions.id', $transactions)
+ ->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
}
/**
@@ -93,8 +96,9 @@ class BackToJournals extends Command
{
$transactions = DB::table('category_transaction')->distinct()->get(['transaction_id'])->pluck('transaction_id')->toArray();
- return DB::table('transactions')->whereIn('transactions.id', $transactions)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray(
- );
+ return DB::table('transactions')
+ ->whereIn('transactions.id', $transactions)
+ ->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
}
/**
@@ -149,9 +153,10 @@ class BackToJournals extends Command
*/
private function migrateBudgets(): void
{
+
$journalIds = $this->getIdsForBudgets();
$journals = TransactionJournal::whereIn('id', $journalIds)->with(['transactions', 'budgets', 'transactions.budgets'])->get();
- $this->line(sprintf('Check %d transaction journals for budget info.',count($journals)));
+ $this->line(sprintf('Check %d transaction journal(s) for budget info.', count($journals)));
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$this->migrateBudgetsForJournal($journal);
@@ -163,26 +168,34 @@ class BackToJournals extends Command
*/
private function migrateBudgetsForJournal(TransactionJournal $journal): void
{
+
// grab category from first transaction
/** @var Transaction $transaction */
$transaction = $journal->transactions->first();
if (null === $transaction) {
+ // @codeCoverageIgnoreStart
$this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id));
return;
+ // @codeCoverageIgnoreEnd
}
/** @var Budget $budget */
$budget = $transaction->budgets->first();
/** @var Budget $journalBudget */
$journalBudget = $journal->budgets->first();
+
+ // both have a budget, but they don't match.
if (null !== $budget && null !== $journalBudget && $budget->id !== $journalBudget->id) {
// sync to journal:
$journal->budgets()->sync([(int)$budget->id]);
+
+ return;
}
- // budget in transaction overrules journal.
- if (null === $budget && null !== $journalBudget) {
- $journal->budgets()->sync([]);
+ // transaction has a budget, but the journal doesn't.
+ if (null !== $budget && null === $journalBudget) {
+ // sync to journal:
+ $journal->budgets()->sync([(int)$budget->id]);
}
}
@@ -193,7 +206,7 @@ class BackToJournals extends Command
{
$journalIds = $this->getIdsForCategories();
$journals = TransactionJournal::whereIn('id', $journalIds)->with(['transactions', 'categories', 'transactions.categories'])->get();
- $this->line(sprintf('Check %d transaction journals for category info.', count($journals)));
+ $this->line(sprintf('Check %d transaction journal(s) for category info.', count($journals)));
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
$this->migrateCategoriesForJournal($journal);
@@ -209,22 +222,26 @@ class BackToJournals extends Command
/** @var Transaction $transaction */
$transaction = $journal->transactions->first();
if (null === $transaction) {
+ // @codeCoverageIgnoreStart
$this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id));
return;
+ // @codeCoverageIgnoreEnd
}
/** @var Category $category */
$category = $transaction->categories->first();
/** @var Category $journalCategory */
$journalCategory = $journal->categories->first();
+
+ // both have a category, but they don't match.
if (null !== $category && null !== $journalCategory && $category->id !== $journalCategory->id) {
// sync to journal:
$journal->categories()->sync([(int)$category->id]);
}
- // category in transaction overrules journal.
- if (null === $category && null !== $journalCategory) {
- $journal->categories()->sync([]);
+ // transaction has a category, but the journal doesn't.
+ if (null !== $category && null === $journalCategory) {
+ $journal->categories()->sync([(int)$category->id]);
}
}
}
diff --git a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php
index c3205fdea0..99a0068d49 100644
--- a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php
+++ b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php
@@ -55,11 +55,14 @@ class BudgetLimitCurrency extends Command
public function handle(): int
{
$start = microtime(true);
+ // @codeCoverageIgnoreStart
if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.');
return 0;
}
+ // @codeCoverageIgnoreEnd
+
$count = 0;
$budgetLimits = BudgetLimit::get();
/** @var BudgetLimit $budgetLimit */
diff --git a/app/Console/Commands/Upgrade/CCLiabilities.php b/app/Console/Commands/Upgrade/CCLiabilities.php
index b6c29c266d..b09a172446 100644
--- a/app/Console/Commands/Upgrade/CCLiabilities.php
+++ b/app/Console/Commands/Upgrade/CCLiabilities.php
@@ -58,14 +58,19 @@ class CCLiabilities extends Command
public function handle(): int
{
$start = microtime(true);
+
+ // @codeCoverageIgnoreStart
if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.');
return 0;
}
+ // @codeCoverageIgnoreEnd
+
$ccType = AccountType::where('type', AccountType::CREDITCARD)->first();
$debtType = AccountType::where('type', AccountType::DEBT)->first();
if (null === $ccType || null === $debtType) {
+ $this->info('No incorrectly stored credit card liabilities.');
return 0;
}
/** @var Collection $accounts */
diff --git a/app/Console/Commands/Upgrade/JournalCurrencies.php b/app/Console/Commands/Upgrade/JournalCurrencies.php
index df4cbaa9fd..0c12eeef09 100644
--- a/app/Console/Commands/Upgrade/JournalCurrencies.php
+++ b/app/Console/Commands/Upgrade/JournalCurrencies.php
@@ -62,6 +62,20 @@ class JournalCurrencies extends Command
private $currencyRepos;
/** @var JournalRepositoryInterface */
private $journalRepos;
+ /** @var int */
+ private $count;
+
+ /**
+ * JournalCurrencies constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->count = 0;
+ $this->accountRepos = app(AccountRepositoryInterface::class);
+ $this->currencyRepos = app(CurrencyRepositoryInterface::class);
+ $this->journalRepos = app(JournalRepositoryInterface::class);
+ }
/**
* Execute the console command.
@@ -71,20 +85,27 @@ class JournalCurrencies extends Command
public function handle(): int
{
$this->accountCurrencies = [];
- $this->accountRepos = app(AccountRepositoryInterface::class);
- $this->currencyRepos = app(CurrencyRepositoryInterface::class);
- $this->journalRepos = app(JournalRepositoryInterface::class);
+
$start = microtime(true);
+ // @codeCoverageIgnoreStart
if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.');
return 0;
}
+ // @codeCoverageIgnoreEnd
$this->updateTransferCurrencies();
$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));
+ }
$end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified and fixed transaction currencies in %s seconds.', $end));
@@ -137,7 +158,8 @@ class JournalCurrencies extends Command
private function getFirstAssetTransaction(TransactionJournal $journal): ?Transaction
{
$result = $journal->transactions->first(
- function (Transaction $transaction) {
+ static function (Transaction $transaction) {
+ // type can also be liability.
return AccountType::ASSET === $transaction->account->accountType->type;
}
);
@@ -180,7 +202,7 @@ class JournalCurrencies extends Command
* This method makes sure that the transaction journal uses the currency given in the transaction.
*
* @param TransactionJournal $journal
- * @param Transaction $transaction
+ * @param Transaction $transaction
*/
private function updateJournalCurrency(TransactionJournal $journal, Transaction $transaction): void
{
@@ -192,6 +214,7 @@ class JournalCurrencies extends Command
}
if (!((int)$currency->id === (int)$journal->transaction_currency_id)) {
+ $this->count++;
$this->line(
sprintf(
'Transfer #%d ("%s") has been updated to use %s instead of %s.',
@@ -223,10 +246,11 @@ class JournalCurrencies extends Command
}
$journal->transactions->each(
- function (Transaction $transaction) use ($currency) {
+ static function (Transaction $transaction) use ($currency) {
if (null === $transaction->transaction_currency_id) {
$transaction->transaction_currency_id = $currency->id;
$transaction->save();
+ $this->count++;
}
// when mismatch in transaction:
@@ -235,11 +259,13 @@ class JournalCurrencies extends Command
$transaction->foreign_amount = $transaction->amount;
$transaction->transaction_currency_id = $currency->id;
$transaction->save();
+ $this->count++;
}
}
);
// also update the journal, of course:
$journal->transaction_currency_id = $currency->id;
+ $this->count++;
$journal->save();
}
@@ -255,11 +281,15 @@ class JournalCurrencies extends Command
*/
private function updateOtherJournalsCurrencies(): void
{
- $set = TransactionJournal
- ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->whereNotIn('transaction_types.type', [TransactionType::TRANSFER])
- ->with(['transactions', 'transactions.account', 'transactions.account.accountType'])
- ->get(['transaction_journals.*']);
+ $set =
+ $this->journalRepos->getAllJournals(
+ [
+ TransactionType::WITHDRAWAL,
+ TransactionType::DEPOSIT,
+ TransactionType::OPENING_BALANCE,
+ TransactionType::RECONCILIATION,
+ ]
+ );
/** @var TransactionJournal $journal */
foreach ($set as $journal) {
@@ -292,46 +322,54 @@ class JournalCurrencies extends Command
$this->journalRepos->setUser($user);
$this->currencyRepos->setUser($user);
- $sourceAccountCurrency = $this->getCurrency($sourceAccount);
- $destAccountCurrency = $this->getCurrency($destAccount);
- if (null === $sourceAccountCurrency) {
- Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $sourceAccount->id, $sourceAccount->name));
+ $sourceCurrency = $this->getCurrency($sourceAccount);
+ $destCurrency = $this->getCurrency($destAccount);
+ if (null === $sourceCurrency) {
+ $message = sprintf('Account #%d ("%s") must have currency preference but has none.', $sourceAccount->id, $sourceAccount->name);
+ Log::error($message);
+ $this->error($message);
return;
}
// has no currency ID? Must have, so fill in using account preference:
if (null === $source->transaction_currency_id) {
- $source->transaction_currency_id = (int)$sourceAccountCurrency->id;
- Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $source->id, $sourceAccountCurrency->code));
+ $source->transaction_currency_id = (int)$sourceCurrency->id;
+ $message = sprintf('Transaction #%d has no currency setting, now set to %s.', $source->id, $sourceCurrency->code);
+ Log::debug($message);
+ $this->line($message);
+ $this->count++;
$source->save();
}
// does not match the source account (see above)? Can be fixed
// when mismatch in transaction and NO foreign amount is set:
- if (!((int)$source->transaction_currency_id === (int)$sourceAccountCurrency->id) && null === $source->foreign_amount) {
- Log::debug(
- sprintf(
- 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
- $source->id,
- $source->transaction_currency_id,
- $sourceAccountCurrency->id,
- $source->amount
- )
+ if (!((int)$source->transaction_currency_id === (int)$sourceCurrency->id) && null === $source->foreign_amount) {
+ $message = sprintf(
+ 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
+ $source->id,
+ $source->transaction_currency_id,
+ $sourceCurrency->id,
+ $source->amount
);
- $source->transaction_currency_id = (int)$sourceAccountCurrency->id;
+ Log::debug($message);
+ $this->line($message);
+ $this->count++;
+ $source->transaction_currency_id = (int)$sourceCurrency->id;
$source->save();
}
- if (null === $destAccountCurrency) {
- Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $destAccount->id, $destAccount->name));
+ if (null === $destCurrency) {
+ $message = sprintf('Account #%d ("%s") must have currency preference but has none.', $destAccount->id, $destAccount->name);
+ Log::error($message);
+ $this->line($message);
return;
}
// if the destination account currency is the same, both foreign_amount and foreign_currency_id must be NULL for both transactions:
- if ((int)$destAccountCurrency->id === (int)$sourceAccountCurrency->id) {
+ if ((int)$destCurrency->id === (int)$sourceCurrency->id) {
// update both transactions to match:
$source->foreign_amount = null;
$source->foreign_currency_id = null;
@@ -343,22 +381,24 @@ class JournalCurrencies extends Command
sprintf(
'Currency for account "%s" is %s, and currency for account "%s" is also
%s, so %s #%d (#%d and #%d) has been verified to be to %s exclusively.',
- $destAccount->name, $destAccountCurrency->code,
- $sourceAccount->name, $sourceAccountCurrency->code,
+ $destAccount->name, $destCurrency->code,
+ $sourceAccount->name, $sourceCurrency->code,
$journal->transactionType->type, $journal->id,
- $source->id, $destination->id, $sourceAccountCurrency->code
+ $source->id, $destination->id, $sourceCurrency->code
)
);
+ $this->count++;
return;
}
// if destination account currency is different, both transactions must have this currency as foreign currency id.
- if (!((int)$destAccountCurrency->id === (int)$sourceAccountCurrency->id)) {
- $source->foreign_currency_id = $destAccountCurrency->id;
- $destination->foreign_currency_id = $destAccountCurrency->id;
+ if (!((int)$destCurrency->id === (int)$sourceCurrency->id)) {
+ $source->foreign_currency_id = $destCurrency->id;
+ $destination->foreign_currency_id = $destCurrency->id;
$source->save();
$destination->save();
+ $this->count++;
Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $source->id, $destination->id));
}
@@ -366,6 +406,7 @@ class JournalCurrencies extends Command
if (null === $source->foreign_amount && null !== $destination->foreign_amount) {
$source->foreign_amount = bcmul((string)$destination->foreign_amount, '-1');
$source->save();
+ $this->count++;
Log::debug(sprintf('Restored foreign amount of source transaction (1) #%d to %s', $source->id, $source->foreign_amount));
}
@@ -373,6 +414,7 @@ class JournalCurrencies extends Command
if (null === $destination->foreign_amount && null !== $destination->foreign_amount) {
$destination->foreign_amount = bcmul((string)$destination->foreign_amount, '-1');
$destination->save();
+ $this->count++;
Log::debug(sprintf('Restored foreign amount of destination transaction (2) #%d to %s', $destination->id, $destination->foreign_amount));
}
@@ -385,6 +427,7 @@ class JournalCurrencies extends Command
$destination->foreign_amount = $destination->amount;
$source->save();
$destination->save();
+ $this->count++;
return;
}
@@ -396,6 +439,7 @@ class JournalCurrencies extends Command
$foreignAmount
)
);
+ $this->count++;
$source->foreign_amount = bcmul($foreignPositive, '-1');
$destination->foreign_amount = $foreignPositive;
$source->save();
@@ -415,12 +459,7 @@ class JournalCurrencies extends Command
*/
private function updateTransferCurrencies(): void
{
- $set = TransactionJournal
- ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
- ->where('transaction_types.type', TransactionType::TRANSFER)
- ->with(['user', 'transactionType', 'transactionCurrency', 'transactions', 'transactions.account'])
- ->get(['transaction_journals.*']);
-
+ $set = $this->journalRepos->getAllJournals([TransactionType::TRANSFER]);
/** @var TransactionJournal $journal */
foreach ($set as $journal) {
$this->updateTransferCurrency($journal);
@@ -435,6 +474,7 @@ class JournalCurrencies extends Command
$sourceTransaction = $this->getSourceTransaction($transfer);
$destTransaction = $this->getDestinationTransaction($transfer);
+ // @codeCoverageIgnoreStart
if (null === $sourceTransaction) {
$this->info(sprintf('Source transaction for journal #%d is null.', $transfer->id));
@@ -445,6 +485,7 @@ class JournalCurrencies extends Command
return;
}
+ // @codeCoverageIgnoreEnd
$this->updateTransactionCurrency($transfer, $sourceTransaction, $destTransaction);
$this->updateJournalCurrency($transfer, $sourceTransaction);
diff --git a/app/Http/Controllers/System/InstallController.php b/app/Http/Controllers/System/InstallController.php
index cc4ec6efd5..b070069ce9 100644
--- a/app/Http/Controllers/System/InstallController.php
+++ b/app/Http/Controllers/System/InstallController.php
@@ -67,7 +67,7 @@ class InstallController extends Controller
$this->upgradeCommands = [
// there are x initial commands
'migrate' => ['--seed' => true, '--force' => true],
- 'firefly:decrypt-all' => [],
+ 'firefly-iii:decrypt-all' => [],
'generate-keys' => [], // an exception :(
// there are 10 upgrade commands.
diff --git a/app/Models/Account.php b/app/Models/Account.php
index b9c7fffb1d..a1ceafd595 100644
--- a/app/Models/Account.php
+++ b/app/Models/Account.php
@@ -165,24 +165,6 @@ class Account extends Model
return $name;
}
- /**
- * Returns the opening balance.
- *
- * @return TransactionJournal
- */
- public function getOpeningBalance(): TransactionJournal
- {
- $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transactions.account_id', $this->id)
- ->transactionTypes([TransactionType::OPENING_BALANCE])
- ->first(['transaction_journals.*']);
- if (null === $journal) {
- return new TransactionJournal;
- }
-
- return $journal;
- }
-
/**
* @codeCoverageIgnore
* Get all of the notes.
diff --git a/app/Models/PiggyBankEvent.php b/app/Models/PiggyBankEvent.php
index 9d9e39b59c..88097add6b 100644
--- a/app/Models/PiggyBankEvent.php
+++ b/app/Models/PiggyBankEvent.php
@@ -38,10 +38,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property string $amount
* @property Carbon created_at
* @property Carbon updated_at
- * @property \Illuminate\Support\Carbon|null $created_at
- * @property \Illuminate\Support\Carbon|null $updated_at
- * @property \Illuminate\Support\Carbon $date
- * @property-read \FireflyIII\Models\TransactionJournal|null $transactionJournal
* @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent query()
diff --git a/app/Providers/ImportServiceProvider.php b/app/Providers/ImportServiceProvider.php
new file mode 100644
index 0000000000..81154dbb71
--- /dev/null
+++ b/app/Providers/ImportServiceProvider.php
@@ -0,0 +1,61 @@
+.
+ */
+declare(strict_types=1);
+
+namespace FireflyIII\Providers;
+
+use FireflyIII\Repositories\ImportJob\ImportJobRepository;
+use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
+use Illuminate\Foundation\Application;
+use Illuminate\Support\ServiceProvider;
+
+/**
+ * @codeCoverageIgnore
+ * Class ImportServiceProvider.
+ */
+class ImportServiceProvider extends ServiceProvider
+{
+ /**
+ * Bootstrap the application services.
+ */
+ public function boot(): void
+ {
+ }
+
+ /**
+ * Register the application services.
+ */
+ public function register(): void
+ {
+ $this->app->bind(
+ ImportJobRepositoryInterface::class,
+ function (Application $app) {
+ /** @var ImportJobRepositoryInterface $repository */
+ $repository = app(ImportJobRepository::class);
+ if ($app->auth->check()) {
+ $repository->setUser(auth()->user());
+ }
+
+ return $repository;
+ }
+ );
+ }
+}
diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php
index 1acc585290..485afa99fe 100644
--- a/app/Repositories/Account/AccountRepository.php
+++ b/app/Repositories/Account/AccountRepository.php
@@ -70,7 +70,7 @@ class AccountRepository implements AccountRepositoryInterface
/**
* Moved here from account CRUD.
*
- * @param Account $account
+ * @param Account $account
* @param Account|null $moveTo
*
* @return bool
@@ -88,7 +88,7 @@ class AccountRepository implements AccountRepositoryInterface
/**
* @param string $number
- * @param array $types
+ * @param array $types
*
* @return Account|null
*/
@@ -115,7 +115,7 @@ class AccountRepository implements AccountRepositoryInterface
/**
* @param string $iban
- * @param array $types
+ * @param array $types
*
* @return Account|null
*/
@@ -141,7 +141,7 @@ class AccountRepository implements AccountRepositoryInterface
/**
* @param string $name
- * @param array $types
+ * @param array $types
*
* @return Account|null
*/
@@ -229,9 +229,10 @@ class AccountRepository implements AccountRepositoryInterface
if (count($accountIds) > 0) {
$query->whereIn('accounts.id', $accountIds);
}
- $query->orderBy('accounts.name','ASC');
+ $query->orderBy('accounts.name', 'ASC');
$result = $query->get(['accounts.*']);
+
return $result;
}
@@ -247,7 +248,7 @@ class AccountRepository implements AccountRepositoryInterface
if (count($types) > 0) {
$query->accountTypeIn($types);
}
- $query->orderBy('accounts.name','ASC');
+ $query->orderBy('accounts.name', 'ASC');
$result = $query->get(['accounts.*']);
@@ -271,8 +272,8 @@ class AccountRepository implements AccountRepositoryInterface
$query->accountTypeIn($types);
}
$query->where('active', 1);
- $query->orderBy('accounts.account_type_id','ASC');
- $query->orderBy('accounts.name','ASC');
+ $query->orderBy('accounts.account_type_id', 'ASC');
+ $query->orderBy('accounts.name', 'ASC');
$result = $query->get(['accounts.*']);
return $result;
@@ -322,7 +323,7 @@ class AccountRepository implements AccountRepositoryInterface
* Return meta value for account. Null if not found.
*
* @param Account $account
- * @param string $field
+ * @param string $field
*
* @return null|string
*/
@@ -546,7 +547,7 @@ class AccountRepository implements AccountRepositoryInterface
/**
* @param string $query
- * @param array $types
+ * @param array $types
*
* @return Collection
*/
@@ -590,7 +591,7 @@ class AccountRepository implements AccountRepositoryInterface
/**
* @param Account $account
- * @param array $data
+ * @param array $data
*
* @return Account
* @throws \FireflyIII\Exceptions\FireflyException
@@ -605,4 +606,17 @@ class AccountRepository implements AccountRepositoryInterface
return $account;
}
+
+ /**
+ * @param Account $account
+ * @return TransactionJournal|null
+ */
+ public function getOpeningBalance(Account $account): ?TransactionJournal
+ {
+ return TransactionJournal
+ ::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->where('transactions.account_id', $account->id)
+ ->transactionTypes([TransactionType::OPENING_BALANCE])
+ ->first(['transaction_journals.*']);
+ }
}
diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php
index 7d0382ca10..de8ddf7125 100644
--- a/app/Repositories/Account/AccountRepositoryInterface.php
+++ b/app/Repositories/Account/AccountRepositoryInterface.php
@@ -37,6 +37,12 @@ use Illuminate\Support\Collection;
interface AccountRepositoryInterface
{
+ /**
+ * @param Account $account
+ * @return TransactionJournal|null
+ */
+ public function getOpeningBalance(Account $account): ?TransactionJournal;
+
/**
* Moved here from account CRUD.
*
@@ -49,7 +55,7 @@ interface AccountRepositoryInterface
/**
* Moved here from account CRUD.
*
- * @param Account $account
+ * @param Account $account
* @param Account|null $moveTo
*
* @return bool
@@ -60,7 +66,7 @@ interface AccountRepositoryInterface
* Find by account number. Is used.
*
* @param string $number
- * @param array $types
+ * @param array $types
*
* @return Account|null
*/
@@ -68,7 +74,7 @@ interface AccountRepositoryInterface
/**
* @param string $iban
- * @param array $types
+ * @param array $types
*
* @return Account|null
*/
@@ -76,7 +82,7 @@ interface AccountRepositoryInterface
/**
* @param string $name
- * @param array $types
+ * @param array $types
*
* @return Account|null
*/
@@ -149,7 +155,7 @@ interface AccountRepositoryInterface
* Return meta value for account. Null if not found.
*
* @param Account $account
- * @param string $field
+ * @param string $field
*
* @return null|string
*/
@@ -250,7 +256,7 @@ interface AccountRepositoryInterface
/**
* @param string $query
- * @param array $types
+ * @param array $types
*
* @return Collection
*/
@@ -270,7 +276,7 @@ interface AccountRepositoryInterface
/**
* @param Account $account
- * @param array $data
+ * @param array $data
*
* @return Account
*/
diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php
index 774c54438d..8603668ee9 100644
--- a/app/Repositories/Journal/JournalRepository.php
+++ b/app/Repositories/Journal/JournalRepository.php
@@ -791,4 +791,19 @@ class JournalRepository implements JournalRepositoryInterface
return $journal;
}
+
+ /**
+ * Get all transaction journals with a specific type, regardless of user.
+ *
+ * @param array $types
+ * @return Collection
+ */
+ public function getAllJournals(array $types): Collection
+ {
+ return TransactionJournal
+ ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
+ ->whereIn('transaction_types.type', $types)
+ ->with(['user', 'transactionType', 'transactionCurrency', 'transactions', 'transactions.account'])
+ ->get(['transaction_journals.*']);
+ }
}
diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php
index 6d4825fd2b..44ab15720f 100644
--- a/app/Repositories/Journal/JournalRepositoryInterface.php
+++ b/app/Repositories/Journal/JournalRepositoryInterface.php
@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon;
-use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
@@ -41,11 +40,20 @@ use Illuminate\Support\MessageBag;
*/
interface JournalRepositoryInterface
{
+
+ /**
+ * Get all transaction journals with a specific type, regardless of user.
+ *
+ * @param array $types
+ * @return Collection
+ */
+ public function getAllJournals(array $types): Collection;
+
/**
* @param TransactionJournal $journal
- * @param TransactionType $type
- * @param Account $source
- * @param Account $destination
+ * @param TransactionType $type
+ * @param Account $source
+ * @param Account $destination
*
* @return MessageBag
*/
@@ -157,7 +165,7 @@ interface JournalRepositoryInterface
* otherwise look for meta field and return that one.
*
* @param TransactionJournal $journal
- * @param null|string $field
+ * @param null|string $field
*
* @return string
*/
@@ -208,7 +216,7 @@ interface JournalRepositoryInterface
* Return Carbon value of a meta field (or NULL).
*
* @param TransactionJournal $journal
- * @param string $field
+ * @param string $field
*
* @return null|Carbon
*/
@@ -218,7 +226,7 @@ interface JournalRepositoryInterface
* Return string value of a meta date (or NULL).
*
* @param TransactionJournal $journal
- * @param string $field
+ * @param string $field
*
* @return null|string
*/
@@ -228,7 +236,7 @@ interface JournalRepositoryInterface
* Return value of a meta field (or NULL).
*
* @param TransactionJournal $journal
- * @param string $field
+ * @param string $field
*
* @return null|string
*/
@@ -301,7 +309,7 @@ interface JournalRepositoryInterface
/**
* @param TransactionJournal $journal
- * @param int $order
+ * @param int $order
*
* @return bool
*/
@@ -316,7 +324,7 @@ interface JournalRepositoryInterface
* Update budget for a journal.
*
* @param TransactionJournal $journal
- * @param int $budgetId
+ * @param int $budgetId
*
* @return TransactionJournal
*/
@@ -326,7 +334,7 @@ interface JournalRepositoryInterface
* Update category for a journal.
*
* @param TransactionJournal $journal
- * @param string $category
+ * @param string $category
*
* @return TransactionJournal
*/
@@ -336,7 +344,7 @@ interface JournalRepositoryInterface
* Update tag(s) for a journal.
*
* @param TransactionJournal $journal
- * @param array $tags
+ * @param array $tags
*
* @return TransactionJournal
*/
diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php
index 27824c5928..fe40e893f8 100644
--- a/app/Validation/FireflyValidator.php
+++ b/app/Validation/FireflyValidator.php
@@ -391,10 +391,10 @@ class FireflyValidator extends Validator
public function validateUniqueAccountForUser($attribute, $value, $parameters): bool
{
// because a user does not have to be logged in (tests and what-not).
+
if (!auth()->check()) {
return $this->validateAccountAnonymously();
}
-
if (isset($this->data['what'])) {
return $this->validateByAccountTypeString($value, $parameters, $this->data['what']);
}
@@ -409,7 +409,8 @@ class FireflyValidator extends Validator
return $this->validateByAccountId($value);
}
- return false;
+ // without type, just try to validate the name.
+ return $this->validateByAccountName($value);
}
/**
@@ -588,6 +589,7 @@ class FireflyValidator extends Validator
$set = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore)->get();
/** @var Account $entry */
foreach ($set as $entry) {
+ // TODO no longer need to loop like this.
if ($entry->name === $value) {
return false;
}
@@ -598,7 +600,7 @@ class FireflyValidator extends Validator
/**
* @param string $value
- * @param array $parameters
+ * @param array $parameters
* @param string $type
*
* @return bool
@@ -627,4 +629,13 @@ class FireflyValidator extends Validator
return true;
}
+
+ /**
+ * @param string $value
+ * @return bool
+ */
+ private function validateByAccountName(string $value): bool
+ {
+ return auth()->user()->accounts()->where('name', $value)->count() === 0;
+ }
}
diff --git a/config/app.php b/config/app.php
index b0215adb1f..f95262a3b1 100644
--- a/config/app.php
+++ b/config/app.php
@@ -19,7 +19,9 @@
* along with Firefly III. If not, see .
*/
+
declare(strict_types=1);
+use FireflyIII\Providers\ImportServiceProvider;
return [
@@ -96,6 +98,7 @@ return [
FireflyIII\Providers\TagServiceProvider::class,
FireflyIII\Providers\AdminServiceProvider::class,
FireflyIII\Providers\RecurringServiceProvider::class,
+ ImportServiceProvider::class,
],
diff --git a/database/seeds/LinkTypeSeeder.php b/database/seeds/LinkTypeSeeder.php
index 47fecbbd7c..01a23cdfe4 100644
--- a/database/seeds/LinkTypeSeeder.php
+++ b/database/seeds/LinkTypeSeeder.php
@@ -46,10 +46,11 @@ class LinkTypeSeeder extends Seeder
'outward' => '(partially) refunds',
'editable' => false,
],
- ['name' => 'Paid',
- 'inward' => 'is (partially) paid for by',
- 'outward' => '(partially) pays for',
- 'editable' => false,
+ [
+ 'name' => 'Paid',
+ 'inward' => 'is (partially) paid for by',
+ 'outward' => '(partially) pays for',
+ 'editable' => false,
],
[
'name' => 'Reimbursement',
diff --git a/phpunit.coverage.specific.xml b/phpunit.coverage.specific.xml
index da7d5b821d..117e800802 100644
--- a/phpunit.coverage.specific.xml
+++ b/phpunit.coverage.specific.xml
@@ -32,16 +32,24 @@
-
- ./tests/Feature
-
-
-
- ./tests/Unit
-
+
./tests/Api
+
+
+ ./tests/Unit/Console
+
+
+
+
diff --git a/phpunit.coverage.xml b/phpunit.coverage.xml
index 60bc506b70..e5cbcc7df2 100644
--- a/phpunit.coverage.xml
+++ b/phpunit.coverage.xml
@@ -32,16 +32,24 @@
-
- ./tests/Feature
-
-
-
- ./tests/Unit
-
+
./tests/Api
+
+
+ ./tests/Unit/Console
+
+
+
+
diff --git a/phpunit.xml b/phpunit.xml
index 736d18a486..697a9238d2 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -32,16 +32,24 @@
-
- ./tests/Feature
-
-
-
- ./tests/Unit
-
+
./tests/Api
+
+
+ ./tests/Unit/Console
+
+
+
+
diff --git a/tests/TestCase.php b/tests/TestCase.php
index b0ecd8cecd..68af6f98cf 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -333,6 +333,9 @@ abstract class TestCase extends BaseTestCase
DB::raw('COUNT(transaction_journal_id) as ct'),
]
)->first();
+ if(null === $result) {
+ throw new FireflyException(sprintf('Cannot find suitable %s to use.', $type));
+ }
return TransactionJournal::find((int)$result->transaction_journal_id);
}
diff --git a/tests/Unit/Console/Commands/Correction/CreateAccessTokensTest.php b/tests/Unit/Console/Commands/Correction/CreateAccessTokensTest.php
new file mode 100644
index 0000000000..384b22c175
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/CreateAccessTokensTest.php
@@ -0,0 +1,96 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\Preference;
+use FireflyIII\Repositories\User\UserRepositoryInterface;
+use Illuminate\Support\Collection;
+use Log;
+use Mockery;
+use Preferences;
+use Tests\TestCase;
+
+/**
+ * Class CreateAccessTokensTest
+ */
+class CreateAccessTokensTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\CreateAccessTokens
+ */
+ public function testHandle(): void
+ {
+ $users = new Collection([$this->user()]);
+ $repository = $this->mock(UserRepositoryInterface::class);
+
+ // mock calls:
+ $repository->shouldReceive('all')->atLeast()->once()->andReturn($users);
+
+ // mock preferences thing:
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'access_token', null])
+ ->once()->andReturn(null);
+
+ // null means user object will generate one and store it.
+ Preferences::shouldReceive('setForUser')->withArgs([Mockery::any(), 'access_token', Mockery::any()])
+ ->once();
+
+
+ $this->artisan('firefly-iii:create-access-tokens')
+ ->expectsOutput(sprintf('Generated access token for user %s', $this->user()->email))
+ ->assertExitCode(0);
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\CreateAccessTokens
+ */
+ public function testHandlePrefExists(): void
+ {
+ $users = new Collection([$this->user()]);
+ $repository = $this->mock(UserRepositoryInterface::class);
+
+ // mock calls:
+ $repository->shouldReceive('all')->atLeast()->once()->andReturn($users);
+
+ // mock preferences thing:
+ $preference = new Preference;
+ $preference->data = '123';
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'access_token', null])
+ ->once()->andReturn($preference);
+
+ // null means user object will generate one and store it.
+ Preferences::shouldNotReceive('setForUser');
+
+ $this->artisan('firefly-iii:create-access-tokens')
+ ->expectsOutput('All access tokens OK!')
+ ->assertExitCode(0);
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/CreateLinkTypesTest.php b/tests/Unit/Console/Commands/Correction/CreateLinkTypesTest.php
new file mode 100644
index 0000000000..db04b21796
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/CreateLinkTypesTest.php
@@ -0,0 +1,78 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\LinkType;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class CreateLinkTypesTest
+ */
+class CreateLinkTypesTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\CreateLinkTypes
+ */
+ public function testHandle(): void
+ {
+ // delete all other link types:
+ LinkType::whereNotIn('name', ['Related', 'Refund', 'Paid', 'Reimbursement'])->forceDelete();
+
+ // delete link type:
+ LinkType::where('name', 'Reimbursement')->forceDelete();
+ $this->assertCount(3, LinkType::get());
+
+ // run command, expect output:
+ $this->artisan('firefly-iii:create-link-types')
+ ->expectsOutput('Created missing link type "Reimbursement"')
+ ->assertExitCode(0);
+
+ $this->assertCount(4, LinkType::get());
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\CreateLinkTypes
+ */
+ public function testHandleNothing(): void
+ {
+ $this->assertCount(4, LinkType::get());
+
+ // run command, expect output:
+ $this->artisan('firefly-iii:create-link-types')
+ ->expectsOutput('All link types OK!')
+ ->assertExitCode(0);
+
+ $this->assertCount(4, LinkType::get());
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/DeleteEmptyGroupsTest.php b/tests/Unit/Console/Commands/Correction/DeleteEmptyGroupsTest.php
new file mode 100644
index 0000000000..4d07dc7078
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/DeleteEmptyGroupsTest.php
@@ -0,0 +1,70 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\TransactionGroup;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class DeleteEmptyGroupsTest
+ */
+class DeleteEmptyGroupsTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\DeleteEmptyGroups
+ */
+ public function testHandle(): void
+ {
+ // assume there are no empty groups..
+ $this->artisan('firefly-iii:delete-empty-groups')
+ ->expectsOutput('No empty transaction groups.')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\DeleteEmptyGroups
+ */
+ public function testHandleWithGroup(): void
+ {
+ // create new group:
+ $group = TransactionGroup::create(['user_id' => 1]);
+
+ // command should delete it.
+ $this->artisan('firefly-iii:delete-empty-groups')
+ ->expectsOutput('Deleted 1 empty transaction group(s).')
+ ->assertExitCode(0);
+
+ // should not be able to find it:
+ $this->assertCount(0, TransactionGroup::where('id', $group->id)->whereNull('deleted_at')->get());
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/DeleteEmptyJournalsTest.php b/tests/Unit/Console/Commands/Correction/DeleteEmptyJournalsTest.php
new file mode 100644
index 0000000000..85385a268e
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/DeleteEmptyJournalsTest.php
@@ -0,0 +1,116 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\Transaction;
+use FireflyIII\Models\TransactionJournal;
+use Log;
+use Tests\TestCase;
+
+class DeleteEmptyJournalsTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\DeleteEmptyJournals
+ */
+ public function testHandle(): void
+ {
+ // assume there are no empty journals or uneven journals
+ $this->artisan('firefly-iii:delete-empty-journals')
+ ->expectsOutput('No uneven transaction journals.')
+ ->expectsOutput('No empty transaction journals.')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\DeleteEmptyJournals
+ */
+ public function testHandleEmptyJournals(): void
+ {
+ // create empty journal:
+ $journal = TransactionJournal::create(
+ [
+ 'user_id' => 1,
+ 'transaction_currency_id' => 1,
+ 'transaction_type_id' => 1,
+ 'description' => 'Hello',
+ 'tag_count' => 0,
+ 'date' => '2019-01-01',
+ ]
+ );
+ $this->artisan('firefly-iii:delete-empty-journals')
+ ->expectsOutput('No uneven transaction journals.')
+ ->expectsOutput(sprintf('Deleted empty transaction journal #%d', $journal->id))
+ ->assertExitCode(0);
+
+ // verify its indeed gone
+ $this->assertCount(0, TransactionJournal::where('id', $journal->id)->whereNull('deleted_at')->get());
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\DeleteEmptyJournals
+ */
+ public function testHandleUnevenJournals(): void
+ {
+ // create empty journal:
+ $journal = TransactionJournal::create(
+ [
+ 'user_id' => 1,
+ 'transaction_currency_id' => 1,
+ 'transaction_type_id' => 1,
+ 'description' => 'Hello',
+ 'tag_count' => 0,
+ 'date' => '2019-01-01',
+ ]
+ );
+
+ // link empty transaction
+ $transaction = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => 1,
+ 'amount' => '5',
+ ]
+ );
+
+
+ $this->artisan('firefly-iii:delete-empty-journals')
+ ->expectsOutput(sprintf('Deleted transaction journal #%d because it had an uneven number of transactions.', $journal->id))
+ ->expectsOutput('No empty transaction journals.')
+ ->assertExitCode(0);
+
+ // verify both are gone
+ $this->assertCount(0, TransactionJournal::where('id', $journal->id)->whereNull('deleted_at')->get());
+ $this->assertCount(0, Transaction::where('id', $transaction->id)->whereNull('deleted_at')->get());
+ }
+
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/DeleteOrphanedTransactionsTest.php b/tests/Unit/Console/Commands/Correction/DeleteOrphanedTransactionsTest.php
new file mode 100644
index 0000000000..d9ce06b711
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/DeleteOrphanedTransactionsTest.php
@@ -0,0 +1,144 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\Account;
+use FireflyIII\Models\Transaction;
+use FireflyIII\Models\TransactionJournal;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class DeleteOrphanedTransactionsTest
+ */
+class DeleteOrphanedTransactionsTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\DeleteOrphanedTransactions
+ */
+ public function testHandle(): void
+ {
+ // assume there are no orphaned transactions.
+ $this->artisan('firefly-iii:delete-orphaned-transactions')
+ ->expectsOutput('No orphaned transactions.')
+ ->expectsOutput('No orphaned accounts.')
+ ->assertExitCode(0);
+ }
+
+ /**
+ *
+ */
+ public function testHandleOrphanedAccounts(): void
+ {
+
+ // create deleted account:
+ $account = Account::create(
+ [
+ 'user_id' => 1,
+ 'name' => 'Some account',
+ 'account_type_id' => 1,
+
+ ]
+ );
+ $account->delete();
+
+ // create NOT deleted journal + transaction.
+ $journal = TransactionJournal::create(
+ [
+ 'user_id' => 1,
+ 'transaction_currency_id' => 1,
+ 'transaction_type_id' => 1,
+ 'description' => 'Hello',
+ 'tag_count' => 0,
+ 'date' => '2019-01-01',
+ ]
+ );
+
+ $transaction = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $account->id,
+ 'amount' => '5',
+ ]
+ );
+
+ $this->artisan('firefly-iii:delete-orphaned-transactions')
+ ->expectsOutput('No orphaned transactions.')
+ ->expectsOutput(sprintf('Deleted transaction journal #%d because account #%d was already deleted.',
+ $journal->id, $account->id))
+ ->assertExitCode(0);
+
+ // verify bad objects are gone.
+ $this->assertCount(0, Transaction::where('id', $transaction->id)->whereNull('deleted_at')->get());
+ $this->assertCount(0, TransactionJournal::where('id', $journal->id)->whereNull('deleted_at')->get());
+ $this->assertCount(0, Account::where('id', $account->id)->whereNull('deleted_at')->get());
+
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\DeleteOrphanedTransactions
+ */
+ public function testHandleOrphanedTransactions(): void
+ {
+ // create deleted journal:
+ $journal = TransactionJournal::create(
+ [
+ 'user_id' => 1,
+ 'transaction_currency_id' => 1,
+ 'transaction_type_id' => 1,
+ 'description' => 'Hello',
+ 'tag_count' => 0,
+ 'date' => '2019-01-01',
+ ]
+ );
+ $journal->delete();
+
+ // create NOT deleted transaction.
+ $transaction = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => 1,
+ 'amount' => '5',
+ ]
+ );
+
+ $this->artisan('firefly-iii:delete-orphaned-transactions')
+ ->expectsOutput(sprintf('Transaction #%d (part of deleted transaction journal #%d) has been deleted as well.',
+ $transaction->id, $journal->id))
+ ->expectsOutput('No orphaned accounts.')
+ ->assertExitCode(0);
+
+ // verify objects are gone.
+ $this->assertCount(0, TransactionJournal::where('id', $journal->id)->whereNull('deleted_at')->get());
+ $this->assertCount(0, Transaction::where('id', $transaction->id)->whereNull('deleted_at')->get());
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/DeleteZeroAmountTest.php b/tests/Unit/Console/Commands/Correction/DeleteZeroAmountTest.php
new file mode 100644
index 0000000000..946a03672e
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/DeleteZeroAmountTest.php
@@ -0,0 +1,91 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\Transaction;
+use FireflyIII\Models\TransactionJournal;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class DeleteZeroAmountTest
+ */
+class DeleteZeroAmountTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\DeleteZeroAmount
+ */
+ public function testHandle(): void
+ {
+ // assume there are no transactions with a zero amount.
+ $this->artisan('firefly-iii:delete-zero-amount')
+ ->expectsOutput('No zero-amount transaction journals.')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\DeleteZeroAmount
+ */
+ public function testHandleTransactions(): void
+ {
+ $account = $this->getRandomAsset();
+ // create NOT deleted journal + transaction.
+ $journal = TransactionJournal::create(
+ [
+ 'user_id' => 1,
+ 'transaction_currency_id' => 1,
+ 'transaction_type_id' => 1,
+ 'description' => 'Hello',
+ 'tag_count' => 0,
+ 'date' => '2019-01-01',
+ ]
+ );
+
+ $transaction = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $account->id,
+ 'amount' => '0',
+ ]
+ );
+
+
+ // assume there are no transactions with a zero amount.
+ $this->artisan('firefly-iii:delete-zero-amount')
+ ->expectsOutput(sprintf('Deleted transaction journal #%d because the amount is zero (0.00).', $journal->id))
+ ->assertExitCode(0);
+
+ // verify objects are gone.
+ $this->assertCount(0, Transaction::where('id', $transaction->id)->whereNull('deleted_at')->get());
+ $this->assertCount(0, TransactionJournal::where('id', $journal->id)->whereNull('deleted_at')->get());
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/EnableCurrenciesTest.php b/tests/Unit/Console/Commands/Correction/EnableCurrenciesTest.php
new file mode 100644
index 0000000000..6c29cb5b73
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/EnableCurrenciesTest.php
@@ -0,0 +1,82 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\BudgetLimit;
+use FireflyIII\Models\TransactionCurrency;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class EnableCurrenciesTest
+ */
+class EnableCurrenciesTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\EnableCurrencies
+ */
+ public function testHandle(): void
+ {
+ // assume the current database is intact.
+ $count = TransactionCurrency::where('enabled', 1)->count();
+
+ $this->artisan('firefly-iii:enable-currencies')
+ ->expectsOutput('All currencies are correctly enabled or disabled.')
+ ->assertExitCode(0);
+
+
+ $this->assertCount($count, TransactionCurrency::where('enabled', 1)->get());
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\EnableCurrencies
+ */
+ public function testHandleDisabled(): void
+ {
+ // find a disabled currency, update a budget limit with it.
+ $currency = TransactionCurrency::where('enabled', 0)->first();
+ /** @var BudgetLimit $budgetLimit */
+ $budgetLimit = BudgetLimit::inRandomOrder()->first();
+ $budgetLimit->transaction_currency_id = $currency->id;
+ $budgetLimit->save();
+
+ // assume the current database is intact.
+ $count = TransactionCurrency::where('enabled', 1)->count();
+ $this->artisan('firefly-iii:enable-currencies')
+ ->expectsOutput(sprintf('%d were (was) still disabled. This has been corrected.', 1))
+ ->assertExitCode(0);
+
+ // assume its been enabled.
+ $this->assertCount($count + 1, TransactionCurrency::where('enabled', 1)->get());
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/FixAccountTypesTest.php b/tests/Unit/Console/Commands/Correction/FixAccountTypesTest.php
new file mode 100644
index 0000000000..7d6b9d0cea
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/FixAccountTypesTest.php
@@ -0,0 +1,371 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Factory\AccountFactory;
+use FireflyIII\Models\AccountType;
+use FireflyIII\Models\Transaction;
+use FireflyIII\Models\TransactionJournal;
+use FireflyIII\Models\TransactionType;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class FixAccountTypesTest
+ */
+class FixAccountTypesTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\FixAccountTypes
+ */
+ public function testHandleUneven(): void
+ {
+ $this->mock(AccountFactory::class);
+ $source = $this->user()->accounts()->where('name', 'Another DUO Student loans')->first();
+ $type = TransactionType::where('type', TransactionType::WITHDRAWAL)->first();
+ $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' => $source->id,
+ 'amount' => '-10',
+ ]
+ );
+
+ // assume there's nothing to fix.
+ $this->artisan('firefly-iii:fix-account-types')
+ ->expectsOutput(sprintf('Cannot inspect transaction journal #%d because it has 1 transaction(s) instead of 2.', $journal->id))
+ ->assertExitCode(0);
+ $one->forceDelete();
+ $journal->forceDelete();
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\FixAccountTypes
+ */
+ public function testHandle(): void
+ {
+ $this->mock(AccountFactory::class);
+
+ // assume there's nothing to fix.
+ $this->artisan('firefly-iii:fix-account-types')
+ ->expectsOutput('All account types are OK!')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * Try to fix a withdrawal that goes from a loan to another loan.
+ *
+ * @covers \FireflyIII\Console\Commands\Correction\FixAccountTypes
+ */
+ public function testHandleWithdrawalLoanLoan(): void
+ {
+ $this->mock(AccountFactory::class);
+ $source = $this->user()->accounts()->where('name', 'Another DUO Student loans')->first();
+ $destination = $this->user()->accounts()->where('name', 'DUO Student loans')->first();
+ $type = TransactionType::where('type', TransactionType::WITHDRAWAL)->first();
+ $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' => $source->id,
+ 'amount' => '-10',
+ ]
+ );
+ $two = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $destination->id,
+ 'amount' => '10',
+ ]
+ );
+
+
+ $this->artisan('firefly-iii:fix-account-types')
+ ->expectsOutput(sprintf('The source account of %s #%d cannot be of type "%s".', $type->type, $journal->id, 'Loan'))
+ ->expectsOutput(sprintf('The destination account of %s #%d cannot be of type "%s".', $type->type, $journal->id, 'Loan'))
+ ->expectsOutput('Acted on 1 transaction(s)!')
+ ->assertExitCode(0);
+
+ // since system cant handle this problem, dont look for changed transactions.
+
+
+ $one->forceDelete();
+ $two->forceDelete();
+ $journal->forceDelete();
+ }
+
+ /**
+ * Transferring from an asset to a loan should be a withdrawal, not a transfer
+ */
+ public function testHandleTransferAssetLoan(): void
+ {
+ $this->mock(AccountFactory::class);
+ $source = $this->getRandomAsset();
+ $destination = $this->user()->accounts()->where('name', 'DUO Student loans')->first();
+ $type = TransactionType::where('type', TransactionType::TRANSFER)->first();
+ $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first();
+ $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' => $source->id,
+ 'amount' => '-10',
+ ]
+ );
+ $two = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $destination->id,
+ 'amount' => '10',
+ ]
+ );
+
+ $this->artisan('firefly-iii:fix-account-types')
+ ->expectsOutput(sprintf('Converted transaction #%d from a transfer to a withdrawal.', $journal->id))
+ ->expectsOutput('Acted on 1 transaction(s)!')
+ ->assertExitCode(0);
+
+ // verify the change has been made.
+ $this->assertCount(1, TransactionJournal::where('id', $journal->id)->where('transaction_type_id', $withdrawal->id)->get());
+ $this->assertCount(0, TransactionJournal::where('id', $journal->id)->where('transaction_type_id', $type->id)->get());
+
+ $one->forceDelete();
+ $two->forceDelete();
+ $journal->forceDelete();
+ }
+
+ /**
+ * Transferring from a loan to an asset should be a deposit, not a transfer
+ */
+ public function testHandleTransferLoanAsset(): void
+ {
+ $this->mock(AccountFactory::class);
+ $source = $this->user()->accounts()->where('name', 'DUO Student loans')->first();
+ $destination = $this->getRandomAsset();
+ $type = TransactionType::where('type', TransactionType::TRANSFER)->first();
+ $deposit = TransactionType::where('type', TransactionType::DEPOSIT)->first();
+ $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' => $source->id,
+ 'amount' => '-10',
+ ]
+ );
+ $two = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $destination->id,
+ 'amount' => '10',
+ ]
+ );
+
+ $this->artisan('firefly-iii:fix-account-types')
+ ->expectsOutput(sprintf('Converted transaction #%d from a transfer to a deposit.', $journal->id))
+ ->expectsOutput('Acted on 1 transaction(s)!')
+ ->assertExitCode(0);
+
+ // verify the change has been made.
+ $this->assertCount(1, TransactionJournal::where('id', $journal->id)->where('transaction_type_id', $deposit->id)->get());
+ $this->assertCount(0, TransactionJournal::where('id', $journal->id)->where('transaction_type_id', $type->id)->get());
+
+ $one->forceDelete();
+ $two->forceDelete();
+ $journal->forceDelete();
+ }
+
+ /**
+ * Withdrawal with a revenue account as a destination must be converted.
+ */
+ public function testHandleWithdrawalAssetRevenue(): void
+ {
+ $source = $this->getRandomAsset();
+ $destination = $this->getRandomRevenue();
+ $newDestination = $this->getRandomExpense();
+ $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first();
+ $journal = TransactionJournal::create(
+ [
+ 'user_id' => 1,
+ 'transaction_currency_id' => 1,
+ 'transaction_type_id' => $withdrawal->id,
+ 'description' => 'Test',
+ 'tag_count' => 0,
+ 'date' => '2019-01-01',
+ ]
+ );
+ $one = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $source->id,
+ 'amount' => '-10',
+ ]
+ );
+ $two = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $destination->id,
+ 'amount' => '10',
+ ]
+ );
+
+ $this->assertCount(0, Transaction::where('id', $two->id)->where('account_id', $newDestination->id)->get());
+ $this->assertCount(1, Transaction::where('id', $two->id)->where('account_id', $destination->id)->get());
+
+ // mock stuff
+ $factory = $this->mock(AccountFactory::class);
+ $factory->shouldReceive('setUser')->atLeast()->once();
+ $factory->shouldReceive('findOrCreate')
+ ->withArgs([$destination->name, AccountType::EXPENSE])
+ ->atLeast()->once()->andReturn($newDestination);
+
+ // Transaction journal #137, destination account changed from #1 ("Checking Account") to #29 ("Land lord").
+ $this->artisan('firefly-iii:fix-account-types')
+ ->expectsOutput(
+ sprintf('Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").',
+ $journal->id,
+ $destination->id, $destination->name,
+ $newDestination->id, $newDestination->name
+ ))
+ ->expectsOutput('Acted on 1 transaction(s)!')
+ ->assertExitCode(0);
+
+ // verify the change has been made
+ $this->assertCount(1, Transaction::where('id', $two->id)->where('account_id', $newDestination->id)->get());
+ $this->assertCount(0, Transaction::where('id', $two->id)->where('account_id', $destination->id)->get());
+
+ $one->forceDelete();
+ $two->forceDelete();
+ $journal->forceDelete();
+ }
+
+ /**
+ * Deposit with an expense account as a source instead of a revenue account must be converted.
+ */
+ public function testHandleDepositAssetExpense(): void
+ {
+ $source = $this->getRandomExpense();
+ $newSource = $this->getRandomRevenue();
+ $destination = $this->getRandomAsset();
+
+ $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first();
+ $deposit = TransactionType::where('type', TransactionType::DEPOSIT)->first();
+ $journal = TransactionJournal::create(
+ [
+ 'user_id' => 1,
+ 'transaction_currency_id' => 1,
+ 'transaction_type_id' => $deposit->id,
+ 'description' => 'Test',
+ 'tag_count' => 0,
+ 'date' => '2019-01-01',
+ ]
+ );
+ $one = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $source->id,
+ 'amount' => '-10',
+ ]
+ );
+ $two = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $destination->id,
+ 'amount' => '10',
+ ]
+ );
+
+ $this->assertCount(0, Transaction::where('id', $one->id)->where('account_id', $newSource->id)->get());
+ $this->assertCount(1, Transaction::where('id', $one->id)->where('account_id', $source->id)->get());
+
+ // mock stuff
+ $factory = $this->mock(AccountFactory::class);
+ $factory->shouldReceive('setUser')->atLeast()->once();
+ $factory->shouldReceive('findOrCreate')
+ ->withArgs([$source->name, AccountType::REVENUE])
+ ->atLeast()->once()->andReturn($newSource);
+
+ // Transaction journal #137, destination account changed from #1 ("Checking Account") to #29 ("Land lord").
+ $this->artisan('firefly-iii:fix-account-types')
+ ->expectsOutput(
+ sprintf('Transaction journal #%d, source account changed from #%d ("%s") to #%d ("%s").',
+ $journal->id,
+ $destination->id, $destination->name,
+ $newSource->id, $newSource->name
+ ))
+ ->expectsOutput('Acted on 1 transaction(s)!')
+ ->assertExitCode(0);
+
+ $this->assertCount(1, Transaction::where('id', $one->id)->where('account_id', $newSource->id)->get());
+ $this->assertCount(0, Transaction::where('id', $one->id)->where('account_id', $source->id)->get());
+
+ $one->forceDelete();
+ $two->forceDelete();
+ $journal->forceDelete();
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/FixPiggiesTest.php b/tests/Unit/Console/Commands/Correction/FixPiggiesTest.php
new file mode 100644
index 0000000000..6f71a03ab6
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/FixPiggiesTest.php
@@ -0,0 +1,130 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\PiggyBank;
+use FireflyIII\Models\PiggyBankEvent;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class FixPiggiesTest
+ */
+class FixPiggiesTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * Null event.
+ *
+ * @covers \FireflyIII\Console\Commands\Correction\FixPiggies
+ */
+ public function testHandleNull(): void
+ {
+ /** @var PiggyBank $piggy */
+ $piggy = $this->user()->piggyBanks()->inRandomOrder()->first();
+
+ // create event to trigger console commands.
+ $event = PiggyBankEvent::create(
+ [
+ 'piggy_bank_id' => $piggy->id,
+ 'date' => '2019-01-01',
+ 'amount' => 5,
+ ]
+ );
+
+ // assume there's nothing to fix.
+ $this->artisan('firefly-iii:fix-piggies')
+ ->expectsOutput('All piggy bank events are correct.')
+ ->assertExitCode(0);
+ $event->forceDelete();
+ }
+
+ /**
+ * Withdrawal instead of transfer
+ *
+ * @covers \FireflyIII\Console\Commands\Correction\FixPiggies
+ */
+ public function testHandleBadJournal(): void
+ {
+ /** @var PiggyBank $piggy */
+ $piggy = $this->user()->piggyBanks()->inRandomOrder()->first();
+ $withdrawal = $this->getRandomWithdrawal();
+ // create event to trigger console commands.
+ $event = PiggyBankEvent::create(
+ [
+ 'piggy_bank_id' => $piggy->id,
+ 'date' => '2019-01-01',
+ 'amount' => 5,
+ 'transaction_journal_id' => $withdrawal->id,
+ ]
+ );
+
+ // assume there's nothing to fix.
+ $this->artisan('firefly-iii:fix-piggies')
+ ->expectsOutput(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $piggy->id))
+ ->expectsOutput('Fixed 1 piggy bank event(s).')
+ ->assertExitCode(0);
+
+ // verify update
+ $this->assertCount(0, PiggyBankEvent::where('id', $event->id)->where('transaction_journal_id', $withdrawal->id)->get());
+ }
+
+ /**
+ * Withdrawal instead of transfer
+ *
+ * @covers \FireflyIII\Console\Commands\Correction\FixPiggies
+ */
+ public function testHandleDeletedJournal(): void
+ {
+ /** @var PiggyBank $piggy */
+ $piggy = $this->user()->piggyBanks()->inRandomOrder()->first();
+ $transfer = $this->getRandomTransfer();
+ $event = PiggyBankEvent::create(
+ [
+ 'piggy_bank_id' => $piggy->id,
+ 'date' => '2019-01-01',
+ 'amount' => 5,
+ 'transaction_journal_id' => $transfer->id,
+ ]
+ );
+ $transfer->deleted_at = '2019-01-01 12:00:00';
+ $transfer->save();
+ $transfer->refresh();
+
+ $this->artisan('firefly-iii:fix-piggies')
+ ->expectsOutput('Fixed 1 piggy bank event(s).')
+ ->assertExitCode(0);
+
+ // verify update
+ $this->assertCount(0, PiggyBankEvent::where('id', $event->id)->where('transaction_journal_id', $transfer->id)->get());
+ $event->forceDelete();
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/FixUnevenAmountTest.php b/tests/Unit/Console/Commands/Correction/FixUnevenAmountTest.php
new file mode 100644
index 0000000000..d5aa891a7a
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/FixUnevenAmountTest.php
@@ -0,0 +1,101 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\Transaction;
+use FireflyIII\Models\TransactionJournal;
+use FireflyIII\Models\TransactionType;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class FixUnevenAmountTest
+ */
+class FixUnevenAmountTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\FixUnevenAmount
+ */
+ public function testHandle(): void
+ {
+ // assume there's nothing to fix.
+ $this->artisan('firefly-iii:fix-uneven-amount')
+ ->expectsOutput('Amount integrity OK!')
+ ->assertExitCode(0);
+
+ // dont verify anything
+ }
+
+ /**
+ * Create uneven journal
+ * @covers \FireflyIII\Console\Commands\Correction\FixUnevenAmount
+ */
+ public function testHandleUneven(): void
+ {
+ $asset = $this->getRandomAsset();
+ $expense = $this->getRandomExpense();
+ $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first();
+ $journal = TransactionJournal::create(
+ [
+ 'user_id' => 1,
+ 'transaction_currency_id' => 1,
+ 'transaction_type_id' => $withdrawal->id,
+ 'description' => 'Test',
+ 'tag_count' => 0,
+ 'date' => '2019-01-01',
+ ]
+ );
+ $one = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $asset->id,
+ 'amount' => '-10',
+ ]
+ );
+ $two = Transaction::create(
+ [
+ 'transaction_journal_id' => $journal->id,
+ 'account_id' => $expense->id,
+ 'amount' => '12',
+ ]
+ );
+
+ $this->artisan('firefly-iii:fix-uneven-amount')
+ ->expectsOutput(sprintf('Corrected amount in transaction journal #%d', $journal->id))
+ ->assertExitCode(0);
+
+ // verify change.
+ $this->assertCount(1, Transaction::where('id', $one->id)->where('amount', '-10')->get());
+ $this->assertCount(1, Transaction::where('id', $two->id)->where('amount', '10')->get());
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/RemoveBillsTest.php b/tests/Unit/Console/Commands/Correction/RemoveBillsTest.php
new file mode 100644
index 0000000000..ef2b5b35ac
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/RemoveBillsTest.php
@@ -0,0 +1,75 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\TransactionJournal;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class RemoveBillsTest
+ */
+class RemoveBillsTest extends TestCase
+{
+
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\RemoveBills
+ */
+ public function testHandle(): void
+ {
+ // assume there's nothing to fix.
+ $this->artisan('firefly-iii:remove-bills')
+ ->expectsOutput('All transaction journals have correct bill information.')
+ ->assertExitCode(0);
+
+ // dont verify anything
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\RemoveBills
+ */
+ public function testHandleWithdrawal(): void
+ {
+ $bill = $this->user()->bills()->first();
+ $journal = $this->getRandomDeposit();
+
+ $journal->bill_id = $bill->id;
+ $journal->save();
+
+ $this->artisan('firefly-iii:remove-bills')
+ ->expectsOutput(sprintf('Transaction journal #%d should not be linked to bill #%d.', $journal->id, $bill->id))
+ ->assertExitCode(0);
+
+ // verify change
+ $this->assertCount(0, TransactionJournal::where('id', $journal->id)->whereNotNull('bill_id')->get());
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/RenameMetaFieldsTest.php b/tests/Unit/Console/Commands/Correction/RenameMetaFieldsTest.php
new file mode 100644
index 0000000000..95a4db4118
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/RenameMetaFieldsTest.php
@@ -0,0 +1,76 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use FireflyIII\Models\TransactionJournalMeta;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class RenameMetaFieldsTest
+ */
+class RenameMetaFieldsTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\RenameMetaFields
+ */
+ public function testHandle(): void
+ {
+ $this->artisan('firefly-iii:rename-meta-fields')
+ ->expectsOutput('All meta fields are correct.')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\RenameMetaFields
+ */
+ public function testHandleFixed(): void
+ {
+ $withdrawal = $this->getRandomWithdrawal();
+ $entry = TransactionJournalMeta::create(
+ [
+ 'transaction_journal_id' => $withdrawal->id,
+ 'name' => 'importHashV2',
+ 'data' => 'Fake data',
+
+ ]
+ );
+
+ $this->artisan('firefly-iii:rename-meta-fields')
+ ->expectsOutput('Renamed 1 meta field(s).')
+ ->assertExitCode(0);
+
+ // verify update
+ $this->assertCount(1, TransactionJournalMeta::where('id', $entry->id)->where('name', 'import_hash_v2')->get());
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Correction/TransferBudgetsTest.php b/tests/Unit/Console/Commands/Correction/TransferBudgetsTest.php
new file mode 100644
index 0000000000..5512b2a86e
--- /dev/null
+++ b/tests/Unit/Console/Commands/Correction/TransferBudgetsTest.php
@@ -0,0 +1,71 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Correction;
+
+
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class TransferBudgetsTest
+ */
+class TransferBudgetsTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\TransferBudgets
+ */
+ public function testHandle(): void
+ {
+ $this->artisan('firefly-iii:fix-transfer-budgets')
+ ->expectsOutput('No invalid budget/journal entries.')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Correction\TransferBudgets
+ */
+ public function testHandleBudget(): void
+ {
+ $deposit = $this->getRandomDeposit();
+ $budget = $this->user()->budgets()->inRandomOrder()->first();
+
+ $deposit->budgets()->save($budget);
+
+ $this->artisan('firefly-iii:fix-transfer-budgets')
+ ->expectsOutput(sprintf('Transaction journal #%d is a %s, so has no longer a budget.', $deposit->id, $deposit->transactionType->type))
+ ->expectsOutput('Corrected 1 invalid budget/journal entries (entry).')
+ ->assertExitCode(0);
+
+ // verify change
+ $this->assertCount(0, $deposit->budgets()->get());
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/DecryptDatabaseTest.php b/tests/Unit/Console/Commands/DecryptDatabaseTest.php
new file mode 100644
index 0000000000..35c65bff5a
--- /dev/null
+++ b/tests/Unit/Console/Commands/DecryptDatabaseTest.php
@@ -0,0 +1,140 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands;
+
+
+use Crypt;
+use FireflyConfig;
+use FireflyIII\Models\Account;
+use FireflyIII\Models\Configuration;
+use Log;
+use Mockery;
+use Tests\TestCase;
+
+/**
+ * Class DecryptDatabaseTest
+ */
+class DecryptDatabaseTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\DecryptDatabase
+ */
+ public function testHandle(): void
+ {
+ // create encrypted account:
+ $name = 'Encrypted name';
+ $iban = 'HR1723600001101234565';
+ $account = Account::create(
+ [
+ 'user_id' => 1,
+ 'account_type_id' => 1,
+ 'name' => Crypt::encrypt($name),
+ 'iban' => Crypt::encrypt($iban),
+ ]);
+
+
+
+ FireflyConfig::shouldReceive('get')->withArgs([Mockery::any(), false])->atLeast()->once()->andReturn(null);
+ FireflyConfig::shouldReceive('set')->withArgs([Mockery::any(), true])->atLeast()->once();
+
+ $this->artisan('firefly-iii:decrypt-all')
+ ->expectsOutput('Done!')
+ ->assertExitCode(0);
+
+ $this->assertCount(1, Account::where('id', $account->id)->where('name', $name)->get());
+ $this->assertCount(1, Account::where('id', $account->id)->where('iban', $iban)->get());
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\DecryptDatabase
+ */
+ public function testHandleDecrypted(): void
+ {
+ // create encrypted account:
+ $name = 'Encrypted name';
+ $iban = 'HR1723600001101234565';
+ $encryptedName = Crypt::encrypt($name);
+ $encryptedIban = Crypt::encrypt($iban);
+ $account = Account::create(
+ [
+ 'user_id' => 1,
+ 'account_type_id' => 1,
+ 'name' => $encryptedName,
+ 'iban' => $encryptedIban,
+ ]);
+
+ // pretend its not yet decrypted.
+ $true = new Configuration;
+ $true->data = true;
+
+ FireflyConfig::shouldReceive('get')->withArgs([Mockery::any(), false])->atLeast()->once()->andReturn($true);
+
+ $this->artisan('firefly-iii:decrypt-all')
+ ->expectsOutput('Done!')
+ ->assertExitCode(0);
+
+ $this->assertCount(1, Account::where('id', $account->id)->where('name', $encryptedName)->get());
+ $this->assertCount(1, Account::where('id', $account->id)->where('iban', $encryptedIban)->get());
+ }
+
+ /**
+ * Try to decrypt data that isn't actually encrypted.
+ *
+ * @covers \FireflyIII\Console\Commands\DecryptDatabase
+ */
+ public function testHandleNotEncrypted(): void
+ {
+ // create encrypted account:
+ $name = 'Encrypted name';
+ $iban = 'HR1723600001101234565';
+ $account = Account::create(
+ [
+ 'user_id' => 1,
+ 'account_type_id' => 1,
+ 'name' => $name,
+ 'iban' => $iban,
+ ]);
+
+ // pretend its not yet decrypted.
+ $true = new Configuration;
+ $true->data = true;
+
+ FireflyConfig::shouldReceive('get')->withArgs([Mockery::any(), false])->atLeast()->once()->andReturn($true);
+
+ $this->artisan('firefly-iii:decrypt-all')
+ ->expectsOutput('Done!')
+ ->assertExitCode(0);
+
+ $this->assertCount(1, Account::where('id', $account->id)->where('name', $name)->get());
+ $this->assertCount(1, Account::where('id', $account->id)->where('iban', $iban)->get());
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Import/CreateCSVImportTest.php b/tests/Unit/Console/Commands/Import/CreateCSVImportTest.php
new file mode 100644
index 0000000000..94e31c68c3
--- /dev/null
+++ b/tests/Unit/Console/Commands/Import/CreateCSVImportTest.php
@@ -0,0 +1,433 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Import;
+
+
+use FireflyIII\Exceptions\FireflyException;
+use FireflyIII\Import\Routine\FileRoutine;
+use FireflyIII\Import\Storage\ImportArrayStorage;
+use FireflyIII\Models\ImportJob;
+use FireflyIII\Models\Preference;
+use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
+use FireflyIII\Repositories\User\UserRepositoryInterface;
+use Illuminate\Support\MessageBag;
+use Log;
+use Mockery;
+use Preferences;
+use Tests\TestCase;
+
+/**
+ * Class CreateCSVImportTest
+ */
+class CreateCSVImportTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * Covers a default run with perfect arguments.
+ *
+ * @covers \FireflyIII\Console\Commands\Import\CreateCSVImport
+ */
+ public function testHandle(): void
+ {
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $jobRepos = $this->mock(ImportJobRepositoryInterface::class);
+ $fileRoutine = $this->mock(FileRoutine::class);
+ $storage = $this->mock(ImportArrayStorage::class);
+ $user = $this->user();
+ $token = new Preference;
+ $importJob = $this->user()->importJobs()->first();
+ $file = storage_path('build/test-upload.csv');
+ $config = storage_path('build/configuration.json');
+
+ // set preferences:
+ $token->data = 'token';
+
+ // mock calls to repository:
+ $userRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($user);
+ $jobRepos->shouldReceive('setUser')->atLeast()->once();
+ $jobRepos->shouldReceive('create')->atLeast()->once()->andReturn($importJob);
+ $jobRepos->shouldReceive('storeCLIupload')->atLeast()->once()->andReturn(new MessageBag);
+ $jobRepos->shouldReceive('setConfiguration')->atLeast()->once();
+
+ // job is ready to run.
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'ready_to_run'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'provider_finished'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'storing_data'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'storage_finished'])->atLeast()->once();
+
+ // file routine gets called.
+ $fileRoutine->shouldReceive('setImportJob')->atLeast()->once();
+ $fileRoutine->shouldReceive('run')->atLeast()->once();
+
+ // store data thing gets called.
+ $storage->shouldReceive('setImportJob')->atLeast()->once();
+ $storage->shouldReceive('store')->atLeast()->once();
+
+ // mock Preferences.
+ Preferences::shouldReceive('setForUser')->atLeast()->once()->withArgs([Mockery::any(), 'lastActivity', Mockery::any()]);
+ Preferences::shouldReceive('getForUser')->atLeast()->once()->withArgs([Mockery::any(), 'access_token', null])->andReturn($token);
+
+
+ $parameters = [
+ $file,
+ $config,
+ '--user=1',
+ '--token=token',
+ ];
+
+ $this->artisan('firefly-iii:csv-import ' . implode(' ', $parameters))
+ ->expectsOutput(sprintf('Import file : %s', $file))
+ ->expectsOutput(sprintf('Configuration file : %s', $config))
+ ->expectsOutput('User : #1 (thegrumpydictator@gmail.com)')
+ ->expectsOutput(sprintf('Job : %s', $importJob->key))
+ ->assertExitCode(0);
+
+ // this method imports nothing so there is nothing to verify.
+
+ }
+
+ /**
+ * Covers a default run with perfect arguments, but no import tag
+ *
+ * @covers \FireflyIII\Console\Commands\Import\CreateCSVImport
+ */
+ public function testHandleNoTag(): void
+ {
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $jobRepos = $this->mock(ImportJobRepositoryInterface::class);
+ $fileRoutine = $this->mock(FileRoutine::class);
+ $storage = $this->mock(ImportArrayStorage::class);
+ $user = $this->user();
+ $token = new Preference;
+
+ $importJob = ImportJob::create(
+ [
+ 'key' => 'key-' . random_int(1, 100000),
+ 'user_id' => 1,
+ 'file_type' => 'csv',
+ 'status' => 'new',
+ 'errors' => [],
+ ]
+ );
+
+ $file = storage_path('build/test-upload.csv');
+ $config = storage_path('build/configuration.json');
+
+ // set preferences:
+ $token->data = 'token';
+
+ // mock calls to repository:
+ $userRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($user);
+ $jobRepos->shouldReceive('setUser')->atLeast()->once();
+ $jobRepos->shouldReceive('create')->atLeast()->once()->andReturn($importJob);
+ $jobRepos->shouldReceive('storeCLIupload')->atLeast()->once()->andReturn(new MessageBag);
+ $jobRepos->shouldReceive('setConfiguration')->atLeast()->once();
+
+ // job is ready to run.
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'ready_to_run'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'provider_finished'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'storing_data'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'storage_finished'])->atLeast()->once();
+
+ // file routine gets called.
+ $fileRoutine->shouldReceive('setImportJob')->atLeast()->once();
+ $fileRoutine->shouldReceive('run')->atLeast()->once();
+
+ // store data thing gets called.
+ $storage->shouldReceive('setImportJob')->atLeast()->once();
+ $storage->shouldReceive('store')->atLeast()->once();
+
+ // mock Preferences.
+ Preferences::shouldReceive('setForUser')->atLeast()->once()->withArgs([Mockery::any(), 'lastActivity', Mockery::any()]);
+ Preferences::shouldReceive('getForUser')->atLeast()->once()->withArgs([Mockery::any(), 'access_token', null])->andReturn($token);
+
+
+ $parameters = [
+ $file,
+ $config,
+ '--user=1',
+ '--token=token',
+ ];
+
+ $this->artisan('firefly-iii:csv-import ' . implode(' ', $parameters))
+ ->expectsOutput(sprintf('Import file : %s', $file))
+ ->expectsOutput(sprintf('Configuration file : %s', $config))
+ ->expectsOutput('User : #1 (thegrumpydictator@gmail.com)')
+ ->expectsOutput(sprintf('Job : %s', $importJob->key))
+ ->expectsOutput('No transactions have been imported :(.')
+ ->assertExitCode(0);
+
+ // this method imports nothing so there is nothing to verify.
+ }
+
+ /**
+ * Covers a default run with perfect arguments, but errors after importing.
+ *
+ * @covers \FireflyIII\Console\Commands\Import\CreateCSVImport
+ */
+ public function testHandleErrors(): void
+ {
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $jobRepos = $this->mock(ImportJobRepositoryInterface::class);
+ $fileRoutine = $this->mock(FileRoutine::class);
+ $storage = $this->mock(ImportArrayStorage::class);
+ $user = $this->user();
+ $token = new Preference;
+
+ $importJob = ImportJob::create(
+ [
+ 'key' => 'key-' . random_int(1, 100000),
+ 'user_id' => 1,
+ 'file_type' => 'csv',
+ 'status' => 'new',
+ 'errors' => ['I am an error'],
+ ]
+ );
+
+ $file = storage_path('build/test-upload.csv');
+ $config = storage_path('build/configuration.json');
+
+ // set preferences:
+ $token->data = 'token';
+
+ // mock calls to repository:
+ $userRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($user);
+ $jobRepos->shouldReceive('setUser')->atLeast()->once();
+ $jobRepos->shouldReceive('create')->atLeast()->once()->andReturn($importJob);
+ $jobRepos->shouldReceive('storeCLIupload')->atLeast()->once()->andReturn(new MessageBag);
+ $jobRepos->shouldReceive('setConfiguration')->atLeast()->once();
+
+ // job is ready to run.
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'ready_to_run'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'provider_finished'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'storing_data'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'storage_finished'])->atLeast()->once();
+
+ // file routine gets called.
+ $fileRoutine->shouldReceive('setImportJob')->atLeast()->once();
+ $fileRoutine->shouldReceive('run')->atLeast()->once();
+
+ // store data thing gets called.
+ $storage->shouldReceive('setImportJob')->atLeast()->once();
+ $storage->shouldReceive('store')->atLeast()->once();
+
+ // mock Preferences.
+ Preferences::shouldReceive('setForUser')->atLeast()->once()->withArgs([Mockery::any(), 'lastActivity', Mockery::any()]);
+ Preferences::shouldReceive('getForUser')->atLeast()->once()->withArgs([Mockery::any(), 'access_token', null])->andReturn($token);
+
+
+ $parameters = [
+ $file,
+ $config,
+ '--user=1',
+ '--token=token',
+ ];
+
+ $this->artisan('firefly-iii:csv-import ' . implode(' ', $parameters))
+ ->expectsOutput(sprintf('Import file : %s', $file))
+ ->expectsOutput(sprintf('Configuration file : %s', $config))
+ ->expectsOutput('User : #1 (thegrumpydictator@gmail.com)')
+ ->expectsOutput(sprintf('Job : %s', $importJob->key))
+ ->expectsOutput('- I am an error')
+ ->assertExitCode(0);
+
+ // this method imports nothing so there is nothing to verify.
+ }
+
+ /**
+ * Crash while storing data.
+ *
+ * @covers \FireflyIII\Console\Commands\Import\CreateCSVImport
+ */
+ public function testHandleCrashStorage(): void
+ {
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $jobRepos = $this->mock(ImportJobRepositoryInterface::class);
+ $fileRoutine = $this->mock(FileRoutine::class);
+ $storage = $this->mock(ImportArrayStorage::class);
+ $user = $this->user();
+ $token = new Preference;
+ $importJob = $this->user()->importJobs()->first();
+ $file = storage_path('build/test-upload.csv');
+ $config = storage_path('build/configuration.json');
+
+ // set preferences:
+ $token->data = 'token';
+
+ // mock calls to repository:
+ $userRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($user);
+ $jobRepos->shouldReceive('setUser')->atLeast()->once();
+ $jobRepos->shouldReceive('create')->atLeast()->once()->andReturn($importJob);
+ $jobRepos->shouldReceive('storeCLIupload')->atLeast()->once()->andReturn(new MessageBag);
+ $jobRepos->shouldReceive('setConfiguration')->atLeast()->once();
+
+ // job is ready to run.
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'ready_to_run'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'provider_finished'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'storing_data'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'error'])->atLeast()->once();
+
+ // file routine gets called.
+ $fileRoutine->shouldReceive('setImportJob')->atLeast()->once();
+ $fileRoutine->shouldReceive('run')->atLeast()->once();
+
+ // store data thing gets called.
+ $storage->shouldReceive('setImportJob')->atLeast()->once();
+ $storage->shouldReceive('store')->atLeast()->once()->andThrow(new FireflyException('I am storage error.'));
+
+ // mock Preferences.
+ Preferences::shouldReceive('getForUser')->atLeast()->once()->withArgs([Mockery::any(), 'access_token', null])->andReturn($token);
+
+
+ $parameters = [
+ $file,
+ $config,
+ '--user=1',
+ '--token=token',
+ ];
+
+ $this->artisan('firefly-iii:csv-import ' . implode(' ', $parameters))
+ ->expectsOutput(sprintf('Import file : %s', $file))
+ ->expectsOutput(sprintf('Configuration file : %s', $config))
+ ->expectsOutput('User : #1 (thegrumpydictator@gmail.com)')
+ ->expectsOutput(sprintf('Job : %s', $importJob->key))
+ ->expectsOutput('The import routine crashed: I am storage error.')
+ ->assertExitCode(1);
+
+ // this method imports nothing so there is nothing to verify.
+
+ }
+
+ /**
+ * The file processor crashes for some reason.
+ *
+ * @covers \FireflyIII\Console\Commands\Import\CreateCSVImport
+ */
+ public function testHandleCrashProcess(): void
+ {
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $jobRepos = $this->mock(ImportJobRepositoryInterface::class);
+ $fileRoutine = $this->mock(FileRoutine::class);
+ $storage = $this->mock(ImportArrayStorage::class);
+ $user = $this->user();
+ $token = new Preference;
+ $importJob = $this->user()->importJobs()->first();
+ $file = storage_path('build/test-upload.csv');
+ $config = storage_path('build/configuration.json');
+
+ // set preferences:
+ $token->data = 'token';
+
+ // mock calls to repository:
+ $userRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($user);
+ $jobRepos->shouldReceive('setUser')->atLeast()->once();
+ $jobRepos->shouldReceive('create')->atLeast()->once()->andReturn($importJob);
+ $jobRepos->shouldReceive('storeCLIupload')->atLeast()->once()->andReturn(new MessageBag);
+ $jobRepos->shouldReceive('setConfiguration')->atLeast()->once();
+
+ // job is ready to run.
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'ready_to_run'])->atLeast()->once();
+ $jobRepos->shouldReceive('setStatus')->withArgs([Mockery::any(), 'error'])->atLeast()->once();
+
+ // file routine gets called.
+ $fileRoutine->shouldReceive('setImportJob')->atLeast()->once();
+ $fileRoutine->shouldReceive('run')->atLeast()->once()->andThrows(new FireflyException('I am big bad exception.'));
+
+ // mock Preferences.
+ Preferences::shouldReceive('getForUser')->atLeast()->once()->withArgs([Mockery::any(), 'access_token', null])->andReturn($token);
+
+ $parameters = [
+ $file,
+ $config,
+ '--user=1',
+ '--token=token',
+ ];
+
+ $this->artisan('firefly-iii:csv-import ' . implode(' ', $parameters))
+ ->expectsOutput(sprintf('Import file : %s', $file))
+ ->expectsOutput(sprintf('Configuration file : %s', $config))
+ ->expectsOutput('User : #1 (thegrumpydictator@gmail.com)')
+ ->expectsOutput(sprintf('Job : %s', $importJob->key))
+ ->expectsOutput('The import routine crashed: I am big bad exception.')
+ ->assertExitCode(1);
+
+ // this method imports nothing so there is nothing to verify.
+
+ }
+
+ /**
+ * Throw error when storing data.
+ *
+ * @covers \FireflyIII\Console\Commands\Import\CreateCSVImport
+ */
+ public function testHandleFileStoreError(): void
+ {
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $jobRepos = $this->mock(ImportJobRepositoryInterface::class);
+ $this->mock(FileRoutine::class);
+ $this->mock(ImportArrayStorage::class);
+ $user = $this->user();
+ $token = new Preference;
+ $importJob = $this->user()->importJobs()->first();
+ $file = storage_path('build/test-upload.csv');
+ $config = storage_path('build/configuration.json');
+ $messages = new MessageBag;
+ $messages->add('file', 'Some file error.');
+
+ // set preferences:
+ $token->data = 'token';
+
+ // mock calls to repository:
+ $userRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($user);
+ $jobRepos->shouldReceive('setUser')->atLeast()->once();
+ $jobRepos->shouldReceive('create')->atLeast()->once()->andReturn($importJob);
+ $jobRepos->shouldReceive('storeCLIupload')->atLeast()->once()->andReturn($messages);
+
+ // mock Preferences.
+ Preferences::shouldReceive('getForUser')->atLeast()->once()->withArgs([Mockery::any(), 'access_token', null])->andReturn($token);
+
+
+ $parameters = [
+ $file,
+ $config,
+ '--user=1',
+ '--token=token',
+ ];
+ $this->artisan('firefly-iii:csv-import ' . implode(' ', $parameters))
+ ->expectsOutput(sprintf('Import file : %s', $file))
+ ->expectsOutput(sprintf('Configuration file : %s', $config))
+ ->expectsOutput('User : #1 (thegrumpydictator@gmail.com)')
+ ->expectsOutput(sprintf('Job : %s', $importJob->key))
+ ->expectsOutput('Some file error.')
+ ->assertExitCode(1);
+
+ // this method imports nothing so there is nothing to verify.
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Integrity/ReportEmptyObjectsTest.php b/tests/Unit/Console/Commands/Integrity/ReportEmptyObjectsTest.php
new file mode 100644
index 0000000000..48f6a8f9ba
--- /dev/null
+++ b/tests/Unit/Console/Commands/Integrity/ReportEmptyObjectsTest.php
@@ -0,0 +1,148 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Integrity;
+
+
+use FireflyIII\Models\Account;
+use FireflyIII\Models\Budget;
+use FireflyIII\Models\Category;
+use FireflyIII\Models\Tag;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class ReportEmptyObjectsTest
+ */
+class ReportEmptyObjectsTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * Run basic test routine.
+ *
+ * @covers \FireflyIII\Console\Commands\Integrity\ReportEmptyObjects
+ */
+ public function testHandleBudget(): void
+ {
+ $user = $this->user();
+ $budget = Budget::create(
+ [
+ 'user_id' => $user->id,
+ 'name' => 'Some budget',
+ ]);
+ $budgetLine = sprintf('User #%d (%s) has budget #%d ("%s") which has no transaction journals.',
+ $user->id, $user->email, $budget->id, $budget->name);
+ $budgetLimitLine = sprintf('User #%d (%s) has budget #%d ("%s") which has no budget limits.',
+ $user->id, $user->email, $budget->id, $budget->name);
+
+ $this->artisan('firefly-iii:report-empty-objects')
+ ->expectsOutput($budgetLine)
+ ->expectsOutput($budgetLimitLine)
+ ->assertExitCode(0);
+ $budget->forceDelete();
+
+ // this method changes no objects so there is nothing to verify.
+ }
+
+ /**
+ * Run basic test routine.
+ *
+ * @covers \FireflyIII\Console\Commands\Integrity\ReportEmptyObjects
+ */
+ public function testHandleCategory(): void
+ {
+ $user = $this->user();
+ $category = Category::create(
+ [
+ 'user_id' => $user->id,
+ 'name' => 'Some category',
+ ]);
+ $categoryLine = sprintf('User #%d (%s) has category #%d ("%s") which has no transaction journals.',
+ $user->id, $user->email, $category->id, $category->name);
+
+ $this->artisan('firefly-iii:report-empty-objects')
+ ->expectsOutput($categoryLine)
+ ->assertExitCode(0);
+ $category->forceDelete();
+
+ // this method changes no objects so there is nothing to verify.
+ }
+
+ /**
+ * Run basic test routine.
+ *
+ * @covers \FireflyIII\Console\Commands\Integrity\ReportEmptyObjects
+ */
+ public function testHandleTag(): void
+ {
+ $user = $this->user();
+ $tag = Tag::create(
+ [
+ 'user_id' => $user->id,
+ 'tag' => 'Some tag',
+ 'tagMode' => 'nothing',
+ ]);
+ $tagLine = sprintf('User #%d (%s) has tag #%d ("%s") which has no transaction journals.',
+ $user->id, $user->email, $tag->id, $tag->tag);
+
+ $this->artisan('firefly-iii:report-empty-objects')
+ ->expectsOutput($tagLine)
+ ->assertExitCode(0);
+ $tag->forceDelete();
+
+ // this method changes no objects so there is nothing to verify.
+ }
+
+ /**
+ * Run basic test routine.
+ *
+ * @covers \FireflyIII\Console\Commands\Integrity\ReportEmptyObjects
+ */
+ public function testHandleAccount(): void
+ {
+ $user = $this->user();
+ $account = Account::create(
+ [
+ 'user_id' => $user->id,
+ 'name' => 'Some account',
+ 'account_type_id' => 1,
+ ]);
+ $tagLine = sprintf('User #%d (%s) has account #%d ("%s") which has no transactions.',
+ $user->id, $user->email, $account->id, $account->name);
+
+ $this->artisan('firefly-iii:report-empty-objects')
+ ->expectsOutput($tagLine)
+ ->assertExitCode(0);
+ $account->forceDelete();
+
+ // this method changes no objects so there is nothing to verify.
+ }
+
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Integrity/ReportSumTest.php b/tests/Unit/Console/Commands/Integrity/ReportSumTest.php
new file mode 100644
index 0000000000..1377a83231
--- /dev/null
+++ b/tests/Unit/Console/Commands/Integrity/ReportSumTest.php
@@ -0,0 +1,85 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Integrity;
+
+
+use FireflyIII\Models\Transaction;
+use FireflyIII\Repositories\User\UserRepositoryInterface;
+use Illuminate\Support\Collection;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class ReportSumTest
+ */
+class ReportSumTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Integrity\ReportSum
+ */
+ public function testHandle(): void
+ {
+ $repository = $this->mock(UserRepositoryInterface::class);
+ $repository->shouldReceive('all')->atLeast()->once()->andReturn(new Collection([$this->user()]));
+
+ $this->artisan('firefly-iii:report-sum')
+ ->expectsOutput(sprintf('Amount integrity OK for user #%d', $this->user()->id))
+ ->assertExitCode(0);
+
+ // this method changes no objects so there is nothing to verify.
+ }
+
+ /**
+ * Create transaction to make balance uneven.
+ * @covers \FireflyIII\Console\Commands\Integrity\ReportSum
+ */
+ public function testHandleUneven(): void
+ {
+ $transaction = Transaction::create(
+ [
+ 'transaction_journal_id' => $this->getRandomWithdrawal()->id,
+ 'user_id' => 1,
+ 'account_id' => $this->getRandomAsset()->id,
+ 'amount' => 10,
+ ]
+ );
+
+ $repository = $this->mock(UserRepositoryInterface::class);
+ $repository->shouldReceive('all')->atLeast()->once()->andReturn(new Collection([$this->user()]));
+
+ $this->artisan('firefly-iii:report-sum')
+ ->expectsOutput(sprintf('Error: Transactions for user #%d (%s) are off by %s!', $this->user()->id, $this->user()->email, '10.0'))
+ ->assertExitCode(0);
+ $transaction->forceDelete();
+
+ // this method changes no objects so there is nothing to verify.
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Tools/ApplyRulesTest.php b/tests/Unit/Console/Commands/Tools/ApplyRulesTest.php
new file mode 100644
index 0000000000..e43e604854
--- /dev/null
+++ b/tests/Unit/Console/Commands/Tools/ApplyRulesTest.php
@@ -0,0 +1,404 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Tools;
+
+
+use FireflyIII\Helpers\Collector\GroupCollectorInterface;
+use FireflyIII\Repositories\Account\AccountRepositoryInterface;
+use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
+use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
+use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
+use FireflyIII\TransactionRules\Engine\RuleEngine;
+use Illuminate\Support\Collection;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class ApplyRulesTest
+ */
+class ApplyRulesTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * Basic call with everything perfect (and ALL rules).
+ *
+ * @covers \FireflyIII\Console\Commands\Tools\ApplyRules
+ */
+ public function testHandle(): void
+ {
+ $ruleRepos = $this->mock(RuleRepositoryInterface::class);
+ $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $collector = $this->mock(GroupCollectorInterface::class);
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $ruleEngine = $this->mock(RuleEngine::class);
+
+ // data
+ $asset = $this->getRandomAsset();
+ $journal = $this->getRandomWithdrawal();
+ $group = $this->user()->ruleGroups()->first();
+ $rule = $this->user()->rules()->first();
+ $groups = new Collection([$group]);
+ $rules = new Collection([$rule]);
+
+ // expected calls:
+ $ruleRepos->shouldReceive('setUser')->atLeast()->once();
+ $ruleGroupRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $journalRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('findNull')->atLeast()->once()->withArgs([1])->andReturn($asset);
+ $journalRepos->shouldReceive('firstNull')->atLeast()->once()->andReturn($journal);
+
+ $ruleGroupRepos->shouldReceive('getActiveGroups')->atLeast()->once()->andReturn($groups);
+ $ruleGroupRepos->shouldReceive('getActiveStoreRules')->atLeast()->once()->andReturn($rules);
+
+
+ $collector->shouldReceive('setUser')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setAccounts')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setRange')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('getExtractedJournals')->atLeast()->once()->andReturn([[], [], []]);
+
+ $ruleEngine->shouldReceive('setUser')->atLeast()->once();
+ $ruleEngine->shouldReceive('setRulesToApply')->atLeast()->once();
+ $ruleEngine->shouldReceive('processJournalArray')->times(3);
+
+ $parameters = [
+ '--user=1',
+ '--token=token',
+ '--accounts=1',
+ '--all_rules',
+ ];
+
+ $this->artisan('firefly-iii:apply-rules ' . implode(' ', $parameters))
+ ->expectsOutput('Will apply 1 rule(s) to 3 transaction(s).')
+ ->expectsOutput('Done!')
+ ->assertExitCode(0);
+
+ // this method changes no objects so there is nothing to verify.
+ }
+
+ /**
+ * Basic call with everything perfect (and ALL rules), but no rules will be selected.
+ *
+ * @covers \FireflyIII\Console\Commands\Tools\ApplyRules
+ */
+ public function testHandEmptye(): void
+ {
+ $ruleRepos = $this->mock(RuleRepositoryInterface::class);
+ $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $collector = $this->mock(GroupCollectorInterface::class);
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $ruleEngine = $this->mock(RuleEngine::class);
+
+ // data
+ $asset = $this->getRandomAsset();
+ $journal = $this->getRandomWithdrawal();
+ $group = $this->user()->ruleGroups()->first();
+ $groups = new Collection([$group]);
+
+ // expected calls:
+ $ruleRepos->shouldReceive('setUser')->atLeast()->once();
+ $ruleGroupRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $journalRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('findNull')->atLeast()->once()->withArgs([1])->andReturn($asset);
+ $journalRepos->shouldReceive('firstNull')->atLeast()->once()->andReturn($journal);
+
+ $ruleGroupRepos->shouldReceive('getActiveGroups')->atLeast()->once()->andReturn($groups);
+ $ruleGroupRepos->shouldReceive('getActiveStoreRules')->atLeast()->once()->andReturn(new Collection);
+
+
+ $collector->shouldReceive('setUser')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setAccounts')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setRange')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('getExtractedJournals')->atLeast()->once()->andReturn([[], [], []]);
+
+ $ruleEngine->shouldReceive('setUser')->atLeast()->once();
+ $ruleEngine->shouldReceive('setRulesToApply')->atLeast()->once();
+ $ruleEngine->shouldReceive('processJournalArray')->times(3);
+
+ $parameters = [
+ '--user=1',
+ '--token=token',
+ '--accounts=1',
+ '--all_rules',
+ ];
+
+ $this->artisan('firefly-iii:apply-rules ' . implode(' ', $parameters))
+ ->expectsOutput('No rules or rule groups have been included.')
+ ->expectsOutput('Done!')
+ ->assertExitCode(0);
+ }
+
+
+ /**
+ * Basic call with everything perfect (and ALL rules) and dates.
+ *
+ * @covers \FireflyIII\Console\Commands\Tools\ApplyRules
+ */
+ public function testHandleDate(): void
+ {
+ $ruleRepos = $this->mock(RuleRepositoryInterface::class);
+ $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class);
+ $this->mock(JournalRepositoryInterface::class);
+ $collector = $this->mock(GroupCollectorInterface::class);
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $ruleEngine = $this->mock(RuleEngine::class);
+
+ // data
+ $asset = $this->getRandomAsset();
+ $group = $this->user()->ruleGroups()->first();
+ $rule = $this->user()->rules()->first();
+ $groups = new Collection([$group]);
+ $rules = new Collection([$rule]);
+
+ // expected calls:
+ $ruleRepos->shouldReceive('setUser')->atLeast()->once();
+ $ruleGroupRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('findNull')->atLeast()->once()->withArgs([1])->andReturn($asset);
+
+ $ruleGroupRepos->shouldReceive('getActiveGroups')->atLeast()->once()->andReturn($groups);
+ $ruleGroupRepos->shouldReceive('getActiveStoreRules')->atLeast()->once()->andReturn($rules);
+
+
+ $collector->shouldReceive('setUser')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setAccounts')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setRange')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('getExtractedJournals')->atLeast()->once()->andReturn([[], [], []]);
+
+ $ruleEngine->shouldReceive('setUser')->atLeast()->once();
+ $ruleEngine->shouldReceive('setRulesToApply')->atLeast()->once();
+ $ruleEngine->shouldReceive('processJournalArray')->times(3);
+
+ $parameters = [
+ '--user=1',
+ '--token=token',
+ '--accounts=1',
+ '--all_rules',
+ '--start_date=2019-01-31',
+ '--end_date=2019-01-01',
+ ];
+
+ $this->artisan('firefly-iii:apply-rules ' . implode(' ', $parameters))
+ ->expectsOutput('Will apply 1 rule(s) to 3 transaction(s).')
+ ->expectsOutput('Done!')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * Will submit some rules to apply.
+ *
+ * @covers \FireflyIII\Console\Commands\Tools\ApplyRules
+ */
+ public function testHandleRules(): void
+ {
+ $ruleRepos = $this->mock(RuleRepositoryInterface::class);
+ $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $collector = $this->mock(GroupCollectorInterface::class);
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $ruleEngine = $this->mock(RuleEngine::class);
+
+ // data
+ $asset = $this->getRandomAsset();
+ $journal = $this->getRandomWithdrawal();
+ $group = $this->user()->ruleGroups()->first();
+ $groups = new Collection([$group]);
+ $activeRule = $this->user()->rules()->where('active', 1)->inRandomOrder()->first();
+ $inactiveRule = $this->user()->rules()->where('active', 0)->inRandomOrder()->first();
+ $rules = new Collection([$activeRule]);
+
+ // expected calls:
+ $ruleRepos->shouldReceive('setUser')->atLeast()->once();
+ $ruleGroupRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $journalRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('findNull')->atLeast()->once()->withArgs([1])->andReturn($asset);
+ $journalRepos->shouldReceive('firstNull')->atLeast()->once()->andReturn($journal);
+
+ $ruleRepos->shouldReceive('find')->atLeast()->once()->withArgs([$activeRule->id])->andReturn($activeRule);
+ $ruleRepos->shouldReceive('find')->atLeast()->once()->withArgs([$inactiveRule->id])->andReturn($inactiveRule);
+
+ $ruleGroupRepos->shouldReceive('getActiveGroups')->atLeast()->once()->andReturn($groups);
+ $ruleGroupRepos->shouldReceive('getActiveStoreRules')->atLeast()->once()->andReturn($rules);
+
+
+ $collector->shouldReceive('setUser')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setAccounts')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setRange')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('getExtractedJournals')->atLeast()->once()->andReturn([[], [], []]);
+
+ $ruleEngine->shouldReceive('setUser')->atLeast()->once();
+ $ruleEngine->shouldReceive('setRulesToApply')->atLeast()->once();
+ $ruleEngine->shouldReceive('processJournalArray')->times(3);
+
+ $parameters = [
+ '--user=1',
+ '--token=token',
+ '--accounts=1',
+ sprintf('--rules=%d,%d', $activeRule->id, $inactiveRule->id),
+ ];
+
+ $this->artisan('firefly-iii:apply-rules ' . implode(' ', $parameters))
+ ->expectsOutput('Will apply 1 rule(s) to 3 transaction(s).')
+ ->expectsOutput('Done!')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * Basic call with two rule groups. One active, one inactive.
+ *
+ * @covers \FireflyIII\Console\Commands\Tools\ApplyRules
+ */
+ public function testHandleRuleGroups(): void
+ {
+ $ruleRepos = $this->mock(RuleRepositoryInterface::class);
+ $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $collector = $this->mock(GroupCollectorInterface::class);
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $ruleEngine = $this->mock(RuleEngine::class);
+
+ $activeGroup = $this->user()->ruleGroups()->where('active', 1)->inRandomOrder()->first();
+ $inactiveGroup = $this->user()->ruleGroups()->where('active', 0)->inRandomOrder()->first();
+
+ // data
+ $asset = $this->getRandomAsset();
+ $journal = $this->getRandomWithdrawal();
+ $rule = $this->user()->rules()->first();
+ $groups = new Collection([$activeGroup]);
+ $rules = new Collection([$rule]);
+
+ // expected calls:
+ $ruleRepos->shouldReceive('setUser')->atLeast()->once();
+ $ruleGroupRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $journalRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('findNull')->atLeast()->once()->withArgs([1])->andReturn($asset);
+ $journalRepos->shouldReceive('firstNull')->atLeast()->once()->andReturn($journal);
+
+ $ruleGroupRepos->shouldReceive('getActiveGroups')->atLeast()->once()->andReturn($groups);
+ $ruleGroupRepos->shouldReceive('getActiveStoreRules')->atLeast()->once()->andReturn($rules);
+ $ruleGroupRepos->shouldReceive('find')->atLeast()->once()->withArgs([$activeGroup->id])->andReturn($activeGroup);
+ $ruleGroupRepos->shouldReceive('find')->atLeast()->once()->withArgs([$inactiveGroup->id])->andReturn($inactiveGroup);
+
+ $collector->shouldReceive('setUser')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setAccounts')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('setRange')->atLeast()->once()->andReturnSelf();
+ $collector->shouldReceive('getExtractedJournals')->atLeast()->once()->andReturn([[], [], []]);
+
+ $ruleEngine->shouldReceive('setUser')->atLeast()->once();
+ $ruleEngine->shouldReceive('setRulesToApply')->atLeast()->once();
+ $ruleEngine->shouldReceive('processJournalArray')->times(3);
+
+ $parameters = [
+ '--user=1',
+ '--token=token',
+ '--accounts=1',
+ sprintf('--rule_groups=%d,%d', $activeGroup->id, $inactiveGroup->id),
+ ];
+
+ $this->artisan('firefly-iii:apply-rules ' . implode(' ', $parameters))
+ ->expectsOutput(sprintf('Will ignore inactive rule group #%d ("%s")', $inactiveGroup->id, $inactiveGroup->title))
+ // one rule out of 2 groups:
+ ->expectsOutput('Will apply 1 rule(s) to 3 transaction(s).')
+ ->expectsOutput('Done!')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * Basic call but no accounts submitted.
+ *
+ * @covers \FireflyIII\Console\Commands\Tools\ApplyRules
+ */
+ public function testHandleNoAccounts(): void
+ {
+ $ruleRepos = $this->mock(RuleRepositoryInterface::class);
+ $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class);
+ $this->mock(JournalRepositoryInterface::class);
+ $this->mock(GroupCollectorInterface::class);
+ $this->mock(AccountRepositoryInterface::class);
+ $this->mock(RuleEngine::class);
+
+ // expected calls:
+ $ruleRepos->shouldReceive('setUser')->atLeast()->once();
+ $ruleGroupRepos->shouldReceive('setUser')->atLeast()->once();
+
+ $parameters = [
+ '--user=1',
+ '--token=token',
+ '--accounts=',
+ '--all_rules',
+ ];
+
+ $this->artisan('firefly-iii:apply-rules ' . implode(' ', $parameters))
+ ->expectsOutput('Please use the --accounts option to indicate the accounts to apply rules to.')
+ ->assertExitCode(1);
+ }
+
+ /**
+ * Basic call but only one expense account submitted
+ *
+ * @covers \FireflyIII\Console\Commands\Tools\ApplyRules
+ */
+ public function testHandleExpenseAccounts(): void
+ {
+ $ruleRepos = $this->mock(RuleRepositoryInterface::class);
+ $ruleGroupRepos = $this->mock(RuleGroupRepositoryInterface::class);
+ $this->mock(JournalRepositoryInterface::class);
+ $this->mock(GroupCollectorInterface::class);
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $this->mock(RuleEngine::class);
+
+ // data
+ $expense = $this->getRandomExpense();
+
+ // expected calls:
+ $ruleRepos->shouldReceive('setUser')->atLeast()->once();
+ $ruleGroupRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('findNull')->atLeast()->once()->withArgs([$expense->id])->andReturn($expense);
+
+
+ $parameters = [
+ '--user=1',
+ '--token=token',
+ '--accounts=' . $expense->id,
+ '--all_rules',
+ ];
+
+ $this->artisan('firefly-iii:apply-rules ' . implode(' ', $parameters))
+ ->expectsOutput('Please make sure all accounts in --accounts are asset accounts or liabilities.')
+ ->assertExitCode(1);
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Upgrade/AccountCurrenciesTest.php b/tests/Unit/Console/Commands/Upgrade/AccountCurrenciesTest.php
new file mode 100644
index 0000000000..7c8df7d964
--- /dev/null
+++ b/tests/Unit/Console/Commands/Upgrade/AccountCurrenciesTest.php
@@ -0,0 +1,341 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Upgrade;
+
+
+use FireflyConfig;
+use FireflyIII\Models\AccountMeta;
+use FireflyIII\Models\Configuration;
+use FireflyIII\Models\Preference;
+use FireflyIII\Models\Transaction;
+use FireflyIII\Models\TransactionCurrency;
+use FireflyIII\Models\TransactionJournal;
+use FireflyIII\Repositories\Account\AccountRepositoryInterface;
+use FireflyIII\Repositories\User\UserRepositoryInterface;
+use Illuminate\Support\Collection;
+use Log;
+use Mockery;
+use Preferences;
+use Tests\TestCase;
+
+/**
+ * Class AccountCurrenciesTest
+ */
+class AccountCurrenciesTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * Perfect run without opening balance.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\AccountCurrencies
+ */
+ public function testHandle(): void
+ {
+ $false = new Configuration;
+ $false->data = false;
+ $pref = new Preference;
+ $pref->data = 'EUR';
+ $account = $this->getRandomAsset();
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+
+ // mock calls
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->andReturn('1');
+ $accountRepos->shouldReceive('getOpeningBalance')->atLeast()->once()->andReturn(null);
+ $accountRepos->shouldReceive('getAccountsByType')->atLeast()->once()->andReturn(new Collection([$account]));
+ $userRepos->shouldReceive('all')->atLeast()->once()->andReturn(new Collection([$this->user()]));
+
+ // check config
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_account_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_account_currencies', true]);
+
+ // check preferences:
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'currencyPreference', 'EUR'])->andReturn($pref);
+
+ $this->artisan('firefly-iii:account-currencies')
+ ->expectsOutput('All accounts are OK.')
+ ->assertExitCode(0);
+
+ // nothing changed, so nothing to verify.
+ }
+
+ /**
+ * Perfect run without opening balance.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\AccountCurrencies
+ */
+ public function testHandleNotNull(): void
+ {
+ $false = new Configuration;
+ $false->data = false;
+ $pref = new Preference;
+ $pref->data = 'EUR';
+ $account = $this->getRandomAsset();
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $journal = $this->getRandomWithdrawal();
+
+ // mock calls
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+
+ // account reports USD
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->andReturn('2');
+ // journal is EUR.
+ $accountRepos->shouldReceive('getOpeningBalance')->atLeast()->once()->andReturn($journal);
+ $accountRepos->shouldReceive('getAccountsByType')->atLeast()->once()->andReturn(new Collection([$account]));
+ $userRepos->shouldReceive('all')->atLeast()->once()->andReturn(new Collection([$this->user()]));
+
+ // check config
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_account_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_account_currencies', true]);
+
+ // check preferences:
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'currencyPreference', 'EUR'])->andReturn($pref);
+
+ $this->artisan('firefly-iii:account-currencies')
+ ->expectsOutput(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name))
+ ->assertExitCode(0);
+
+ // check if currency has been changed for the journal + transactions.
+ $this->assertCount(1, TransactionJournal::where('id', $journal->id)->where('transaction_currency_id', 2)->get());
+ $this->assertCount(2, Transaction::where('transaction_journal_id', $journal->id)->where('transaction_currency_id', 2)->get());
+ }
+
+ /**
+ * Perfect run with opening balance.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\AccountCurrencies
+ */
+ public function testHandleOpeningBalance(): void
+ {
+ $false = new Configuration;
+ $false->data = false;
+ $pref = new Preference;
+ $pref->data = 'EUR';
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $journal = $this->getRandomWithdrawal();
+ $account = $this->getRandomAsset();
+ // mock calls
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->andReturn('1');
+ $accountRepos->shouldReceive('getOpeningBalance')->atLeast()->once()->andReturn($journal);
+ $userRepos->shouldReceive('all')->atLeast()->once()->andReturn(new Collection([$this->user()]));
+ $accountRepos->shouldReceive('getAccountsByType')->atLeast()->once()->andReturn(new Collection([$account]));
+
+ // check config
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_account_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_account_currencies', true]);
+
+ // check preferences:
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'currencyPreference', 'EUR'])->andReturn($pref);
+
+ $this->artisan('firefly-iii:account-currencies')
+ ->expectsOutput('All accounts are OK.')
+ ->assertExitCode(0);
+
+ // nothing changed, dont check output.
+ }
+
+ /**
+ * Perfect run with opening balance with different currencies
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\AccountCurrencies
+ */
+ public function testHandleDifferent(): void
+ {
+ $false = new Configuration;
+ $false->data = false;
+ $pref = new Preference;
+ $pref->data = 'USD';
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $journal = $this->getRandomWithdrawal();
+ $account = $this->getRandomAsset();
+ $euro = TransactionCurrency::where('code', 'EUR')->first();
+
+ // delete meta data of account just in case:
+ AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete();
+ // mock calls
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->andReturn('0');
+ $accountRepos->shouldReceive('getOpeningBalance')->atLeast()->once()->andReturn($journal);
+ $userRepos->shouldReceive('all')->atLeast()->once()->andReturn(new Collection([$this->user()]));
+ $accountRepos->shouldReceive('getAccountsByType')->atLeast()->once()->andReturn(new Collection([$account]));
+
+ // check config
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_account_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_account_currencies', true]);
+
+ // check preferences:
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'currencyPreference', 'EUR'])->andReturn($pref);
+
+ $this->artisan('firefly-iii:account-currencies')
+ ->expectsOutput(sprintf('Account #%d ("%s") now has a currency setting (#%d).', $account->id, $account->name, $euro->id))
+ ->assertExitCode(0);
+
+ // verify account meta data change.
+ $this->assertCount(1,
+ AccountMeta::where('account_id', $account->id)
+ ->where('name', 'currency_id')
+ ->where('data', $euro->id)->get());
+ }
+
+ /**
+ * No known currency preferences.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\AccountCurrencies
+ */
+ public function testHandleZeroPreference(): void
+ {
+ $false = new Configuration;
+ $false->data = false;
+ $pref = new Preference;
+ $pref->data = 'EUR';
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $account = $this->getRandomAsset();
+ $euro = TransactionCurrency::where('code', 'EUR')->first();
+ // mock calls
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->andReturn('0');
+ $accountRepos->shouldReceive('getOpeningBalance')->atLeast()->once()->andReturn(null);
+ $userRepos->shouldReceive('all')->atLeast()->once()->andReturn(new Collection([$this->user()]));
+ $accountRepos->shouldReceive('getAccountsByType')->atLeast()->once()->andReturn(new Collection([$account]));
+
+ // check config
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_account_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_account_currencies', true]);
+
+ // check preferences:
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'currencyPreference', 'EUR'])->andReturn($pref);
+
+ $this->artisan('firefly-iii:account-currencies')
+ ->expectsOutput(sprintf('Account #%d ("%s") now has a currency setting (%s).',
+ $account->id, $account->name, $euro->code
+ ))
+ ->expectsOutput('Corrected 1 account(s).')
+ ->assertExitCode(0);
+
+ $this->assertCount(1, AccountMeta::where('account_id', $account->id)
+ ->where('name', 'currency_id')
+ ->where('data', $euro->id)->get());
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Upgrade\AccountCurrencies
+ */
+ public function testHandleNoPreference(): void
+ {
+ $false = new Configuration;
+ $false->data = false;
+ $pref = new Preference;
+ $pref->data = false;
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $account = $this->getRandomAsset();
+ // mock calls
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->andReturn('1');
+ $accountRepos->shouldReceive('getOpeningBalance')->atLeast()->once()->andReturn(null);
+ $userRepos->shouldReceive('all')->atLeast()->once()->andReturn(new Collection([$this->user()]));
+ $accountRepos->shouldReceive('getAccountsByType')->atLeast()->once()->andReturn(new Collection([$account]));
+
+ // check config
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_account_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_account_currencies', true]);
+
+ // check preferences:
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'currencyPreference', 'EUR'])->andReturn($pref);
+
+ $this->artisan('firefly-iii:account-currencies')
+ ->expectsOutput('All accounts are OK.')
+ ->assertExitCode(0);
+ // nothing changed, so nothing to verify.
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Upgrade\AccountCurrencies
+ */
+ public function testHandleInvalidPreference(): void
+ {
+ $false = new Configuration;
+ $false->data = false;
+ $pref = new Preference;
+ $pref->data = 'ABC';
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $userRepos = $this->mock(UserRepositoryInterface::class);
+ $account = $this->getRandomAsset();
+
+ // mock calls
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $accountRepos->shouldReceive('getAccountsByType')->atLeast()->once()->andReturn(new Collection([$account]));
+ $userRepos->shouldReceive('all')->atLeast()->once()->andReturn(new Collection([$this->user()]));
+
+ // check config
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_account_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_account_currencies', true]);
+
+ // check preferences:
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'currencyPreference', 'EUR'])->andReturn($pref);
+
+ $this->artisan('firefly-iii:account-currencies')
+ ->expectsOutput('User has a preference for "ABC", but this currency does not exist.')
+ ->assertExitCode(0);
+
+ // nothing changed, so nothing to verify.
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Upgrade\AccountCurrencies
+ */
+ public function testHandleAlreadyExecuted(): void
+ {
+ $true = new Configuration;
+ $true->data = true;
+ $pref = new Preference;
+ $pref->data = 'EUR';
+ $this->mock(AccountRepositoryInterface::class);
+ $this->mock(UserRepositoryInterface::class);
+
+ // check config
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_account_currencies', false])->andReturn($true);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_account_currencies', true]);
+
+ // check preferences:
+ Preferences::shouldReceive('getForUser')->withArgs([Mockery::any(), 'currencyPreference', 'EUR'])->andReturn($pref);
+
+ $this->artisan('firefly-iii:account-currencies')
+ ->expectsOutput('This command has already been executed.')
+ ->assertExitCode(0);
+ // nothing changed, so nothing to verify.
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Upgrade/BackToJournalsTest.php b/tests/Unit/Console/Commands/Upgrade/BackToJournalsTest.php
new file mode 100644
index 0000000000..203471e974
--- /dev/null
+++ b/tests/Unit/Console/Commands/Upgrade/BackToJournalsTest.php
@@ -0,0 +1,244 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Upgrade;
+
+
+use FireflyConfig;
+use FireflyIII\Models\Budget;
+use FireflyIII\Models\Category;
+use FireflyIII\Models\Configuration;
+use FireflyIII\Models\Transaction;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class BackToJournalsTest
+ */
+class BackToJournalsTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * Perfect run. Will report on nothing.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\BackToJournals
+ */
+ public function testHandle(): void
+ {
+ // verify preference:
+ $false = new Configuration;
+ $false->data = false;
+ $true = new Configuration;
+ $true->data = true;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_back_to_journals', false])->andReturn($false);
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_migrated_to_groups', false])->andReturn($true);
+
+ // set new preference after running:
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_back_to_journals', true]);
+
+ $this->artisan('firefly-iii:back-to-journals')
+ ->expectsOutput('Check 0 transaction journal(s) for budget info.')
+ ->expectsOutput('Check 0 transaction journal(s) for category info.')
+ ->assertExitCode(0);
+
+ }
+
+ /**
+ * Transaction has a budget, journal doesn't.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\BackToJournals
+ */
+ public function testHandleBudget(): void
+ {
+ $journal = $this->getRandomWithdrawal();
+ /** @var Transaction $transaction */
+ $transaction = $journal->transactions()->first();
+ /** @var Budget $budget */
+ $budget = $this->user()->budgets()->first();
+ $transaction->budgets()->sync([$budget->id]);
+ $journal->budgets()->sync([]);
+ $journal->save();
+ $transaction->save();
+
+
+ // verify preference:
+ $false = new Configuration;
+ $false->data = false;
+ $true = new Configuration;
+ $true->data = true;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_back_to_journals', false])->andReturn($false);
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_migrated_to_groups', false])->andReturn($true);
+
+ // set new preference after running:
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_back_to_journals', true]);
+
+ $this->artisan('firefly-iii:back-to-journals')
+ ->expectsOutput('Check 1 transaction journal(s) for budget info.')
+ ->expectsOutput('Check 0 transaction journal(s) for category info.')
+ ->assertExitCode(0);
+
+ // transaction should have no budget:
+ $this->assertEquals(0, $transaction->budgets()->count());
+ // journal should have one.
+ $this->assertEquals(1, $journal->budgets()->count());
+ // should be $budget:
+ $this->assertEquals($budget->id, $journal->budgets()->first()->id);
+ }
+
+ /**
+ * Transaction has a category, journal doesn't.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\BackToJournals
+ */
+ public function testHandleCategory(): void
+ {
+ $journal = $this->getRandomWithdrawal();
+ /** @var Transaction $transaction */
+ $transaction = $journal->transactions()->first();
+ /** @var Category $category */
+ $category = $this->user()->categories()->first();
+ $transaction->categories()->sync([$category->id]);
+ $journal->categories()->sync([]);
+ $journal->save();
+ $transaction->save();
+
+ // verify preference:
+ $false = new Configuration;
+ $false->data = false;
+ $true = new Configuration;
+ $true->data = true;
+
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_back_to_journals', false])->andReturn($false);
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_migrated_to_groups', false])->andReturn($true);
+
+ // set new preference after running:
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_back_to_journals', true]);
+
+ $this->artisan('firefly-iii:back-to-journals')
+ ->expectsOutput('Check 0 transaction journal(s) for budget info.')
+ ->expectsOutput('Check 1 transaction journal(s) for category info.')
+ ->assertExitCode(0);
+
+ // transaction should have no category:
+ $this->assertEquals(0, $transaction->categories()->count());
+ // journal should have one.
+ $this->assertEquals(1, $journal->categories()->count());
+ // should be $category:
+ $this->assertEquals($category->id, $journal->categories()->first()->id);
+ }
+
+ /**
+ * Transaction has a budget, journal has another
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\BackToJournals
+ */
+ public function testHandleDifferentBudget(): void
+ {
+ $journal = $this->getRandomWithdrawal();
+ /** @var Transaction $transaction */
+ $transaction = $journal->transactions()->first();
+ /** @var Budget $budget */
+ $budget = $this->user()->budgets()->first();
+ $otherBudget = $this->user()->budgets()->where('id', '!=', $budget->id)->first();
+ $transaction->budgets()->sync([$budget->id]);
+ $journal->budgets()->sync([$otherBudget->id]);
+ $journal->save();
+ $transaction->save();
+
+
+ // verify preference:
+ $false = new Configuration;
+ $false->data = false;
+ $true = new Configuration;
+ $true->data = true;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_back_to_journals', false])->andReturn($false);
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_migrated_to_groups', false])->andReturn($true);
+
+ // set new preference after running:
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_back_to_journals', true]);
+
+ $this->artisan('firefly-iii:back-to-journals')
+ ->expectsOutput('Check 1 transaction journal(s) for budget info.')
+ ->expectsOutput('Check 0 transaction journal(s) for category info.')
+ ->assertExitCode(0);
+
+ // transaction should have no budget:
+ $this->assertEquals(0, $transaction->budgets()->count());
+ // journal should have one.
+ $this->assertEquals(1, $journal->budgets()->count());
+ // should be $budget:
+ $this->assertEquals($budget->id, $journal->budgets()->first()->id);
+
+ }
+
+
+ /**
+ * Transaction has a category, journal has another
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\BackToJournals
+ */
+ public function testHandleDifferentCategory(): void
+ {
+ $journal = $this->getRandomWithdrawal();
+ /** @var Transaction $transaction */
+ $transaction = $journal->transactions()->first();
+ /** @var Category $category */
+ $category = $this->user()->categories()->first();
+ $otherCategory = $this->user()->categories()->where('id', '!=', $category->id)->first();
+ $transaction->categories()->sync([$category->id]);
+ $journal->categories()->sync([$otherCategory->id]);
+ $journal->save();
+ $transaction->save();
+
+ // verify preference:
+ $false = new Configuration;
+ $false->data = false;
+ $true = new Configuration;
+ $true->data = true;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_back_to_journals', false])->andReturn($false);
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_migrated_to_groups', false])->andReturn($true);
+
+ // set new preference after running:
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_back_to_journals', true]);
+
+ $this->artisan('firefly-iii:back-to-journals')
+ ->expectsOutput('Check 0 transaction journal(s) for budget info.')
+ ->expectsOutput('Check 1 transaction journal(s) for category info.')
+ ->assertExitCode(0);
+
+ // transaction should have no category:
+ $this->assertEquals(0, $transaction->categories()->count());
+ // journal should have one.
+ $this->assertEquals(1, $journal->categories()->count());
+ // should be $category:
+ $this->assertEquals($category->id, $journal->categories()->first()->id);
+
+ }
+
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Upgrade/BudgetLimitCurrencyTest.php b/tests/Unit/Console/Commands/Upgrade/BudgetLimitCurrencyTest.php
new file mode 100644
index 0000000000..f2d55e6eab
--- /dev/null
+++ b/tests/Unit/Console/Commands/Upgrade/BudgetLimitCurrencyTest.php
@@ -0,0 +1,85 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Upgrade;
+
+
+use FireflyConfig;
+use FireflyIII\Models\BudgetLimit;
+use FireflyIII\Models\Configuration;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class BudgetLimitCurrencyTest
+ */
+class BudgetLimitCurrencyTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Upgrade\BudgetLimitCurrency
+ */
+ public function testHandle(): void
+ {
+ $this->artisan('firefly-iii:bl-currency')
+ ->assertExitCode(0);
+ }
+
+ /**
+ * Create a bad budget limit.
+ * @covers \FireflyIII\Console\Commands\Upgrade\BudgetLimitCurrency
+ */
+ public function testHandleBadLimit(): void
+ {
+ $false = new Configuration;
+ $false->data = false;
+ $budget = $this->user()->budgets()->first();
+ $limit = BudgetLimit::create(
+ [
+ 'budget_id' => $budget->id,
+ 'amount' => '10',
+ 'start_date' => '2019-01-01',
+ 'end_date' => '2019-01-31',
+ ]);
+
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_bl_currency', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_bl_currency', true]);
+
+ $this->artisan('firefly-iii:bl-currency')
+ ->expectsOutput(
+ sprintf('Budget limit #%d (part of budget "%s") now has a currency setting (%s).',
+ $limit->id, $budget->name, 'Euro'
+ )
+ )
+ ->assertExitCode(0);
+
+ // assume currency is filled in.
+ $this->assertCount(1, BudgetLimit::where('id', $limit->id)->where('transaction_currency_id', 1)->get());
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Upgrade/CCLiabilitiesTest.php b/tests/Unit/Console/Commands/Upgrade/CCLiabilitiesTest.php
new file mode 100644
index 0000000000..ef093beb38
--- /dev/null
+++ b/tests/Unit/Console/Commands/Upgrade/CCLiabilitiesTest.php
@@ -0,0 +1,129 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Upgrade;
+
+
+use FireflyConfig;
+use FireflyIII\Models\Account;
+use FireflyIII\Models\AccountType;
+use FireflyIII\Models\Configuration;
+use Log;
+use Tests\TestCase;
+
+/**
+ * Class CCLiabilitiesTest
+ */
+class CCLiabilitiesTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * @covers \FireflyIII\Console\Commands\Upgrade\CCLiabilities
+ */
+ public function testHandle(): void
+ {
+ $false = new Configuration;
+ $false->data = false;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_cc_liabilities', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_cc_liabilities', true]);
+
+
+ $this->artisan('firefly-iii:cc-liabilities')
+ ->expectsOutput('No incorrectly stored credit card liabilities.')
+ ->assertExitCode(0);
+
+ // nothing changed, so nothing to verify.
+ }
+
+ /**
+ * Add type to make the script run.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\CCLiabilities
+ */
+ public function testHandleEmpty(): void
+ {
+ $type = AccountType::create(
+ [
+ 'type' => AccountType::CREDITCARD,
+ ]
+ );
+ $false = new Configuration;
+ $false->data = false;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_cc_liabilities', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_cc_liabilities', true]);
+
+
+ $this->artisan('firefly-iii:cc-liabilities')
+ ->expectsOutput('No incorrectly stored credit card liabilities.')
+ ->assertExitCode(0);
+ $type->forceDelete();
+ // nothing changed, so nothing to verify.
+ }
+
+ /**
+ * Add some things to make it trigger.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\CCLiabilities
+ */
+ public function testHandleCase(): void
+ {
+ $type = AccountType::create(
+ [
+ 'type' => AccountType::CREDITCARD,
+ ]
+ );
+
+ $account = Account::create(
+ [
+ 'name' => 'CC',
+ 'user_id' => 1,
+ 'account_type_id' => $type->id,
+
+ ]
+ );
+
+ $false = new Configuration;
+ $false->data = false;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_cc_liabilities', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_cc_liabilities', true]);
+
+
+ $this->artisan('firefly-iii:cc-liabilities')
+ ->expectsOutput(sprintf('Converted credit card liability account "%s" (#%d) to generic debt liability.', $account->name, $account->id))
+ ->expectsOutput('Credit card liability types are no longer supported and have been converted to generic debts. See: http://bit.ly/FF3-credit-cards')
+ ->assertExitCode(0);
+
+ // verify new type.
+ $this->assertCount(0, Account::where('id', $account->id)->where('account_type_id', $type->id)->get());
+
+ $account->forceDelete();
+ $type->forceDelete();
+
+ }
+}
\ No newline at end of file
diff --git a/tests/Unit/Console/Commands/Upgrade/JournalCurrenciesTest.php b/tests/Unit/Console/Commands/Upgrade/JournalCurrenciesTest.php
new file mode 100644
index 0000000000..9b7c00a2eb
--- /dev/null
+++ b/tests/Unit/Console/Commands/Upgrade/JournalCurrenciesTest.php
@@ -0,0 +1,354 @@
+.
+ */
+
+namespace Tests\Unit\Console\Commands\Upgrade;
+
+
+use FireflyConfig;
+use FireflyIII\Models\Account;
+use FireflyIII\Models\Configuration;
+use FireflyIII\Models\Transaction;
+use FireflyIII\Models\TransactionCurrency;
+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 JournalCurrenciesTest
+ */
+class JournalCurrenciesTest extends TestCase
+{
+ /**
+ *
+ */
+ public function setUp(): void
+ {
+ parent::setUp();
+ Log::info(sprintf('Now in %s.', get_class($this)));
+ }
+
+ /**
+ * Basic run. Would not change anything.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\JournalCurrencies
+ */
+ public function testHandle(): void
+ {
+ // mock classes
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $currencyRepos = $this->mock(CurrencyRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $euro = TransactionCurrency::find(1);
+
+ // update transfer if necessary for the test:
+ $false = new Configuration;
+ $false->data = false;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_journal_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_journal_currencies', true]);
+
+ // mock stuff
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::TRANSFER]])
+ ->andReturn(new Collection);
+
+ // for the "other journals" check, return nothing.
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]])
+ ->andReturn(new Collection);
+
+ // transaction would be verified, nothing more.
+ $this->artisan('firefly-iii:journal-currencies')
+ ->expectsOutput('All transactions are correct.')
+ ->assertExitCode(0);
+ // nothing changed, so no verification.
+
+ }
+
+ /**
+ * Submit a single transfer which has no issues.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\JournalCurrencies
+ */
+ public function testHandleTransfer(): void
+ {
+ // mock classes
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $currencyRepos = $this->mock(CurrencyRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $euro = TransactionCurrency::find(1);
+ $transfer = $this->getRandomTransfer();
+
+ // update transfer if necessary for the test:
+ $collection = new Collection([$transfer]);
+ $false = new Configuration;
+ $false->data = false;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_journal_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_journal_currencies', true]);
+
+ // mock stuff
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $journalRepos->shouldReceive('setUser')->atLeast()->once();
+ $currencyRepos->shouldReceive('setUser')->atLeast()->once();
+
+ // return single tranfer
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::TRANSFER]])
+ ->andReturn($collection);
+
+ // for the "other journals" check, return nothing.
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]])
+ ->andReturn(new Collection);
+
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->withArgs([Mockery::any(), 'currency_id'])->andReturn($euro->id);
+ $currencyRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($euro);
+
+ // transaction would be verified, nothing more.
+ $this->artisan('firefly-iii:journal-currencies')
+ ->expectsOutput('Verified 1 transaction(s) and journal(s).')
+ ->assertExitCode(0);
+ // nothing changed, so no verification.
+ }
+
+ /**
+ * Submit a single transfer where the source account has no currency preference.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\JournalCurrencies
+ */
+ public function testHandleTransferSourceNoPref(): void
+ {
+ // mock classes
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $currencyRepos = $this->mock(CurrencyRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $euro = TransactionCurrency::find(1);
+ $transfer = $this->getRandomTransfer();
+
+ // edit source to remove currency preference:
+ /** @var Account $source */
+ $source = $transfer->transactions()->where('amount', '<', 0)->first()->account;
+// AccountMeta::where('account_id', $source->id)->where('name', 'currency_id')->delete();
+
+ // update transfer if necessary for the test:
+ $collection = new Collection([$transfer]);
+ $false = new Configuration;
+ $false->data = false;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_journal_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_journal_currencies', true]);
+
+ // mock stuff
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $journalRepos->shouldReceive('setUser')->atLeast()->once();
+ $currencyRepos->shouldReceive('setUser')->atLeast()->once();
+
+ // return single transfer
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::TRANSFER]])
+ ->andReturn($collection);
+
+ // for the "other journals" check, return nothing.
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]])
+ ->andReturn(new Collection);
+
+ // return NULL for first currency ID and currency.
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->withArgs([Mockery::any(), 'currency_id'])->andReturnNull();
+ $currencyRepos->shouldReceive('findNull')->atLeast()->once()->andReturnNull();
+
+ // transaction would be verified, nothing more.
+ $this->artisan('firefly-iii:journal-currencies')
+ ->expectsOutput(sprintf('Account #%d ("%s") must have currency preference but has none.', $source->id, $source->name))
+ ->assertExitCode(0);
+ // nothing changed, so no verification.
+
+ }
+
+ /**
+ * Submit a single transfer where the source transaction has no currency set.
+ * Because this is not done over repositories, we must edit the DB.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\JournalCurrencies
+ */
+ public function testHandleTransferSourceNoCurrency(): void
+ {
+ // mock classes
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $currencyRepos = $this->mock(CurrencyRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $euro = TransactionCurrency::find(1);
+ $transfer = $this->getRandomTransfer();
+ /** @var Transaction $source */
+ $source = $transfer->transactions()->where('amount', '<', 0)->first();
+ $source->transaction_currency_id = null;
+ $source->save();
+
+ // update transfer if necessary for the test:
+ $collection = new Collection([$transfer]);
+ $false = new Configuration;
+ $false->data = false;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_journal_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_journal_currencies', true]);
+
+ // mock stuff
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $journalRepos->shouldReceive('setUser')->atLeast()->once();
+ $currencyRepos->shouldReceive('setUser')->atLeast()->once();
+
+ // return single tranfer
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::TRANSFER]])
+ ->andReturn($collection);
+
+ // for the "other journals" check, return nothing.
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]])
+ ->andReturn(new Collection);
+
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->withArgs([Mockery::any(), 'currency_id'])->andReturn($euro->id);
+ $currencyRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($euro);
+
+ // transaction would be verified, nothing more.
+ $this->artisan('firefly-iii:journal-currencies')
+ ->expectsOutput(sprintf('Transaction #%d has no currency setting, now set to %s.', $source->id, $euro->code))
+ ->expectsOutput('Verified 2 transaction(s) and journal(s).')
+ ->assertExitCode(0);
+
+ // check transaction
+ $this->assertCount(1, Transaction::where('id', $source->id)->where('transaction_currency_id', $euro->id)->get());
+ }
+
+ /**
+ * Submit a single transfer where the source transaction has a different currency than the source account does.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\JournalCurrencies
+ */
+ public function testHandleMismatchedTransfer(): void
+ {
+ // mock classes
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $currencyRepos = $this->mock(CurrencyRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $euro = TransactionCurrency::find(1);
+ $usd = TransactionCurrency::where('code', 'USD')->first();
+ $transfer = $this->getRandomTransfer();
+
+ /** @var Transaction $source */
+ $source = $transfer->transactions()->where('amount', '<', 0)->first();
+ $source->transaction_currency_id = $usd->id;
+ $source->save();
+
+ // update transfer if necessary for the test:
+ $collection = new Collection([$transfer]);
+ $false = new Configuration;
+ $false->data = false;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_journal_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_journal_currencies', true]);
+
+ // mock stuff
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $journalRepos->shouldReceive('setUser')->atLeast()->once();
+ $currencyRepos->shouldReceive('setUser')->atLeast()->once();
+
+ // return single tranfer
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::TRANSFER]])
+ ->andReturn($collection);
+
+ // for the "other journals" check, return nothing.
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]])
+ ->andReturn(new Collection);
+
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->withArgs([Mockery::any(), 'currency_id'])->andReturn($euro->id);
+ $currencyRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($euro);
+
+ // transaction would be verified, nothing more.
+ $this->artisan('firefly-iii:journal-currencies')
+ ->expectsOutput(
+ sprintf(
+ 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
+ $source->id,
+ $source->transaction_currency_id,
+ $euro->id,
+ $source->amount
+ )
+ )
+ ->expectsOutput('Verified 2 transaction(s) and journal(s).')
+ ->assertExitCode(0);
+ // nothing changed, so no verification.
+ }
+
+ /**
+ * Submit a single transfer where the destination account has no currency preference.
+ *
+ * @covers \FireflyIII\Console\Commands\Upgrade\JournalCurrencies
+ */
+ public function testHandleTransferNoDestinationCurrency(): void
+ {
+ // mock classes
+ $accountRepos = $this->mock(AccountRepositoryInterface::class);
+ $currencyRepos = $this->mock(CurrencyRepositoryInterface::class);
+ $journalRepos = $this->mock(JournalRepositoryInterface::class);
+ $euro = TransactionCurrency::find(1);
+ $transfer = $this->getRandomTransfer();
+
+ /** @var Account $destination */
+ $destination = $transfer->transactions()->where('amount', '>', 0)->first()->account;
+
+ // update transfer if necessary for the test:
+ $collection = new Collection([$transfer]);
+ $false = new Configuration;
+ $false->data = false;
+ FireflyConfig::shouldReceive('get')->withArgs(['4780_journal_currencies', false])->andReturn($false);
+ FireflyConfig::shouldReceive('set')->withArgs(['4780_journal_currencies', true]);
+
+ // mock stuff
+ $accountRepos->shouldReceive('setUser')->atLeast()->once();
+ $journalRepos->shouldReceive('setUser')->atLeast()->once();
+ $currencyRepos->shouldReceive('setUser')->atLeast()->once();
+
+ // return single tranfer
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::TRANSFER]])
+ ->andReturn($collection);
+
+ // for the "other journals" check, return nothing.
+ $journalRepos->shouldReceive('getAllJournals')->atLeast()->once()
+ ->withArgs([[TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,]])
+ ->andReturn(new Collection);
+
+ $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->withArgs([Mockery::any(), 'currency_id'])->andReturn($euro->id, 0);
+ $currencyRepos->shouldReceive('findNull')->atLeast()->once()->andReturn($euro, null);
+
+ // transaction would be verified, nothing more.
+ $this->artisan('firefly-iii:journal-currencies')
+ ->expectsOutput(sprintf('Account #%d ("%s") must have currency preference but has none.', $destination->id, $destination->name))
+ ->assertExitCode(0);
+ // nothing changed, so no verification.
+ }
+
+
+}
\ No newline at end of file