Improve test coverage.

This commit is contained in:
James Cole
2019-06-10 20:14:00 +02:00
parent 8efb73694d
commit 2ab9d2e6ee
75 changed files with 4672 additions and 484 deletions

View File

@@ -59,7 +59,7 @@ fi
#env $(grep -v "^\#" .env | xargs) #env $(grep -v "^\#" .env | xargs)
php artisan cache:clear php artisan cache:clear
php artisan migrate --seed php artisan migrate --seed
php artisan firefly:decrypt-all php artisan firefly-iii:decrypt-all
# upgrade database commands: # upgrade database commands:
php artisan firefly-iii:transaction-identifiers php artisan firefly-iii:transaction-identifiers

View File

@@ -27,7 +27,6 @@ namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRequest; use FireflyIII\Api\V1\Requests\DateRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
@@ -74,7 +73,6 @@ class AccountController extends Controller
* @param DateRequest $request * @param DateRequest $request
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
*/ */
public function expenseOverview(DateRequest $request): JsonResponse public function expenseOverview(DateRequest $request): JsonResponse
{ {
@@ -161,7 +159,6 @@ class AccountController extends Controller
* @param DateRequest $request * @param DateRequest $request
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
*/ */
public function overview(DateRequest $request): JsonResponse public function overview(DateRequest $request): JsonResponse
{ {
@@ -224,7 +221,6 @@ class AccountController extends Controller
* @param DateRequest $request * @param DateRequest $request
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
*/ */
public function revenueOverview(DateRequest $request): JsonResponse public function revenueOverview(DateRequest $request): JsonResponse
{ {

View File

@@ -27,11 +27,9 @@ namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRequest; use FireflyIII\Api\V1\Requests\DateRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
@@ -66,7 +64,6 @@ class CategoryController extends Controller
* @param DateRequest $request * @param DateRequest $request
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
*/ */
public function overview(DateRequest $request): JsonResponse public function overview(DateRequest $request): JsonResponse
{ {

View File

@@ -128,7 +128,7 @@ class CurrencyController extends Controller
// filter list on currency preference: // filter list on currency preference:
$collection = $unfiltered->filter( $collection = $unfiltered->filter(
function (Account $account) use ($currency, $accountRepository) { static function (Account $account) use ($currency, $accountRepository) {
$currencyId = (int)$accountRepository->getMetaValue($account, 'currency_id'); $currencyId = (int)$accountRepository->getMetaValue($account, 'currency_id');
return $currencyId === $currency->id; return $currencyId === $currency->id;
@@ -229,7 +229,7 @@ class CurrencyController extends Controller
// filter and paginate list: // filter and paginate list:
$collection = $unfiltered->filter( $collection = $unfiltered->filter(
function (Bill $bill) use ($currency) { static function (Bill $bill) use ($currency) {
return $bill->transaction_currency_id === $currency->id; return $bill->transaction_currency_id === $currency->id;
} }
); );
@@ -505,7 +505,7 @@ class CurrencyController extends Controller
// filter selection // filter selection
$collection = $unfiltered->filter( $collection = $unfiltered->filter(
function (Recurrence $recurrence) use ($currency) { static function (Recurrence $recurrence) use ($currency) {
/** @var RecurrenceTransaction $transaction */ /** @var RecurrenceTransaction $transaction */
foreach ($recurrence->recurrenceTransactions as $transaction) { foreach ($recurrence->recurrenceTransactions as $transaction) {
if ($transaction->transaction_currency_id === $currency->id || $transaction->foreign_currency_id === $currency->id) { if ($transaction->transaction_currency_id === $currency->id || $transaction->foreign_currency_id === $currency->id) {
@@ -560,7 +560,7 @@ class CurrencyController extends Controller
$unfiltered = $repository->getAll(); $unfiltered = $repository->getAll();
$collection = $unfiltered->filter( $collection = $unfiltered->filter(
function (Rule $rule) use ($currency) { static function (Rule $rule) use ($currency) {
/** @var RuleTrigger $trigger */ /** @var RuleTrigger $trigger */
foreach ($rule->ruleTriggers as $trigger) { foreach ($rule->ruleTriggers as $trigger) {
if ('currency_is' === $trigger->trigger_type && $currency->name === $trigger->trigger_value) { if ('currency_is' === $trigger->trigger_type && $currency->name === $trigger->trigger_value) {

View File

@@ -52,7 +52,7 @@ class PreferenceController extends Controller
{ {
parent::__construct(); parent::__construct();
$this->middleware( $this->middleware(
function ($request, $next) { static function ($request, $next) {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);

View File

@@ -259,6 +259,7 @@ class RuleGroupController extends Controller
$matcher->setAccounts($parameters['accounts']); $matcher->setAccounts($parameters['accounts']);
$result = $matcher->findTransactionsByRule(); $result = $matcher->findTransactionsByRule();
/** @noinspection AdditionOperationOnArraysInspection */
$matchingTransactions = $result + $matchingTransactions; $matchingTransactions = $result + $matchingTransactions;
} }

View File

@@ -28,7 +28,6 @@ namespace FireflyIII\Api\V1\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use Exception; use Exception;
use FireflyIII\Api\V1\Requests\DateRequest; use FireflyIII\Api\V1\Requests\DateRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface; use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
@@ -41,7 +40,6 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
@@ -86,10 +84,9 @@ class SummaryController extends Controller
} }
/** /**
* @param Request $request * @param DateRequest $request
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
* @throws Exception * @throws Exception
*/ */
public function basic(DateRequest $request): JsonResponse public function basic(DateRequest $request): JsonResponse

View File

@@ -26,7 +26,6 @@ namespace FireflyIII\Api\V1\Controllers;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Api\V1\Requests\DateRequest; use FireflyIII\Api\V1\Requests\DateRequest;
use FireflyIII\Api\V1\Requests\TagRequest; use FireflyIII\Api\V1\Requests\TagRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Tag; use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface;
@@ -79,7 +78,6 @@ class TagController extends Controller
* @param DateRequest $request * @param DateRequest $request
* *
* @return JsonResponse * @return JsonResponse
* @throws FireflyException
*/ */
public function cloud(DateRequest $request): JsonResponse public function cloud(DateRequest $request): JsonResponse
{ {

View File

@@ -105,7 +105,7 @@ class TransactionLinkController extends Controller
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
// read type from URI // read type from URI
$name = $request->get('name') ?? null; $name = $request->get('name');
// types to get, page size: // types to get, page size:
$pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;

View File

@@ -56,7 +56,7 @@ class AccountStoreRequest extends Request
$ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes')));
$rules = [ $rules = [
'name' => 'required|min:1|uniqueAccountForUser', 'name' => 'required|min:1|uniqueAccountForUser',
'type' => sprintf('in:%s', $types), 'type' => 'required|' . sprintf('in:%s', $types),
'iban' => 'iban|nullable', 'iban' => 'iban|nullable',
'bic' => 'bic|nullable', 'bic' => 'bic|nullable',
'account_number' => 'between:1,255|nullable|uniqueAccountNumberForUser', 'account_number' => 'between:1,255|nullable|uniqueAccountNumberForUser',

View File

@@ -119,7 +119,7 @@ class BillRequest extends Request
public function withValidator(Validator $validator): void public function withValidator(Validator $validator): void
{ {
$validator->after( $validator->after(
function (Validator $validator) { static function (Validator $validator) {
$data = $validator->getData(); $data = $validator->getData();
$min = (float)($data['amount_min'] ?? 0); $min = (float)($data['amount_min'] ?? 0);
$max = (float)($data['amount_max'] ?? 0); $max = (float)($data['amount_max'] ?? 0);

View File

@@ -123,6 +123,7 @@ class RuleGroupTestRequest extends Request
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int)$accountId); $account = $accountRepository->findNull((int)$accountId);
if ($this->validAccount($account)) { if ($this->validAccount($account)) {
/** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account); $accounts->push($account);
} }

View File

@@ -123,6 +123,7 @@ class RuleTestRequest extends Request
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int)$accountId); $account = $accountRepository->findNull((int)$accountId);
if ($this->validAccount($account)) { if ($this->validAccount($account)) {
/** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account); $accounts->push($account);
} }

View File

@@ -98,6 +98,7 @@ class RuleTriggerRequest extends Request
Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); Log::debug(sprintf('Searching for asset account with id "%s"', $accountId));
$account = $accountRepository->findNull((int)$accountId); $account = $accountRepository->findNull((int)$accountId);
if ($this->validAccount($account)) { if ($this->validAccount($account)) {
/** @noinspection NullPointerExceptionInspection */
Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name));
$accounts->push($account); $accounts->push($account);
} }

View File

@@ -30,6 +30,7 @@ use Schema;
/** /**
* Class CorrectDatabase * Class CorrectDatabase
* @codeCoverageIgnore
*/ */
class CorrectDatabase extends Command class CorrectDatabase extends Command
{ {
@@ -38,7 +39,7 @@ class CorrectDatabase extends Command
* *
* @var string * @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. * The name and signature of the console command.
* *

View File

@@ -22,6 +22,7 @@
namespace FireflyIII\Console\Commands\Correction; namespace FireflyIII\Console\Commands\Correction;
use Exception; use Exception;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@@ -35,7 +36,7 @@ class CreateAccessTokens extends Command
* *
* @var string * @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. * The name and signature of the console command.
* *
@@ -51,9 +52,13 @@ class CreateAccessTokens extends Command
*/ */
public function handle(): int public function handle(): int
{ {
// make repository:
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
$start = microtime(true); $start = microtime(true);
$count = 0; $count = 0;
$users = User::get(); $users= $repository->all();
/** @var User $user */ /** @var User $user */
foreach ($users as $user) { foreach ($users as $user) {
$pref = app('preferences')->getForUser($user, 'access_token', null); $pref = app('preferences')->getForUser($user, 'access_token', null);

View File

@@ -58,12 +58,13 @@ class CreateLinkTypes extends Command
'Reimbursement' => ['(partially) reimburses', 'is (partially) reimbursed by'], 'Reimbursement' => ['(partially) reimburses', 'is (partially) reimbursed by'],
]; ];
foreach ($set as $name => $values) { 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) { if (null === $link) {
$link = new LinkType; $link = new LinkType;
$link->name = $name; $link->name = $name;
$link->outward = $values[0];
$link->inward = $values[1]; $link->inward = $values[1];
$link->outward = $values[0];
++$count; ++$count;
$this->line(sprintf('Created missing link type "%s"', $name)); $this->line(sprintf('Created missing link type "%s"', $name));
} }

View File

@@ -59,7 +59,7 @@ class DeleteEmptyGroups extends Command
$this->info('No empty transaction groups.'); $this->info('No empty transaction groups.');
} }
if ($count > 0) { 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(); TransactionGroup::whereNull('deleted_at')->whereNotIn('id', $groups)->delete();
} }
$end = round(microtime(true) - $start, 2); $end = round(microtime(true) - $start, 2);

View File

@@ -72,9 +72,12 @@ class DeleteEmptyJournals extends Command
foreach ($set as $entry) { foreach ($set as $entry) {
try { try {
TransactionJournal::find($entry->id)->delete(); TransactionJournal::find($entry->id)->delete();
// @codeCoverageIgnoreStart
} catch (Exception $e) { } catch (Exception $e) {
Log::info(sprintf('Could not delete entry: %s', $e->getMessage())); Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
} }
// @codeCoverageIgnoreEnd
$this->info(sprintf('Deleted empty transaction journal #%d', $entry->id)); $this->info(sprintf('Deleted empty transaction journal #%d', $entry->id));
++$count; ++$count;
} }
@@ -90,15 +93,6 @@ class DeleteEmptyJournals extends Command
*/ */
private function deleteUnevenJournals(): void 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 $set = Transaction
::whereNull('deleted_at') ::whereNull('deleted_at')
->having('the_count', '!=', '2') ->having('the_count', '!=', '2')
@@ -111,9 +105,12 @@ class DeleteEmptyJournals extends Command
// uneven number, delete journal and transactions: // uneven number, delete journal and transactions:
try { try {
TransactionJournal::find((int)$row->transaction_journal_id)->delete(); TransactionJournal::find((int)$row->transaction_journal_id)->delete();
// @codeCoverageIgnoreStart
} catch(Exception $e) { } catch(Exception $e) {
Log::info(sprintf('Could not delete journal: %s', $e->getMessage())); Log::info(sprintf('Could not delete journal: %s', $e->getMessage()));
} }
// @codeCoverageIgnoreEnd
Transaction::where('transaction_journal_id', (int)$row->transaction_journal_id)->delete(); 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)); $this->info(sprintf('Deleted transaction journal #%d because it had an uneven number of transactions.', $row->transaction_journal_id));
$total++; $total++;

View File

@@ -25,8 +25,8 @@ use Exception;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use stdClass;
use Log; use Log;
use stdClass;
/** /**
* Deletes transactions where the journal has been deleted. * Deletes transactions where the journal has been deleted.
@@ -81,17 +81,20 @@ class DeleteOrphanedTransactions extends Command
if ($journal) { if ($journal) {
try { try {
$journal->delete(); $journal->delete();
// @codeCoverageIgnoreStart
} catch (Exception $e) { } 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(); Transaction::where('transaction_journal_id', (int)$transaction->transaction_journal_id)->delete();
$this->line( $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++; $count++;
} }
if(0===$count) { if (0 === $count) {
$this->info('No orphaned accounts.'); $this->info('No orphaned accounts.');
} }
} }

View File

@@ -47,7 +47,6 @@ class DeleteZeroAmount extends Command
/** /**
* Execute the console command. * Execute the console command.
* @throws Exception
* @return int * @return int
*/ */
public function handle(): int public function handle(): int
@@ -60,7 +59,13 @@ class DeleteZeroAmount extends Command
/** @var TransactionJournal $journal */ /** @var TransactionJournal $journal */
foreach ($journals as $journal) { foreach ($journals as $journal) {
$this->info(sprintf('Deleted transaction journal #%d because the amount is zero (0.00).', $journal->id)); $this->info(sprintf('Deleted transaction journal #%d because the amount is zero (0.00).', $journal->id));
try {
$journal->delete(); $journal->delete();
// @codeCoverageIgnoreStart
} catch (Exception $e) {
$this->line($e->getMessage());
}
// @codeCoverageIgnoreEnd
Transaction::where('transaction_journal_id', $journal->id)->delete(); Transaction::where('transaction_journal_id', $journal->id)->delete();
} }
if (0 === $journals->count()) { if (0 === $journals->count()) {

View File

@@ -86,9 +86,13 @@ class EnableCurrencies extends Command
$found = array_unique($found); $found = array_unique($found);
$this->info(sprintf('%d different currencies are currently in use.', count($found))); $this->info(sprintf('%d different currencies are currently in use.', count($found)));
$disabled = TransactionCurrency::whereIn('id', $found)->where('enabled', false)->count(); $disabled = TransactionCurrency::whereIn('id', $found)->where('enabled', false)->count();
if ($disabled > 0) { 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]); TransactionCurrency::whereIn('id', $found)->update(['enabled' => true]);

View File

@@ -52,82 +52,16 @@ class FixAccountTypes extends Command
private $factory; private $factory;
/** @var array */ /** @var array */
private $fixable; private $fixable;
/** @var int */
private $count;
/** /**
* @param TransactionJournal $journal * FixAccountTypes constructor.
* @param string $type
* @param Transaction $source
* @param Transaction $dest
* @throws FireflyException
*/ */
public function fixJournal(TransactionJournal $journal, string $type, Transaction $source, Transaction $dest): void public function __construct()
{ {
// variables: parent::__construct();
$combination = sprintf('%s%s%s', $type, $source->account->accountType->type, $dest->account->accountType->type); $this->count = 0;
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;
}
} }
/** /**
@@ -159,19 +93,103 @@ class FixAccountTypes extends Command
$this->expected = config('firefly.source_dests'); $this->expected = config('firefly.source_dests');
$journals = TransactionJournal $journals = TransactionJournal::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get();
::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get();
foreach ($journals as $journal) { foreach ($journals as $journal) {
$this->inspectJournal($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); $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; 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 * @param TransactionJournal $journal
* *
@@ -201,7 +219,7 @@ class FixAccountTypes extends Command
{ {
$count = $journal->transactions()->count(); $count = $journal->transactions()->count();
if (2 !== $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; return;
} }
@@ -213,9 +231,11 @@ class FixAccountTypes extends Command
$destAccount = $destTransaction->account; $destAccount = $destTransaction->account;
$destAccountType = $destAccount->accountType->type; $destAccountType = $destAccount->accountType->type;
if (!isset($this->expected[$type])) { if (!isset($this->expected[$type])) {
// @codeCoverageIgnoreStart
$this->info(sprintf('No source/destination info for transaction type %s.', $type)); $this->info(sprintf('No source/destination info for transaction type %s.', $type));
return; return;
// @codeCoverageIgnoreEnd
} }
if (!isset($this->expected[$type][$sourceAccountType])) { if (!isset($this->expected[$type][$sourceAccountType])) {
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);

View File

@@ -46,6 +46,9 @@ class FixPiggies extends Command
*/ */
protected $signature = 'firefly-iii:fix-piggies'; protected $signature = 'firefly-iii:fix-piggies';
/** @var int */
private $count;
/** /**
* Execute the console command. * Execute the console command.
* *
@@ -53,29 +56,43 @@ class FixPiggies extends Command
*/ */
public function handle(): int public function handle(): int
{ {
$this->count = 0;
$start = microtime(true); $start = microtime(true);
$set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get(); $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
$set->each(
function (PiggyBankEvent $event) { /** @var PiggyBankEvent $event */
foreach ($set as $event) {
if (null === $event->transaction_journal_id) { if (null === $event->transaction_journal_id) {
return true; continue;
} }
/** @var TransactionJournal $journal */ /** @var TransactionJournal $journal */
$journal = $event->transactionJournal()->first(); $journal = $event->transactionJournal;
// @codeCoverageIgnoreStart
if (null === $journal) { if (null === $journal) {
return true; $event->transaction_journal_id = null;
$event->save();
$this->count++;
continue;
} }
// @codeCoverageIgnoreEnd
$type = $journal->transactionType->type; $type = $journal->transactionType->type;
if (TransactionType::TRANSFER !== $type) { if (TransactionType::TRANSFER !== $type) {
$event->transaction_journal_id = null; $event->transaction_journal_id = null;
$event->save(); $event->save();
$this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id)); $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));
} }
return true;
}
);
$end = round(microtime(true) - $start, 2); $end = round(microtime(true) - $start, 2);
$this->line(sprintf('Verified the content of %d piggy bank events in %s seconds.', $set->count(), $end)); $this->line(sprintf('Verified the content of %d piggy bank events in %s seconds.', $set->count(), $end));

View File

@@ -22,6 +22,7 @@
namespace FireflyIII\Console\Commands\Correction; namespace FireflyIII\Console\Commands\Correction;
use DB; use DB;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@@ -84,7 +85,7 @@ class FixUnevenAmount extends Command
// one of the transactions is bad. // one of the transactions is bad.
$journal = TransactionJournal::find($param); $journal = TransactionJournal::find($param);
if (!$journal) { if (!$journal) {
return; return; // @codeCoverageIgnore
} }
/** @var Transaction $source */ /** @var Transaction $source */
$source = $journal->transactions()->where('amount', '<', 0)->first(); $source = $journal->transactions()->where('amount', '<', 0)->first();
@@ -96,7 +97,7 @@ class FixUnevenAmount extends Command
$destination->amount = $amount; $destination->amount = $amount;
$destination->save(); $destination->save();
$this->line(sprintf('Corrected amount in transaction journal #%d', $param)); $message = sprintf('Corrected amount in transaction journal #%d', $param);
$this->line($message);
} }
} }

View File

@@ -42,6 +42,9 @@ class RenameMetaFields extends Command
*/ */
protected $signature = 'firefly-iii:rename-meta-fields'; protected $signature = 'firefly-iii:rename-meta-fields';
/** @var int */
private $count;
/** /**
* Execute the console command. * Execute the console command.
* *
@@ -49,6 +52,7 @@ class RenameMetaFields extends Command
*/ */
public function handle(): int public function handle(): int
{ {
$this->count = 0;
$start = microtime(true); $start = microtime(true);
$changes = [ $changes = [
@@ -67,6 +71,12 @@ class RenameMetaFields extends Command
foreach ($changes as $original => $update) { foreach ($changes as $original => $update) {
$this->rename($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); $end = round(microtime(true) - $start, 2);
$this->info(sprintf('Renamed meta fields in %s seconds', $end)); $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 private function rename(string $original, string $update): void
{ {
DB::table('journal_meta') $count = DB::table('journal_meta')
->where('name', '=', $original) ->where('name', '=', $original)
->update(['name' => $update]); ->update(['name' => $update]);
$this->count += $count;
} }
} }

View File

@@ -66,6 +66,9 @@ class TransferBudgets extends Command
if (0 === $count) { if (0 === $count) {
$this->info('No invalid budget/journal entries.'); $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); $end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified budget/journals in %s seconds.', $end)); $this->info(sprintf('Verified budget/journals in %s seconds.', $end));

View File

@@ -49,15 +49,15 @@ class DecryptDatabase extends Command
* *
* @var string * @var string
*/ */
protected $signature = 'firefly:decrypt-all'; protected $signature = 'firefly-iii:decrypt-all';
/** /**
* Execute the console command. * Execute the console command.
* *
* @return mixed * @return int
* @throws FireflyException * @throws FireflyException
*/ */
public function handle() public function handle(): int
{ {
$this->line('Going to decrypt the database.'); $this->line('Going to decrypt the database.');
$tables = [ $tables = [
@@ -151,7 +151,7 @@ class DecryptDatabase extends Command
$value = Crypt::decrypt($value); $value = Crypt::decrypt($value);
} catch (DecryptException $e) { } catch (DecryptException $e) {
if ('The MAC is invalid.' === $e->getMessage()) { 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())); Log::debug(sprintf('Could not decrypt. %s', $e->getMessage()));
} }

View File

@@ -28,18 +28,17 @@ namespace FireflyIII\Console\Commands\Import;
use Exception; use Exception;
use FireflyIII\Console\Commands\VerifiesAccessToken; use FireflyIII\Console\Commands\VerifiesAccessToken;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Import\Prerequisites\PrerequisitesInterface;
use FireflyIII\Import\Routine\RoutineInterface; use FireflyIII\Import\Routine\RoutineInterface;
use FireflyIII\Import\Storage\ImportArrayStorage; use FireflyIII\Import\Storage\ImportArrayStorage;
use FireflyIII\Models\ImportJob;
use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Log; use Log;
/** /**
* Class CreateCSVImport. * Class CreateCSVImport.
*
* @codeCoverageIgnore
*/ */
class CreateCSVImport extends Command class CreateCSVImport extends Command
{ {
@@ -50,7 +49,6 @@ class CreateCSVImport extends Command
* @var string * @var string
*/ */
protected $description = 'Use this command to create a new CSV file import.'; protected $description = 'Use this command to create a new CSV file import.';
/** /**
* The name and signature of the console command. * 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.} {configuration? : The configuration file to use for the import.}
{--user=1 : The user ID that the import should import for.} {--user=1 : The user ID that the import should import for.}
{--token= : The user\'s access token.}'; {--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. * Run the command.
*
* @throws FireflyException
*/ */
public function handle(): int public function handle(): int
{ {
// @codeCoverageIgnoreStart
if (!$this->verifyAccessToken()) { if (!$this->verifyAccessToken()) {
$this->errorLine('Invalid access token.'); $this->errorLine('Invalid access token.');
return 1; 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()) { if (!$this->validArguments()) {
$this->errorLine('Invalid arguments.'); $this->errorLine('Invalid arguments.');
return 1; return 1;
} }
if ('' !== $configuration) { // @codeCoverageIgnoreEnd
/** @var User $user */
$user = $this->userRepository->findNull((int)$this->option('user'));
$file = (string)$this->argument('file');
$configuration = (string)$this->argument('configuration');
$this->importRepository->setUser($user);
$configurationData = json_decode(file_get_contents($configuration), true); $configurationData = json_decode(file_get_contents($configuration), true);
if (null === $configurationData) { $this->importJob = $this->importRepository->create('file');
$this->errorLine(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd));
// 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; 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.'); $this->infoLine('The import routine has started. The process is not visible. Please wait.');
Log::debug('Go for import!'); 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" // 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 { try {
$routine->run(); $this->processFile();
} catch (FireflyException|Exception $e) { } catch (FireflyException $e) {
$message = 'The import routine crashed: ' . $e->getMessage(); $this->errorLine($e->getMessage());
Log::error($message);
Log::error($e->getTraceAsString());
// set job errored out:
$jobRepository->setStatus($importJob, 'error');
$this->errorLine($message);
return 1; return 1;
} }
$count++;
}
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 { try {
$storage->store(); $this->storeData();
} catch (FireflyException|Exception $e) { } catch (FireflyException $e) {
$message = 'The import routine crashed: ' . $e->getMessage(); $this->errorLine($e->getMessage());
Log::error($message);
Log::error($e->getTraceAsString());
// set job errored out:
$jobRepository->setStatus($importJob, 'error');
$this->errorLine($message);
return 1; return 1;
} }
// set storage to be finished:
$jobRepository->setStatus($importJob, 'storage_finished');
}
// give feedback: // give feedback:
$this->infoLine('Job has finished.'); $this->giveFeedback();
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));
}
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: // clear cache for user:
app('preferences')->setForUser($user, 'lastActivity', microtime()); app('preferences')->setForUser($user, 'lastActivity', microtime());
@@ -234,6 +159,7 @@ class CreateCSVImport extends Command
/** /**
* @param string $message * @param string $message
* @param array|null $data * @param array|null $data
* @codeCoverageIgnore
*/ */
private function errorLine(string $message, array $data = null): void private function errorLine(string $message, array $data = null): void
{ {
@@ -245,6 +171,7 @@ class CreateCSVImport extends Command
/** /**
* @param string $message * @param string $message
* @param array $data * @param array $data
* @codeCoverageIgnore
*/ */
private function infoLine(string $message, array $data = null): void private function infoLine(string $message, array $data = null): void
{ {
@@ -257,6 +184,7 @@ class CreateCSVImport extends Command
* *
* @noinspection MultipleReturnStatementsInspection * @noinspection MultipleReturnStatementsInspection
* @return bool * @return bool
* @codeCoverageIgnore
*/ */
private function validArguments(): bool private function validArguments(): bool
{ {
@@ -283,6 +211,119 @@ class CreateCSVImport extends Command
return false; 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; 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);
}
}
}
} }

View File

@@ -80,9 +80,8 @@ class ReportEmptyObjects extends Command
/** @var stdClass $entry */ /** @var stdClass $entry */
foreach ($set as $entry) { foreach ($set as $entry) {
$name = $entry->name;
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.'; $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); $this->line($line);
} }
} }
@@ -125,13 +124,12 @@ class ReportEmptyObjects extends Command
/** @var stdClass $entry */ /** @var stdClass $entry */
foreach ($set as $entry) { foreach ($set as $entry) {
$objName = $entry->name;
$line = sprintf( $line = sprintf(
'User #%d (%s) has budget #%d ("%s") which has no transaction journals.', 'User #%d (%s) has budget #%d ("%s") which has no transaction journals.',
$entry->user_id, $entry->user_id,
$entry->email, $entry->email,
$entry->id, $entry->id,
$objName $entry->name
); );
$this->line($line); $this->line($line);
} }
@@ -151,14 +149,12 @@ class ReportEmptyObjects extends Command
/** @var stdClass $entry */ /** @var stdClass $entry */
foreach ($set as $entry) { foreach ($set as $entry) {
$objName = $entry->name;
$line = sprintf( $line = sprintf(
'User #%d (%s) has category #%d ("%s") which has no transaction journals.', 'User #%d (%s) has category #%d ("%s") which has no transaction journals.',
$entry->user_id, $entry->user_id,
$entry->email, $entry->email,
$entry->id, $entry->id,
$objName $entry->name
); );
$this->line($line); $this->line($line);
} }
@@ -178,14 +174,13 @@ class ReportEmptyObjects extends Command
/** @var stdClass $entry */ /** @var stdClass $entry */
foreach ($set as $entry) { foreach ($set as $entry) {
$objName = $entry->tag;
$line = sprintf( $line = sprintf(
'User #%d (%s) has tag #%d ("%s") which has no transaction journals.', 'User #%d (%s) has tag #%d ("%s") which has no transaction journals.',
$entry->user_id, $entry->user_id,
$entry->email, $entry->email,
$entry->id, $entry->id,
$objName $entry->tag
); );
$this->line($line); $this->line($line);
} }

View File

@@ -30,6 +30,7 @@ use Artisan;
/** /**
* Class ReportIntegrity * Class ReportIntegrity
* @codeCoverageIgnore
*/ */
class ReportIntegrity extends Command class ReportIntegrity extends Command
{ {

View File

@@ -75,8 +75,6 @@ class ApplyRules extends Command
private $acceptedAccounts; private $acceptedAccounts;
/** @var Carbon */ /** @var Carbon */
private $endDate; private $endDate;
/** @var Collection */
private $results;
/** @var array */ /** @var array */
private $ruleGroupSelection; private $ruleGroupSelection;
/** @var array */ /** @var array */
@@ -106,7 +104,6 @@ class ApplyRules extends Command
$this->accounts = new Collection; $this->accounts = new Collection;
$this->ruleSelection = []; $this->ruleSelection = [];
$this->ruleGroupSelection = []; $this->ruleGroupSelection = [];
$this->results = new Collection;
$this->ruleRepository = app(RuleRepositoryInterface::class); $this->ruleRepository = app(RuleRepositoryInterface::class);
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class); $this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
$this->acceptedAccounts = [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE]; $this->acceptedAccounts = [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE];
@@ -121,11 +118,14 @@ class ApplyRules extends Command
*/ */
public function handle(): int public function handle(): int
{ {
// @codeCoverageIgnoreStart
if (!$this->verifyAccessToken()) { if (!$this->verifyAccessToken()) {
$this->error('Invalid access token.'); $this->error('Invalid access token.');
return 1; return 1;
} }
// @codeCoverageIgnoreEnd
// set user: // set user:
$this->ruleRepository->setUser($this->getUser()); $this->ruleRepository->setUser($this->getUser());
$this->ruleGroupRepository->setUser($this->getUser()); $this->ruleGroupRepository->setUser($this->getUser());
@@ -141,24 +141,8 @@ class ApplyRules extends Command
$this->grabAllRules(); $this->grabAllRules();
// loop all groups and rules and indicate if they're included: // loop all groups and rules and indicate if they're included:
$count = 0; $rulesToApply = $this->getRulesToApply();
$rulesToApply = []; $count = count($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));
}
}
}
if (0 === $count) { if (0 === $count) {
$this->error('No rules or rule groups have been included.'); $this->error('No rules or rule groups have been included.');
$this->warn('Make a selection using:'); $this->warn('Make a selection using:');
@@ -167,7 +151,6 @@ class ApplyRules extends Command
$this->warn(' --all_rules'); $this->warn(' --all_rules');
} }
// get transactions from asset accounts.
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->getUser()); $collector->setUser($this->getUser());
@@ -176,7 +159,7 @@ class ApplyRules extends Command
$journals = $collector->getExtractedJournals(); $journals = $collector->getExtractedJournals();
// start running rules. // 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. // start looping.
/** @var RuleEngine $ruleEngine */ /** @var RuleEngine $ruleEngine */
@@ -191,6 +174,7 @@ class ApplyRules extends Command
Log::debug('Start of new journal.'); Log::debug('Start of new journal.');
$ruleEngine->processJournalArray($journal); $ruleEngine->processJournalArray($journal);
Log::debug('Done with all rules for this group + done with journal.'); Log::debug('Done with all rules for this group + done with journal.');
/** @noinspection DisconnectedForeachInstructionInspection */
$bar->advance(); $bar->advance();
} }
$this->line(''); $this->line('');
@@ -212,16 +196,10 @@ class ApplyRules extends Command
} }
// verify rule groups. // verify rule groups.
$result = $this->verifyInputRuleGroups(); $this->verifyInputRuleGroups();
if (false === $result) {
return $result;
}
// verify rules. // verify rules.
$result = $this->verifyInputRules(); $this->verifyInputRules();
if (false === $result) {
return $result;
}
$this->verifyInputDates(); $this->verifyInputDates();
@@ -243,11 +221,13 @@ class ApplyRules extends Command
$finalList = new Collection; $finalList = new Collection;
$accountList = explode(',', $accountString); $accountList = explode(',', $accountString);
// @codeCoverageIgnoreStart
if (0 === count($accountList)) { if (0 === count($accountList)) {
$this->error('Please use the --accounts option to indicate the accounts to apply rules to.'); $this->error('Please use the --accounts option to indicate the accounts to apply rules to.');
return false; return false;
} }
// @codeCoverageIgnoreEnd
/** @var AccountRepositoryInterface $accountRepository */ /** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class); $accountRepository = app(AccountRepositoryInterface::class);
@@ -284,11 +264,12 @@ class ApplyRules extends Command
return true; return true;
} }
$ruleGroupList = explode(',', $ruleGroupString); $ruleGroupList = explode(',', $ruleGroupString);
// @codeCoverageIgnoreStart
if (0 === count($ruleGroupList)) { if (0 === count($ruleGroupList)) {
// can be empty. // can be empty.
return true; return true;
} }
// @codeCoverageIgnoreEnd
foreach ($ruleGroupList as $ruleGroupId) { foreach ($ruleGroupList as $ruleGroupId) {
$ruleGroup = $this->ruleGroupRepository->find((int)$ruleGroupId); $ruleGroup = $this->ruleGroupRepository->find((int)$ruleGroupId);
if ($ruleGroup->active) { if ($ruleGroup->active) {
@@ -314,11 +295,14 @@ class ApplyRules extends Command
} }
$ruleList = explode(',', $ruleString); $ruleList = explode(',', $ruleString);
// @codeCoverageIgnoreStart
if (0 === count($ruleList)) { if (0 === count($ruleList)) {
// can be empty. // can be empty.
return true; return true;
} }
// @codeCoverageIgnoreEnd
foreach ($ruleList as $ruleId) { foreach ($ruleList as $ruleId) {
$rule = $this->ruleRepository->find((int)$ruleId); $rule = $this->ruleRepository->find((int)$ruleId);
if (null !== $rule && $rule->active) { if (null !== $rule && $rule->active) {
@@ -383,4 +367,27 @@ class ApplyRules extends Command
in_array($rule->id, $this->ruleSelection, true) || in_array($rule->id, $this->ruleSelection, true) ||
$this->allRules; $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;
}
} }

View File

@@ -24,8 +24,10 @@ namespace FireflyIII\Console\Commands\Upgrade;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Log; use Log;
@@ -48,9 +50,23 @@ class AccountCurrencies extends Command
* @var string * @var string
*/ */
protected $signature = 'firefly-iii:account-currencies {--F|force : Force the execution of this command.}'; protected $signature = 'firefly-iii:account-currencies {--F|force : Force the execution of this command.}';
/** @var AccountRepositoryInterface */ /** @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. * 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,7 +75,6 @@ class AccountCurrencies extends Command
*/ */
public function handle(): int public function handle(): int
{ {
$this->repository = app(AccountRepositoryInterface::class);
$start = microtime(true); $start = microtime(true);
if ($this->isExecuted() && true !== $this->option('force')) { if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.'); $this->warn('This command has already been executed.');
@@ -69,11 +84,17 @@ class AccountCurrencies extends Command
Log::debug('Now in updateAccountCurrencies()'); Log::debug('Now in updateAccountCurrencies()');
$this->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); $end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified and fixed account currencies in %s seconds.', $end)); $this->info(sprintf('Verified and fixed account currencies in %s seconds.', $end));
$this->markAsExecuted(); $this->markAsExecuted();
return 0; return 0;
} }
@@ -105,36 +126,48 @@ class AccountCurrencies extends Command
*/ */
private function updateAccount(Account $account, TransactionCurrency $currency): void private function updateAccount(Account $account, TransactionCurrency $currency): void
{ {
$this->repository->setUser($account->user); $this->accountRepos->setUser($account->user);
$accountCurrency = (int)$this->repository->getMetaValue($account, 'currency_id'); $accountCurrency = (int)$this->accountRepos->getMetaValue($account, 'currency_id');
$openingBalance = $account->getOpeningBalance(); $openingBalance = $this->accountRepos->getOpeningBalance($account);
$obCurrency = 0;
if (null !== $openingBalance) {
$obCurrency = (int)$openingBalance->transaction_currency_id; $obCurrency = (int)$openingBalance->transaction_currency_id;
}
// both 0? set to default currency: // both 0? set to default currency:
if (0 === $accountCurrency && 0 === $obCurrency) { if (0 === $accountCurrency && 0 === $obCurrency) {
AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete(); AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete();
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $currency->id]); 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->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $currency->code));
$this->count++;
return; return;
} }
// account is set to 0, opening balance is not? // account is set to 0, opening balance is not?
if (0 === $accountCurrency && $obCurrency > 0) { if (0 === $accountCurrency && $obCurrency > 0) {
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]); 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; return;
} }
// do not match and opening balance id is not null. // do not match and opening balance id is not null.
if ($accountCurrency !== $obCurrency && $openingBalance->id > 0) { if ($accountCurrency !== $obCurrency && null !== $openingBalance) {
// update opening balance: // update opening balance:
$openingBalance->transaction_currency_id = $accountCurrency; $openingBalance->transaction_currency_id = $accountCurrency;
$openingBalance->save(); $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->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
$this->count++;
return;
} }
} }
@@ -143,9 +176,8 @@ class AccountCurrencies extends Command
*/ */
private function updateAccountCurrencies(): void private function updateAccountCurrencies(): void
{ {
$users = $this->userRepos->all();
$defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR'); $defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR');
$users = User::get();
foreach ($users as $user) { foreach ($users as $user) {
$this->updateCurrenciesForUser($user, $defaultCurrencyCode); $this->updateCurrenciesForUser($user, $defaultCurrencyCode);
} }
@@ -157,10 +189,8 @@ class AccountCurrencies extends Command
*/ */
private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void
{ {
$accounts = $user->accounts() $this->accountRepos->setUser($user);
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') $accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])
->get(['accounts.*']);
// get user's currency preference: // get user's currency preference:
$defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data; $defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data;

View File

@@ -54,6 +54,7 @@ class BackToJournals extends Command
*/ */
public function handle(): int public function handle(): int
{ {
// @codeCoverageIgnoreStart
$start = microtime(true); $start = microtime(true);
if (!$this->isMigrated()) { if (!$this->isMigrated()) {
$this->error('Please run firefly-iii:migrate-to-groups first.'); $this->error('Please run firefly-iii:migrate-to-groups first.');
@@ -66,6 +67,7 @@ class BackToJournals extends Command
if (true === $this->option('force')) { if (true === $this->option('force')) {
$this->warn('Forcing the command.'); $this->warn('Forcing the command.');
} }
// @codeCoverageIgnoreEnd
$this->migrateAll(); $this->migrateAll();
$end = round(microtime(true) - $start, 2); $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(); $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(); $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 private function migrateBudgets(): void
{ {
$journalIds = $this->getIdsForBudgets(); $journalIds = $this->getIdsForBudgets();
$journals = TransactionJournal::whereIn('id', $journalIds)->with(['transactions', 'budgets', 'transactions.budgets'])->get(); $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 */ /** @var TransactionJournal $journal */
foreach ($journals as $journal) { foreach ($journals as $journal) {
$this->migrateBudgetsForJournal($journal); $this->migrateBudgetsForJournal($journal);
@@ -163,26 +168,34 @@ class BackToJournals extends Command
*/ */
private function migrateBudgetsForJournal(TransactionJournal $journal): void private function migrateBudgetsForJournal(TransactionJournal $journal): void
{ {
// grab category from first transaction // grab category from first transaction
/** @var Transaction $transaction */ /** @var Transaction $transaction */
$transaction = $journal->transactions->first(); $transaction = $journal->transactions->first();
if (null === $transaction) { if (null === $transaction) {
// @codeCoverageIgnoreStart
$this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id)); $this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id));
return; return;
// @codeCoverageIgnoreEnd
} }
/** @var Budget $budget */ /** @var Budget $budget */
$budget = $transaction->budgets->first(); $budget = $transaction->budgets->first();
/** @var Budget $journalBudget */ /** @var Budget $journalBudget */
$journalBudget = $journal->budgets->first(); $journalBudget = $journal->budgets->first();
// both have a budget, but they don't match.
if (null !== $budget && null !== $journalBudget && $budget->id !== $journalBudget->id) { if (null !== $budget && null !== $journalBudget && $budget->id !== $journalBudget->id) {
// sync to journal: // sync to journal:
$journal->budgets()->sync([(int)$budget->id]); $journal->budgets()->sync([(int)$budget->id]);
return;
} }
// budget in transaction overrules journal. // transaction has a budget, but the journal doesn't.
if (null === $budget && null !== $journalBudget) { if (null !== $budget && null === $journalBudget) {
$journal->budgets()->sync([]); // sync to journal:
$journal->budgets()->sync([(int)$budget->id]);
} }
} }
@@ -193,7 +206,7 @@ class BackToJournals extends Command
{ {
$journalIds = $this->getIdsForCategories(); $journalIds = $this->getIdsForCategories();
$journals = TransactionJournal::whereIn('id', $journalIds)->with(['transactions', 'categories', 'transactions.categories'])->get(); $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 */ /** @var TransactionJournal $journal */
foreach ($journals as $journal) { foreach ($journals as $journal) {
$this->migrateCategoriesForJournal($journal); $this->migrateCategoriesForJournal($journal);
@@ -209,22 +222,26 @@ class BackToJournals extends Command
/** @var Transaction $transaction */ /** @var Transaction $transaction */
$transaction = $journal->transactions->first(); $transaction = $journal->transactions->first();
if (null === $transaction) { if (null === $transaction) {
// @codeCoverageIgnoreStart
$this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id)); $this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id));
return; return;
// @codeCoverageIgnoreEnd
} }
/** @var Category $category */ /** @var Category $category */
$category = $transaction->categories->first(); $category = $transaction->categories->first();
/** @var Category $journalCategory */ /** @var Category $journalCategory */
$journalCategory = $journal->categories->first(); $journalCategory = $journal->categories->first();
// both have a category, but they don't match.
if (null !== $category && null !== $journalCategory && $category->id !== $journalCategory->id) { if (null !== $category && null !== $journalCategory && $category->id !== $journalCategory->id) {
// sync to journal: // sync to journal:
$journal->categories()->sync([(int)$category->id]); $journal->categories()->sync([(int)$category->id]);
} }
// category in transaction overrules journal. // transaction has a category, but the journal doesn't.
if (null === $category && null !== $journalCategory) { if (null !== $category && null === $journalCategory) {
$journal->categories()->sync([]); $journal->categories()->sync([(int)$category->id]);
} }
} }
} }

View File

@@ -55,11 +55,14 @@ class BudgetLimitCurrency extends Command
public function handle(): int public function handle(): int
{ {
$start = microtime(true); $start = microtime(true);
// @codeCoverageIgnoreStart
if ($this->isExecuted() && true !== $this->option('force')) { if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.'); $this->warn('This command has already been executed.');
return 0; return 0;
} }
// @codeCoverageIgnoreEnd
$count = 0; $count = 0;
$budgetLimits = BudgetLimit::get(); $budgetLimits = BudgetLimit::get();
/** @var BudgetLimit $budgetLimit */ /** @var BudgetLimit $budgetLimit */

View File

@@ -58,14 +58,19 @@ class CCLiabilities extends Command
public function handle(): int public function handle(): int
{ {
$start = microtime(true); $start = microtime(true);
// @codeCoverageIgnoreStart
if ($this->isExecuted() && true !== $this->option('force')) { if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.'); $this->warn('This command has already been executed.');
return 0; return 0;
} }
// @codeCoverageIgnoreEnd
$ccType = AccountType::where('type', AccountType::CREDITCARD)->first(); $ccType = AccountType::where('type', AccountType::CREDITCARD)->first();
$debtType = AccountType::where('type', AccountType::DEBT)->first(); $debtType = AccountType::where('type', AccountType::DEBT)->first();
if (null === $ccType || null === $debtType) { if (null === $ccType || null === $debtType) {
$this->info('No incorrectly stored credit card liabilities.');
return 0; return 0;
} }
/** @var Collection $accounts */ /** @var Collection $accounts */

View File

@@ -62,6 +62,20 @@ class JournalCurrencies extends Command
private $currencyRepos; private $currencyRepos;
/** @var JournalRepositoryInterface */ /** @var JournalRepositoryInterface */
private $journalRepos; 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. * Execute the console command.
@@ -71,20 +85,27 @@ class JournalCurrencies extends Command
public function handle(): int public function handle(): int
{ {
$this->accountCurrencies = []; $this->accountCurrencies = [];
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->journalRepos = app(JournalRepositoryInterface::class);
$start = microtime(true); $start = microtime(true);
// @codeCoverageIgnoreStart
if ($this->isExecuted() && true !== $this->option('force')) { if ($this->isExecuted() && true !== $this->option('force')) {
$this->warn('This command has already been executed.'); $this->warn('This command has already been executed.');
return 0; return 0;
} }
// @codeCoverageIgnoreEnd
$this->updateTransferCurrencies(); $this->updateTransferCurrencies();
$this->updateOtherJournalsCurrencies(); $this->updateOtherJournalsCurrencies();
$this->markAsExecuted(); $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); $end = round(microtime(true) - $start, 2);
$this->info(sprintf('Verified and fixed transaction currencies in %s seconds.', $end)); $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 private function getFirstAssetTransaction(TransactionJournal $journal): ?Transaction
{ {
$result = $journal->transactions->first( $result = $journal->transactions->first(
function (Transaction $transaction) { static function (Transaction $transaction) {
// type can also be liability.
return AccountType::ASSET === $transaction->account->accountType->type; return AccountType::ASSET === $transaction->account->accountType->type;
} }
); );
@@ -192,6 +214,7 @@ class JournalCurrencies extends Command
} }
if (!((int)$currency->id === (int)$journal->transaction_currency_id)) { if (!((int)$currency->id === (int)$journal->transaction_currency_id)) {
$this->count++;
$this->line( $this->line(
sprintf( sprintf(
'Transfer #%d ("%s") has been updated to use %s instead of %s.', 'Transfer #%d ("%s") has been updated to use %s instead of %s.',
@@ -223,10 +246,11 @@ class JournalCurrencies extends Command
} }
$journal->transactions->each( $journal->transactions->each(
function (Transaction $transaction) use ($currency) { static function (Transaction $transaction) use ($currency) {
if (null === $transaction->transaction_currency_id) { if (null === $transaction->transaction_currency_id) {
$transaction->transaction_currency_id = $currency->id; $transaction->transaction_currency_id = $currency->id;
$transaction->save(); $transaction->save();
$this->count++;
} }
// when mismatch in transaction: // when mismatch in transaction:
@@ -235,11 +259,13 @@ class JournalCurrencies extends Command
$transaction->foreign_amount = $transaction->amount; $transaction->foreign_amount = $transaction->amount;
$transaction->transaction_currency_id = $currency->id; $transaction->transaction_currency_id = $currency->id;
$transaction->save(); $transaction->save();
$this->count++;
} }
} }
); );
// also update the journal, of course: // also update the journal, of course:
$journal->transaction_currency_id = $currency->id; $journal->transaction_currency_id = $currency->id;
$this->count++;
$journal->save(); $journal->save();
} }
@@ -255,11 +281,15 @@ class JournalCurrencies extends Command
*/ */
private function updateOtherJournalsCurrencies(): void private function updateOtherJournalsCurrencies(): void
{ {
$set = TransactionJournal $set =
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') $this->journalRepos->getAllJournals(
->whereNotIn('transaction_types.type', [TransactionType::TRANSFER]) [
->with(['transactions', 'transactions.account', 'transactions.account.accountType']) TransactionType::WITHDRAWAL,
->get(['transaction_journals.*']); TransactionType::DEPOSIT,
TransactionType::OPENING_BALANCE,
TransactionType::RECONCILIATION,
]
);
/** @var TransactionJournal $journal */ /** @var TransactionJournal $journal */
foreach ($set as $journal) { foreach ($set as $journal) {
@@ -292,46 +322,54 @@ class JournalCurrencies extends Command
$this->journalRepos->setUser($user); $this->journalRepos->setUser($user);
$this->currencyRepos->setUser($user); $this->currencyRepos->setUser($user);
$sourceAccountCurrency = $this->getCurrency($sourceAccount); $sourceCurrency = $this->getCurrency($sourceAccount);
$destAccountCurrency = $this->getCurrency($destAccount); $destCurrency = $this->getCurrency($destAccount);
if (null === $sourceAccountCurrency) { if (null === $sourceCurrency) {
Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $sourceAccount->id, $sourceAccount->name)); $message = sprintf('Account #%d ("%s") must have currency preference but has none.', $sourceAccount->id, $sourceAccount->name);
Log::error($message);
$this->error($message);
return; return;
} }
// has no currency ID? Must have, so fill in using account preference: // has no currency ID? Must have, so fill in using account preference:
if (null === $source->transaction_currency_id) { if (null === $source->transaction_currency_id) {
$source->transaction_currency_id = (int)$sourceAccountCurrency->id; $source->transaction_currency_id = (int)$sourceCurrency->id;
Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $source->id, $sourceAccountCurrency->code)); $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(); $source->save();
} }
// does not match the source account (see above)? Can be fixed // does not match the source account (see above)? Can be fixed
// when mismatch in transaction and NO foreign amount is set: // when mismatch in transaction and NO foreign amount is set:
if (!((int)$source->transaction_currency_id === (int)$sourceAccountCurrency->id) && null === $source->foreign_amount) { if (!((int)$source->transaction_currency_id === (int)$sourceCurrency->id) && null === $source->foreign_amount) {
Log::debug( $message = sprintf(
sprintf(
'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.', 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
$source->id, $source->id,
$source->transaction_currency_id, $source->transaction_currency_id,
$sourceAccountCurrency->id, $sourceCurrency->id,
$source->amount $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(); $source->save();
} }
if (null === $destAccountCurrency) { if (null === $destCurrency) {
Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $destAccount->id, $destAccount->name)); $message = sprintf('Account #%d ("%s") must have currency preference but has none.', $destAccount->id, $destAccount->name);
Log::error($message);
$this->line($message);
return; return;
} }
// if the destination account currency is the same, both foreign_amount and foreign_currency_id must be NULL for both transactions: // 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: // update both transactions to match:
$source->foreign_amount = null; $source->foreign_amount = null;
$source->foreign_currency_id = null; $source->foreign_currency_id = null;
@@ -343,22 +381,24 @@ class JournalCurrencies extends Command
sprintf( sprintf(
'Currency for account "%s" is %s, and currency for account "%s" is also '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.', %s, so %s #%d (#%d and #%d) has been verified to be to %s exclusively.',
$destAccount->name, $destAccountCurrency->code, $destAccount->name, $destCurrency->code,
$sourceAccount->name, $sourceAccountCurrency->code, $sourceAccount->name, $sourceCurrency->code,
$journal->transactionType->type, $journal->id, $journal->transactionType->type, $journal->id,
$source->id, $destination->id, $sourceAccountCurrency->code $source->id, $destination->id, $sourceCurrency->code
) )
); );
$this->count++;
return; return;
} }
// if destination account currency is different, both transactions must have this currency as foreign currency id. // if destination account currency is different, both transactions must have this currency as foreign currency id.
if (!((int)$destAccountCurrency->id === (int)$sourceAccountCurrency->id)) { if (!((int)$destCurrency->id === (int)$sourceCurrency->id)) {
$source->foreign_currency_id = $destAccountCurrency->id; $source->foreign_currency_id = $destCurrency->id;
$destination->foreign_currency_id = $destAccountCurrency->id; $destination->foreign_currency_id = $destCurrency->id;
$source->save(); $source->save();
$destination->save(); $destination->save();
$this->count++;
Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $source->id, $destination->id)); 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) { if (null === $source->foreign_amount && null !== $destination->foreign_amount) {
$source->foreign_amount = bcmul((string)$destination->foreign_amount, '-1'); $source->foreign_amount = bcmul((string)$destination->foreign_amount, '-1');
$source->save(); $source->save();
$this->count++;
Log::debug(sprintf('Restored foreign amount of source transaction (1) #%d to %s', $source->id, $source->foreign_amount)); 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) { if (null === $destination->foreign_amount && null !== $destination->foreign_amount) {
$destination->foreign_amount = bcmul((string)$destination->foreign_amount, '-1'); $destination->foreign_amount = bcmul((string)$destination->foreign_amount, '-1');
$destination->save(); $destination->save();
$this->count++;
Log::debug(sprintf('Restored foreign amount of destination transaction (2) #%d to %s', $destination->id, $destination->foreign_amount)); 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; $destination->foreign_amount = $destination->amount;
$source->save(); $source->save();
$destination->save(); $destination->save();
$this->count++;
return; return;
} }
@@ -396,6 +439,7 @@ class JournalCurrencies extends Command
$foreignAmount $foreignAmount
) )
); );
$this->count++;
$source->foreign_amount = bcmul($foreignPositive, '-1'); $source->foreign_amount = bcmul($foreignPositive, '-1');
$destination->foreign_amount = $foreignPositive; $destination->foreign_amount = $foreignPositive;
$source->save(); $source->save();
@@ -415,12 +459,7 @@ class JournalCurrencies extends Command
*/ */
private function updateTransferCurrencies(): void private function updateTransferCurrencies(): void
{ {
$set = TransactionJournal $set = $this->journalRepos->getAllJournals([TransactionType::TRANSFER]);
::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.*']);
/** @var TransactionJournal $journal */ /** @var TransactionJournal $journal */
foreach ($set as $journal) { foreach ($set as $journal) {
$this->updateTransferCurrency($journal); $this->updateTransferCurrency($journal);
@@ -435,6 +474,7 @@ class JournalCurrencies extends Command
$sourceTransaction = $this->getSourceTransaction($transfer); $sourceTransaction = $this->getSourceTransaction($transfer);
$destTransaction = $this->getDestinationTransaction($transfer); $destTransaction = $this->getDestinationTransaction($transfer);
// @codeCoverageIgnoreStart
if (null === $sourceTransaction) { if (null === $sourceTransaction) {
$this->info(sprintf('Source transaction for journal #%d is null.', $transfer->id)); $this->info(sprintf('Source transaction for journal #%d is null.', $transfer->id));
@@ -445,6 +485,7 @@ class JournalCurrencies extends Command
return; return;
} }
// @codeCoverageIgnoreEnd
$this->updateTransactionCurrency($transfer, $sourceTransaction, $destTransaction); $this->updateTransactionCurrency($transfer, $sourceTransaction, $destTransaction);
$this->updateJournalCurrency($transfer, $sourceTransaction); $this->updateJournalCurrency($transfer, $sourceTransaction);

View File

@@ -67,7 +67,7 @@ class InstallController extends Controller
$this->upgradeCommands = [ $this->upgradeCommands = [
// there are x initial commands // there are x initial commands
'migrate' => ['--seed' => true, '--force' => true], 'migrate' => ['--seed' => true, '--force' => true],
'firefly:decrypt-all' => [], 'firefly-iii:decrypt-all' => [],
'generate-keys' => [], // an exception :( 'generate-keys' => [], // an exception :(
// there are 10 upgrade commands. // there are 10 upgrade commands.

View File

@@ -165,24 +165,6 @@ class Account extends Model
return $name; 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 * @codeCoverageIgnore
* Get all of the notes. * Get all of the notes.

View File

@@ -38,10 +38,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
* @property string $amount * @property string $amount
* @property Carbon created_at * @property Carbon created_at
* @property Carbon updated_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 newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent newQuery() * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent query() * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent query()

View File

@@ -0,0 +1,61 @@
<?php
/**
* ImportServiceProvider.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
);
}
}

View File

@@ -229,9 +229,10 @@ class AccountRepository implements AccountRepositoryInterface
if (count($accountIds) > 0) { if (count($accountIds) > 0) {
$query->whereIn('accounts.id', $accountIds); $query->whereIn('accounts.id', $accountIds);
} }
$query->orderBy('accounts.name','ASC'); $query->orderBy('accounts.name', 'ASC');
$result = $query->get(['accounts.*']); $result = $query->get(['accounts.*']);
return $result; return $result;
} }
@@ -247,7 +248,7 @@ class AccountRepository implements AccountRepositoryInterface
if (count($types) > 0) { if (count($types) > 0) {
$query->accountTypeIn($types); $query->accountTypeIn($types);
} }
$query->orderBy('accounts.name','ASC'); $query->orderBy('accounts.name', 'ASC');
$result = $query->get(['accounts.*']); $result = $query->get(['accounts.*']);
@@ -271,8 +272,8 @@ class AccountRepository implements AccountRepositoryInterface
$query->accountTypeIn($types); $query->accountTypeIn($types);
} }
$query->where('active', 1); $query->where('active', 1);
$query->orderBy('accounts.account_type_id','ASC'); $query->orderBy('accounts.account_type_id', 'ASC');
$query->orderBy('accounts.name','ASC'); $query->orderBy('accounts.name', 'ASC');
$result = $query->get(['accounts.*']); $result = $query->get(['accounts.*']);
return $result; return $result;
@@ -605,4 +606,17 @@ class AccountRepository implements AccountRepositoryInterface
return $account; 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.*']);
}
} }

View File

@@ -37,6 +37,12 @@ use Illuminate\Support\Collection;
interface AccountRepositoryInterface interface AccountRepositoryInterface
{ {
/**
* @param Account $account
* @return TransactionJournal|null
*/
public function getOpeningBalance(Account $account): ?TransactionJournal;
/** /**
* Moved here from account CRUD. * Moved here from account CRUD.
* *

View File

@@ -791,4 +791,19 @@ class JournalRepository implements JournalRepositoryInterface
return $journal; 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.*']);
}
} }

View File

@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Journal; namespace FireflyIII\Repositories\Journal;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
@@ -41,6 +40,15 @@ use Illuminate\Support\MessageBag;
*/ */
interface JournalRepositoryInterface 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 TransactionJournal $journal
* @param TransactionType $type * @param TransactionType $type

View File

@@ -391,10 +391,10 @@ class FireflyValidator extends Validator
public function validateUniqueAccountForUser($attribute, $value, $parameters): bool public function validateUniqueAccountForUser($attribute, $value, $parameters): bool
{ {
// because a user does not have to be logged in (tests and what-not). // because a user does not have to be logged in (tests and what-not).
if (!auth()->check()) { if (!auth()->check()) {
return $this->validateAccountAnonymously(); return $this->validateAccountAnonymously();
} }
if (isset($this->data['what'])) { if (isset($this->data['what'])) {
return $this->validateByAccountTypeString($value, $parameters, $this->data['what']); return $this->validateByAccountTypeString($value, $parameters, $this->data['what']);
} }
@@ -409,7 +409,8 @@ class FireflyValidator extends Validator
return $this->validateByAccountId($value); 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(); $set = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore)->get();
/** @var Account $entry */ /** @var Account $entry */
foreach ($set as $entry) { foreach ($set as $entry) {
// TODO no longer need to loop like this.
if ($entry->name === $value) { if ($entry->name === $value) {
return false; return false;
} }
@@ -627,4 +629,13 @@ class FireflyValidator extends Validator
return true; return true;
} }
/**
* @param string $value
* @return bool
*/
private function validateByAccountName(string $value): bool
{
return auth()->user()->accounts()->where('name', $value)->count() === 0;
}
} }

View File

@@ -19,7 +19,9 @@
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>. * along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/ */
declare(strict_types=1); declare(strict_types=1);
use FireflyIII\Providers\ImportServiceProvider;
return [ return [
@@ -96,6 +98,7 @@ return [
FireflyIII\Providers\TagServiceProvider::class, FireflyIII\Providers\TagServiceProvider::class,
FireflyIII\Providers\AdminServiceProvider::class, FireflyIII\Providers\AdminServiceProvider::class,
FireflyIII\Providers\RecurringServiceProvider::class, FireflyIII\Providers\RecurringServiceProvider::class,
ImportServiceProvider::class,
], ],

View File

@@ -46,7 +46,8 @@ class LinkTypeSeeder extends Seeder
'outward' => '(partially) refunds', 'outward' => '(partially) refunds',
'editable' => false, 'editable' => false,
], ],
['name' => 'Paid', [
'name' => 'Paid',
'inward' => 'is (partially) paid for by', 'inward' => 'is (partially) paid for by',
'outward' => '(partially) pays for', 'outward' => '(partially) pays for',
'editable' => false, 'editable' => false,

View File

@@ -32,16 +32,24 @@
<listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener" /> <listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener" />
</listeners> </listeners>
<testsuites> <testsuites>
<testsuite name="Feature"> <!-- test suites -->
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Api"> <testsuite name="Api">
<directory suffix="Test.php">./tests/Api</directory> <directory suffix="Test.php">./tests/Api</directory>
</testsuite> </testsuite>
<!-- unit tests, splitted: -->
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit/Console</directory>
</testsuite>
<!--
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
-->
</testsuites> </testsuites>
<filter> <filter>
<whitelist processUncoveredFilesFromWhitelist="true"> <whitelist processUncoveredFilesFromWhitelist="true">

View File

@@ -32,16 +32,24 @@
<listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener" /> <listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener" />
</listeners> </listeners>
<testsuites> <testsuites>
<testsuite name="Feature"> <!-- test suites -->
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Api"> <testsuite name="Api">
<directory suffix="Test.php">./tests/Api</directory> <directory suffix="Test.php">./tests/Api</directory>
</testsuite> </testsuite>
<!-- unit tests, splitted: -->
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit/Console</directory>
</testsuite>
<!--
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
-->
</testsuites> </testsuites>
<filter> <filter>
<whitelist processUncoveredFilesFromWhitelist="true"> <whitelist processUncoveredFilesFromWhitelist="true">

View File

@@ -32,16 +32,24 @@
<listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener" /> <listener class="JohnKary\PHPUnit\Listener\SpeedTrapListener" />
</listeners> </listeners>
<testsuites> <testsuites>
<testsuite name="Feature"> <!-- test suites -->
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Api"> <testsuite name="Api">
<directory suffix="Test.php">./tests/Api</directory> <directory suffix="Test.php">./tests/Api</directory>
</testsuite> </testsuite>
<!-- unit tests, splitted: -->
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit/Console</directory>
</testsuite>
<!--
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
-->
</testsuites> </testsuites>
<filter> <filter>
<whitelist processUncoveredFilesFromWhitelist="true"> <whitelist processUncoveredFilesFromWhitelist="true">

View File

@@ -333,6 +333,9 @@ abstract class TestCase extends BaseTestCase
DB::raw('COUNT(transaction_journal_id) as ct'), DB::raw('COUNT(transaction_journal_id) as ct'),
] ]
)->first(); )->first();
if(null === $result) {
throw new FireflyException(sprintf('Cannot find suitable %s to use.', $type));
}
return TransactionJournal::find((int)$result->transaction_journal_id); return TransactionJournal::find((int)$result->transaction_journal_id);
} }

View File

@@ -0,0 +1,96 @@
<?php
/**
* CorrectDatabaseTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* CreateLinkTypesTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* DeleteEmptyGroupsTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* DeleteEmptyJournalsTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,144 @@
<?php
/**
* DeleteOrphanedTransactionsTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* DeleteZeroAmountTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* EnableCurrenciesTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,371 @@
<?php
/**
* FixAccountTypesTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* FixPiggiesTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* FixUnevenAmountTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* RemoveBillsTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* RenameMetaFieldsTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* TransferBudgetsTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,140 @@
<?php
/**
* DecryptDatabaseTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,433 @@
<?php
/**
* CreateCSVImportTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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.
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* ReportEmptyObjectsTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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.
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* ReportSumTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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.
}
}

View File

@@ -0,0 +1,404 @@
<?php
/**
* ApplyRulesTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@@ -0,0 +1,341 @@
<?php
/**
* AccountCurrenciesTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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.
}
}

View File

@@ -0,0 +1,244 @@
<?php
/**
* BackToJournalsTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* BudgetLimitCurrencyTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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());
}
}

View File

@@ -0,0 +1,129 @@
<?php
/**
* CCLiabilitiesTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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();
}
}

View File

@@ -0,0 +1,354 @@
<?php
/**
* JournalCurrenciesTest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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.
}
}