Expand test coverage and improve transaction management code.

This commit is contained in:
James Cole
2019-07-01 20:22:35 +02:00
parent 94acb50a6f
commit 5bbe1eab7c
63 changed files with 1251 additions and 812 deletions

View File

@@ -153,7 +153,7 @@ class BudgetReportController extends Controller
$cache->addProperty($start);
$cache->addProperty($end);
if ($cache->has()) {
//return response()->json($cache->get()); // @codeCoverageIgnore
return response()->json($cache->get()); // @codeCoverageIgnore
}
$format = app('navigation')->preferredCarbonLocalizedFormat($start, $end);
$function = app('navigation')->preferredEndOfPeriod($start, $end);

View File

@@ -30,8 +30,10 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Log;
@@ -54,7 +56,7 @@ class AutoCompleteController extends Controller
public function accounts(Request $request): JsonResponse
{
$accountTypes = explode(',', $request->get('types') ?? '');
$search = $request->get('query');
$search = $request->get('search');
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
@@ -92,15 +94,45 @@ class AutoCompleteController extends Controller
}
/**
* Searches in the titles of all transaction journals.
* The result is limited to the top 15 unique results.
*
* @param Request $request
* @return JsonResponse
* @codeCoverageIgnore
*/
public function budgets(): JsonResponse
public function allJournals(Request $request): JsonResponse
{
$search = (string)$request->get('search');
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$result = $repository->searchJournalDescriptions($search);
// limit and unique
$filtered = $result->unique('description');
$limited = $filtered->slice(0, 15);
$array = $limited->toArray();
foreach ($array as $index => $item) {
// give another key for consistency
$array[$index]['name'] = $item['description'];
}
return response()->json($array);
}
/**
* @param Request $request
* @return JsonResponse
*/
public function budgets(Request $request): JsonResponse
{
$search = (string)$request->get('search');
/** @var BudgetRepositoryInterface $repository */
$repository = app(BudgetRepositoryInterface::class);
$result = $repository->searchBudget($search);
return response()->json($repository->getActiveBudgets()->toArray());
return response()->json($result->toArray());
}
/**
@@ -111,7 +143,7 @@ class AutoCompleteController extends Controller
*/
public function categories(Request $request): JsonResponse
{
$query = (string)$request->get('query');
$query = (string)$request->get('search');
/** @var CategoryRepositoryInterface $repository */
$repository = app(CategoryRepositoryInterface::class);
$result = $repository->searchCategory($query);
@@ -119,6 +151,44 @@ class AutoCompleteController extends Controller
return response()->json($result->toArray());
}
/**
* @param Request $request
* @return JsonResponse
*/
public function currencyNames(Request $request): JsonResponse
{
$query = (string)$request->get('search');
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$result = $repository->searchCurrency($query)->toArray();
foreach ($result as $index => $item) {
$result[$index]['name'] = sprintf('%s (%s)', $item['name'], $item['code']);
}
return response()->json($result);
}
/**
* @param Request $request
*
* @return JsonResponse
* @codeCoverageIgnore
*/
public function transactionTypes(Request $request): JsonResponse
{
$query = (string)$request->get('search');
/** @var TransactionTypeRepositoryInterface $repository */
$repository = app(TransactionTypeRepositoryInterface::class);
$array = $repository->searchTypes($query)->toArray();
foreach ($array as $index => $item) {
// different key for consistency.
$array[$index]['name'] = $item['type'];
}
return response()->json($array);
}
/**
* @return JsonResponse
* @codeCoverageIgnore
@@ -164,13 +234,18 @@ class AutoCompleteController extends Controller
*/
public function tags(Request $request): JsonResponse
{
$query = (string)$request->get('query');
$search = (string)$request->get('search');
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$result = $repository->searchTags($query);
$result = $repository->searchTags($search);
$array = $result->toArray();
foreach ($array as $index => $item) {
// rename field for consistency.
$array[$index]['name'] = $item['tag'];
}
return response()->json($result->toArray());
return response()->json($array);
}
}

View File

@@ -46,6 +46,7 @@ class CreateController extends Controller
/**
* CreateController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{

View File

@@ -39,6 +39,7 @@ class DeleteController extends Controller
/**
* DeleteController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{
@@ -79,8 +80,8 @@ class DeleteController extends Controller
* Destroy the recurring transaction.
*
* @param RecurringRepositoryInterface $repository
* @param Request $request
* @param Recurrence $recurrence
* @param Request $request
* @param Recurrence $recurrence
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/

View File

@@ -47,6 +47,7 @@ class EditController extends Controller
/**
* EditController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{
@@ -70,7 +71,7 @@ class EditController extends Controller
/**
* Edit a recurring transaction.
*
* @param Request $request
* @param Request $request
* @param Recurrence $recurrence
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
@@ -92,7 +93,7 @@ class EditController extends Controller
$repetition = $recurrence->recurrenceRepetitions()->first();
$currentRepType = $repetition->repetition_type;
if ('' !== $repetition->repetition_moment) {
$currentRepType .= ',' . $repetition->repetition_moment;
$currentRepType .= ',' . $repetition->repetition_moment; // @codeCoverageIgnore
}
// put previous url in session if not redirect from store (not "return_to_edit").
@@ -123,9 +124,12 @@ class EditController extends Controller
$hasOldInput = null !== $request->old('_token');
$preFilled = [
'transaction_type' => strtolower($recurrence->transactionType->type),
'active' => $hasOldInput ? (bool)$request->old('active') : $recurrence->active,
'apply_rules' => $hasOldInput ? (bool)$request->old('apply_rules') : $recurrence->apply_rules,
'transaction_type' => strtolower($recurrence->transactionType->type),
'active' => $hasOldInput ? (bool)$request->old('active') : $recurrence->active,
'apply_rules' => $hasOldInput ? (bool)$request->old('apply_rules') : $recurrence->apply_rules,
'deposit_source_id' => $array['transactions'][0]['source_id'],
'withdrawal_destination_id' => $array['transactions'][0]['destination_id'],
];
return view(
@@ -138,7 +142,7 @@ class EditController extends Controller
* Update the recurring transaction.
*
* @param RecurrenceFormRequest $request
* @param Recurrence $recurrence
* @param Recurrence $recurrence
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws \FireflyIII\Exceptions\FireflyException

View File

@@ -48,6 +48,7 @@ class IndexController extends Controller
/**
* IndexController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{
@@ -121,8 +122,8 @@ class IndexController extends Controller
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters(new ParameterBag);
$array = $transformer->transform($recurrence);
$transactions = $this->recurring->getTransactions($recurrence);
$array = $transformer->transform($recurrence);
$groups = $this->recurring->getTransactions($recurrence);
// transform dates back to Carbon objects:
foreach ($array['recurrence_repetitions'] as $index => $repetition) {
@@ -133,7 +134,7 @@ class IndexController extends Controller
$subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'transactions'));
return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'groups'));
}
}

View File

@@ -47,6 +47,7 @@ class ExpenseController extends Controller
/**
* Constructor for ExpenseController
* @codeCoverageIgnore
*/
public function __construct()
{
@@ -252,11 +253,11 @@ class ExpenseController extends Controller
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expense-budget');
$cache->addProperty('top-expense');
$cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($expense->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
//return $cache->get(); // @codeCoverageIgnore
}
$combined = $this->combineAccounts($expense);
$all = new Collection;
@@ -268,11 +269,11 @@ class ExpenseController extends Controller
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($accounts);
$collector->setAccounts($all);
$set = $collector->getExtractedJournals();
$collector->setAccounts($all)->withAccountInformation();
$sorted = $collector->getExtractedJournals();
usort($set, function ($a, $b) {
return $a['amount'] <=> $b['amount'];
usort($sorted, function ($a, $b) {
return $a['amount'] <=> $b['amount']; // @codeCoverageIgnore
});
try {
@@ -304,11 +305,11 @@ class ExpenseController extends Controller
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('expense-budget');
$cache->addProperty('top-income');
$cache->addProperty($accounts->pluck('id')->toArray());
$cache->addProperty($expense->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get(); // @codeCoverageIgnore
//return $cache->get(); // @codeCoverageIgnore
}
$combined = $this->combineAccounts($expense);
$all = new Collection;
@@ -321,11 +322,15 @@ class ExpenseController extends Controller
$collector = app(GroupCollectorInterface::class);
$total = $accounts->merge($all);
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total);
$journals = $collector->getExtractedJournals();
$collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total)->withAccountInformation();
$sorted = $collector->getExtractedJournals();
usort($journals, function ($a, $b) {
return $a['amount'] <=> $b['amount'];
foreach (array_keys($sorted) as $key) {
$sorted[$key]['amount'] = bcmul($sorted[$key]['amount'], '-1');
}
usort($sorted, function ($a, $b) {
return $a['amount'] <=> $b['amount']; // @codeCoverageIgnore
});
try {

View File

@@ -41,6 +41,7 @@ class OperationsController extends Controller
/**
* OperationsController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{

View File

@@ -45,6 +45,7 @@ class CreateController extends Controller
/**
* RuleController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{

View File

@@ -39,6 +39,7 @@ class DeleteController extends Controller
/**
* RuleController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{

View File

@@ -45,6 +45,7 @@ class EditController extends Controller
/**
* RuleController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{

View File

@@ -45,6 +45,7 @@ class IndexController extends Controller
/**
* RuleController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{

View File

@@ -76,7 +76,7 @@ class SelectController extends Controller
* Execute the given rule on a set of existing transactions.
*
* @param SelectTransactionsRequest $request
* @param Rule $rule
* @param Rule $rule
*
* @return RedirectResponse
*/
@@ -181,11 +181,12 @@ class SelectController extends Controller
// Return json response
$view = 'ERROR, see logs.';
try {
$view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render();
$view = view('list.journals-array-tiny', ['journals' => $matchingTransactions])->render();
// @codeCoverageIgnoreStart
} catch (Throwable $exception) {
Log::error(sprintf('Could not render view in testTriggers(): %s', $exception->getMessage()));
Log::error($exception->getTraceAsString());
$view = sprintf('Could not render list.journals-tiny: %s', $exception->getMessage());
}
// @codeCoverageIgnoreEnd
@@ -236,17 +237,17 @@ class SelectController extends Controller
// Warn the user if only a subset of transactions is returned
$warning = '';
if ($matchingTransactions->count() === $limit) {
if (count($matchingTransactions) === $limit) {
$warning = (string)trans('firefly.warning_transaction_subset', ['max_num_transactions' => $limit]); // @codeCoverageIgnore
}
if (0 === $matchingTransactions->count()) {
if (0 === count($matchingTransactions)) {
$warning = (string)trans('firefly.warning_no_matching_transactions', ['num_transactions' => $range]); // @codeCoverageIgnore
}
// Return json response
$view = 'ERROR, see logs.';
try {
$view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render();
$view = view('list.journals-array-tiny', ['journals' => $matchingTransactions])->render();
// @codeCoverageIgnoreStart
} catch (Throwable $exception) {
Log::error(sprintf('Could not render view in testTriggersByRule(): %s', $exception->getMessage()));

View File

@@ -36,7 +36,31 @@ use Illuminate\Http\Request;
*/
class IndexController extends Controller
{
use PeriodOverview;
use PeriodOverview;
/** @var JournalRepositoryInterface */
private $repository;
/**
* IndexController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{
parent::__construct();
// translations:
$this->middleware(
function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-credit-card');
app('view')->share('title', (string)trans('firefly.accounts'));
$this->repository = app(JournalRepositoryInterface::class);
return $next($request);
}
);
}
/**
* Index for a range of transactions.
@@ -54,6 +78,7 @@ class IndexController extends Controller
$types = config('firefly.transactionTypesByType.' . $objectType);
$page = (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$pageSize =3;
if (null === $start) {
$start = session('start');
$end = session('end');
@@ -62,16 +87,16 @@ class IndexController extends Controller
$end = session('end');
}
if ($end < $start) {
[$start, $end] = [$end, $start];
}
$path = route('transactions.index', [$objectType, $start->format('Y-m-d'), $end->format('Y-m-d')]);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
$path = route('transactions.index', [$objectType, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$startStr = $start->formatLocalized($this->monthAndDayFormat);
$endStr = $end->formatLocalized($this->monthAndDayFormat);
$subTitle = (string)trans(sprintf('firefly.title_%s_between', $objectType), ['start' => $startStr, 'end' => $endStr]);
$periods = $this->getTransactionPeriodOverview($objectType, $end);
$firstJournal = $this->repository->firstNull();
$startPeriod = null === $firstJournal ? new Carbon : $firstJournal->date;
$endPeriod = clone $end;
$periods = $this->getTransactionPeriodOverview($objectType, $startPeriod, $endPeriod);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);

View File

@@ -53,10 +53,10 @@ class StartFireflySession extends StartSession
&& 'GET' === $request->method()
&& !$request->ajax()) {
$session->setPreviousUrl($uri);
Log::debug(sprintf('Will set previous URL to %s', $uri));
//Log::debug(sprintf('Will set previous URL to %s', $uri));
return;
}
Log::debug(sprintf('Will NOT set previous URL to %s', $uri));
//Log::debug(sprintf('Will NOT set previous URL to %s', $uri));
}
}

View File

@@ -29,6 +29,9 @@ use FireflyIII\Models\Recurrence;
use FireflyIII\Models\TransactionType;
use FireflyIII\Rules\ValidRecurrenceRepetitionType;
use FireflyIII\Rules\ValidRecurrenceRepetitionValue;
use FireflyIII\Validation\AccountValidator;
use Illuminate\Validation\Validator;
use Log;
/**
* Class RecurrenceFormRequest
@@ -136,6 +139,86 @@ class RecurrenceFormRequest extends Request
return $return;
}
/**
* Configure the validator instance with special rules for after the basic validation rules.
*
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
$validator->after(
function (Validator $validator) {
// validate all account info
$this->validateAccountInformation($validator);
}
);
}
/**
* Validates the given account information. Switches on given transaction type.
*
* @param Validator $validator
* @throws FireflyException
*/
public function validateAccountInformation(Validator $validator): void
{
Log::debug('Now in validateAccountInformation()');
/** @var AccountValidator $accountValidator */
$accountValidator = app(AccountValidator::class);
$data = $validator->getData();
$transactionType = $data['transaction_type'] ?? 'invalid';
$accountValidator->setTransactionType($transactionType);
// default values:
$sourceId = null;
$destinationId = null;
switch ($this->string('transaction_type')) {
default:
throw new FireflyException(sprintf('Cannot handle transaction type "%s"', $this->string('transaction_type'))); // @codeCoverageIgnore
case 'withdrawal':
$sourceId = (int)$data['source_id'];
$destinationId = (int)$data['withdrawal_destination_id'];
break;
case 'deposit':
$sourceId = (int)$data['deposit_source_id'];
$destinationId = (int)$data['destination_id'];
break;
case 'transfer':
$sourceId = (int)$data['source_id'];
$destinationId = (int)$data['destination_id'];
break;
}
// validate source account.
$validSource = $accountValidator->validateSource($sourceId, null);
// do something with result:
if (false === $validSource) {
$message = (string)trans('validation.generic_invalid_source');
$validator->errors()->add('source_id', $message);
$validator->errors()->add('deposit_source_id', $message);
return;
}
// validate destination account
$validDestination = $accountValidator->validateDestination($destinationId, null);
// do something with result:
if (false === $validDestination) {
$message = (string)trans('validation.generic_invalid_destination');
$validator->errors()->add('destination_id', $message);
$validator->errors()->add('withdrawal_destination_id', $message);
return;
}
}
/**
* The rules for this request.
*