Fix tests for transaction storage.

This commit is contained in:
James Cole
2021-03-14 16:08:49 +01:00
parent 288052905e
commit beece4dcbb
14 changed files with 1067 additions and 167 deletions

View File

@@ -43,7 +43,6 @@ class StoreRequest extends FormRequest
use ConvertsDataTypes, RecurrenceValidation, TransactionValidation, CurrencyValidation, GetRecurrenceData, ChecksLogin; use ConvertsDataTypes, RecurrenceValidation, TransactionValidation, CurrencyValidation, GetRecurrenceData, ChecksLogin;
/** /**
* Get all data from the request. * Get all data from the request.
* *
@@ -51,26 +50,21 @@ class StoreRequest extends FormRequest
*/ */
public function getAll(): array public function getAll(): array
{ {
$active = true; $fields = [
$applyRules = true; 'type' => ['type', 'string'],
if (null !== $this->get('active')) { 'title' => ['title', 'string'],
$active = $this->boolean('active'); 'description' => ['description', 'string'],
} 'first_date' => ['first_date', 'date'],
if (null !== $this->get('apply_rules')) { 'repeat_until' => ['repeat_until', 'date'],
$applyRules = $this->boolean('apply_rules'); 'nr_of_repetitions' => ['nr_of_repetitions', 'integer'],
} 'apply_rules' => ['apply_rules', 'boolean'],
'active' => ['active', 'boolean'],
'notes' => ['notes', 'nlString'],
];
$recurrence = $this->getAllData($fields);
return [ return [
'recurrence' => [ 'recurrence' => $recurrence,
'type' => $this->string('type'),
'title' => $this->string('title'),
'description' => $this->string('description'),
'first_date' => $this->date('first_date'),
'repeat_until' => $this->date('repeat_until'),
'repetitions' => $this->integer('nr_of_repetitions'),
'apply_rules' => $applyRules,
'active' => $active,
],
'transactions' => $this->getTransactionData(), 'transactions' => $this->getTransactionData(),
'repetitions' => $this->getRepetitionData(), 'repetitions' => $this->getRepetitionData(),
]; ];
@@ -93,7 +87,7 @@ class StoreRequest extends FormRequest
} }
/** @var array $transaction */ /** @var array $transaction */
foreach ($transactions as $transaction) { foreach ($transactions as $transaction) {
$return[] = $this->getSingleRecurrenceData($transaction); $return[] = $this->getSingleTransactionData($transaction);
} }
return $return; return $return;
@@ -115,12 +109,21 @@ class StoreRequest extends FormRequest
} }
/** @var array $repetition */ /** @var array $repetition */
foreach ($repetitions as $repetition) { foreach ($repetitions as $repetition) {
$return[] = [ $current = [];
'type' => $repetition['type'], if (array_key_exists('type', $repetition)) {
'moment' => $repetition['moment'], $current['type'] = $repetition['type'];
'skip' => (int) $repetition['skip'], }
'weekend' => (int) $repetition['weekend'], if (array_key_exists('moment', $repetition)) {
]; $current['moment'] = $repetition['moment'];
}
if (array_key_exists('skip', $repetition)) {
$current['skip'] = (int)$repetition['skip'];
}
if (array_key_exists('weekend', $repetition)) {
$current['weekend'] = (int)$repetition['weekend'];
}
$return[] = $current;
} }
return $return; return $return;
@@ -142,12 +145,12 @@ class StoreRequest extends FormRequest
'first_date' => 'required|date', 'first_date' => 'required|date',
'apply_rules' => [new IsBoolean], 'apply_rules' => [new IsBoolean],
'active' => [new IsBoolean], 'active' => [new IsBoolean],
'repeat_until' => sprintf('date|after:%s', $today->format('Y-m-d')), 'repeat_until' => 'date',
'nr_of_repetitions' => 'numeric|between:1,31', 'nr_of_repetitions' => 'numeric|between:1,31',
'repetitions.*.type' => 'required|in:daily,weekly,ndom,monthly,yearly', 'repetitions.*.type' => 'required|in:daily,weekly,ndom,monthly,yearly',
'repetitions.*.moment' => 'between:0,10', 'repetitions.*.moment' => 'between:0,10',
'repetitions.*.skip' => 'required|numeric|between:0,31', 'repetitions.*.skip' => 'numeric|between:0,31',
'repetitions.*.weekend' => 'required|numeric|min:1|max:4', 'repetitions.*.weekend' => 'numeric|min:1|max:4',
'transactions.*.description' => 'required|between:1,255', 'transactions.*.description' => 'required|between:1,255',
'transactions.*.amount' => 'required|numeric|gt:0', 'transactions.*.amount' => 'required|numeric|gt:0',
'transactions.*.foreign_amount' => 'numeric|gt:0', 'transactions.*.foreign_amount' => 'numeric|gt:0',
@@ -184,6 +187,7 @@ class StoreRequest extends FormRequest
{ {
$validator->after( $validator->after(
function (Validator $validator) { function (Validator $validator) {
$this->validateRecurringConfig($validator);
$this->validateOneRecurrenceTransaction($validator); $this->validateOneRecurrenceTransaction($validator);
$this->validateOneRepetition($validator); $this->validateOneRepetition($validator);
$this->validateRecurrenceRepetition($validator); $this->validateRecurrenceRepetition($validator);

View File

@@ -40,7 +40,6 @@ class StoreRequest extends FormRequest
use ConvertsDataTypes, GetRuleConfiguration, ChecksLogin; use ConvertsDataTypes, GetRuleConfiguration, ChecksLogin;
/** /**
* Get all data from the request. * Get all data from the request.
* *
@@ -48,31 +47,23 @@ class StoreRequest extends FormRequest
*/ */
public function getAll(): array public function getAll(): array
{ {
$strict = true; $fields = [
$active = true; 'title' => ['title', 'string'],
$stopProcessing = false; 'description' => ['description', 'string'],
if (null !== $this->get('active')) { 'rule_group_id' => ['rule_group_id', 'integer'],
$active = $this->boolean('active'); 'order' => ['order', 'integer'],
} 'rule_group_title' => ['rule_group_title', 'string'],
if (null !== $this->get('strict')) { 'trigger' => ['trigger', 'string'],
$strict = $this->boolean('strict'); 'strict' => ['strict', 'boolean'],
} 'stop_processing' => ['stop_processing', 'boolean'],
if (null !== $this->get('stop_processing')) { 'active' => ['active', 'boolean'],
$stopProcessing = $this->boolean('stop_processing');
}
return [
'title' => $this->string('title'),
'description' => $this->string('description'),
'rule_group_id' => $this->integer('rule_group_id'),
'rule_group_title' => $this->string('rule_group_title'),
'trigger' => $this->string('trigger'),
'strict' => $strict,
'stop_processing' => $stopProcessing,
'active' => $active,
'triggers' => $this->getRuleTriggers(),
'actions' => $this->getRuleActions(),
]; ];
$data = $this->getAllData($fields);
$data['triggers'] = $this->getRuleTriggers();
$data['actions'] = $this->getRuleActions();
return $data;
} }
/** /**
@@ -87,8 +78,8 @@ class StoreRequest extends FormRequest
$return[] = [ $return[] = [
'type' => $trigger['type'], 'type' => $trigger['type'],
'value' => $trigger['value'], 'value' => $trigger['value'],
'active' => $this->convertBoolean((string) ($trigger['active'] ?? 'false')), 'active' => $this->convertBoolean((string)($trigger['active'] ?? 'false')),
'stop_processing' => $this->convertBoolean((string) ($trigger['stop_processing'] ?? 'false')), 'stop_processing' => $this->convertBoolean((string)($trigger['stop_processing'] ?? 'false')),
]; ];
} }
} }
@@ -108,8 +99,8 @@ class StoreRequest extends FormRequest
$return[] = [ $return[] = [
'type' => $action['type'], 'type' => $action['type'],
'value' => $action['value'], 'value' => $action['value'],
'active' => $this->convertBoolean((string) ($action['active'] ?? 'false')), 'active' => $this->convertBoolean((string)($action['active'] ?? 'false')),
'stop_processing' => $this->convertBoolean((string) ($action['stop_processing'] ?? 'false')), 'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')),
]; ];
} }
} }
@@ -134,7 +125,7 @@ class StoreRequest extends FormRequest
return [ return [
'title' => 'required|between:1,100|uniqueObjectForUser:rules,title', 'title' => 'required|between:1,100|uniqueObjectForUser:rules,title',
'description' => 'between:1,5000|nullable', 'description' => 'between:1,5000|nullable',
'rule_group_id' => 'required|belongsToUser:rule_groups|required_without:rule_group_title', 'rule_group_id' => 'belongsToUser:rule_groups|required_without:rule_group_title',
'rule_group_title' => 'nullable|between:1,255|required_without:rule_group_id|belongsToUser:rule_groups,title', 'rule_group_title' => 'nullable|between:1,255|required_without:rule_group_id|belongsToUser:rule_groups,title',
'trigger' => 'required|in:store-journal,update-journal', 'trigger' => 'required|in:store-journal,update-journal',
'triggers.*.type' => 'required|in:' . implode(',', $validTriggers), 'triggers.*.type' => 'required|in:' . implode(',', $validTriggers),
@@ -179,7 +170,7 @@ class StoreRequest extends FormRequest
$triggers = $data['triggers'] ?? []; $triggers = $data['triggers'] ?? [];
// need at least one trigger // need at least one trigger
if (!is_countable($triggers) || 0 === count($triggers)) { if (!is_countable($triggers) || 0 === count($triggers)) {
$validator->errors()->add('title', (string) trans('validation.at_least_one_trigger')); $validator->errors()->add('title', (string)trans('validation.at_least_one_trigger'));
} }
} }
@@ -194,7 +185,7 @@ class StoreRequest extends FormRequest
$actions = $data['actions'] ?? []; $actions = $data['actions'] ?? [];
// need at least one trigger // need at least one trigger
if (!is_countable($actions) || 0 === count($actions)) { if (!is_countable($actions) || 0 === count($actions)) {
$validator->errors()->add('title', (string) trans('validation.at_least_one_action')); $validator->errors()->add('title', (string)trans('validation.at_least_one_action'));
} }
} }
} }

View File

@@ -41,8 +41,9 @@ class RecurrenceFactory
{ {
use TransactionTypeTrait, RecurringTransactionTrait; use TransactionTypeTrait, RecurringTransactionTrait;
private MessageBag $errors; private MessageBag $errors;
private User $user; private User $user;
/** /**
@@ -58,8 +59,8 @@ class RecurrenceFactory
/** /**
* @param array $data * @param array $data
* *
* @throws FireflyException
* @return Recurrence * @return Recurrence
* @throws FireflyException
*/ */
public function create(array $data): Recurrence public function create(array $data): Recurrence
{ {
@@ -72,26 +73,60 @@ class RecurrenceFactory
throw new FireflyException($message); throw new FireflyException($message);
} }
/** @var Carbon $firstDate */ $firstDate = null;
$firstDate = $data['recurrence']['first_date']; $repeatUntil = null;
$repetitions = 0;
$title = null;
$description = '';
$applyRules = true;
$active = true;
if (array_key_exists('first_date', $data['recurrence'])) {
/** @var Carbon $firstDate */
$firstDate = $data['recurrence']['first_date'];
}
if (array_key_exists('nr_of_repetitions', $data['recurrence'])) {
$repetitions = (int)$data['recurrence']['nr_of_repetitions'];
}
if (array_key_exists('repeat_until', $data['recurrence'])) {
$repeatUntil = $data['recurrence']['repeat_until'];
}
if (array_key_exists('title', $data['recurrence'])) {
$title = $data['recurrence']['title'];
}
if (array_key_exists('description', $data['recurrence'])) {
$description = $data['recurrence']['description'];
}
if (array_key_exists('apply_rules', $data['recurrence'])) {
$applyRules = $data['recurrence']['apply_rules'];
}
if (array_key_exists('active', $data['recurrence'])) {
$active = $data['recurrence']['active'];
}
if ($repetitions > 0 && null === $repeatUntil) {
$repeatUntil = Carbon::create()->addyear();
}
$repetitions = (int) $data['recurrence']['repetitions']; $recurrence = new Recurrence(
$recurrence = new Recurrence(
[ [
'user_id' => $this->user->id, 'user_id' => $this->user->id,
'transaction_type_id' => $type->id, 'transaction_type_id' => $type->id,
'title' => $data['recurrence']['title'], 'title' => $title,
'description' => $data['recurrence']['description'], 'description' => $description,
'first_date' => $firstDate->format('Y-m-d'), 'first_date' => $firstDate ? $firstDate->format('Y-m-d') : null,
'repeat_until' => $repetitions > 0 ? null : $data['recurrence']['repeat_until'], 'repeat_until' => $repetitions > 0 ? null : $repeatUntil->format('Y-m-d'),
'latest_date' => null, 'latest_date' => null,
'repetitions' => $data['recurrence']['repetitions'], 'repetitions' => $repetitions,
'apply_rules' => $data['recurrence']['apply_rules'], 'apply_rules' => $applyRules,
'active' => $data['recurrence']['active'], 'active' => $active,
] ]
); );
$recurrence->save(); $recurrence->save();
if (array_key_exists('notes', $data['recurrence'])) {
$this->updateNote($recurrence, (string)$data['recurrence']['notes']);
}
$this->createRepetitions($recurrence, $data['repetitions'] ?? []); $this->createRepetitions($recurrence, $data['repetitions'] ?? []);
try { try {
$this->createTransactions($recurrence, $data['transactions'] ?? []); $this->createTransactions($recurrence, $data['transactions'] ?? []);

View File

@@ -28,9 +28,11 @@ use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleAction;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\RuleTrigger; use FireflyIII\Models\RuleTrigger;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Support\Search\OperatorQuerySearch; use FireflyIII\Support\Search\OperatorQuerySearch;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
/** /**
* Class RuleRepository. * Class RuleRepository.
@@ -367,19 +369,9 @@ class RuleRepository implements RuleRepositoryInterface
*/ */
public function resetRuleOrder(RuleGroup $ruleGroup): bool public function resetRuleOrder(RuleGroup $ruleGroup): bool
{ {
$ruleGroup->rules()->withTrashed()->whereNotNull('deleted_at')->update(['order' => 0]); $groupRepository = app(RuleGroupRepositoryInterface::class);
$groupRepository->setUser($ruleGroup->user);
$set = $ruleGroup->rules() $groupRepository->resetRuleOrder($ruleGroup);
->orderBy('order', 'ASC')
->orderBy('updated_at', 'DESC')
->get();
$count = 1;
/** @var Rule $entry */
foreach ($set as $entry) {
$entry->order = $count;
$entry->save();
++$count;
}
return true; return true;
} }
@@ -407,6 +399,43 @@ class RuleRepository implements RuleRepositoryInterface
$this->user = $user; $this->user = $user;
} }
/**
* @inheritDoc
*/
public function setOrder(Rule $rule, int $newOrder): void
{
$oldOrder = (int)$rule->order;
$groupId = (int)$rule->rule_group_id;
$maxOrder = $this->maxOrder($rule->ruleGroup);
$newOrder = $newOrder > $maxOrder ? $maxOrder + 1 : $newOrder;
Log::debug(sprintf('New order will be %d', $newOrder));
if ($newOrder > $oldOrder) {
$this->user->rules()
->where('rules.rule_group_id', $groupId)
->where('rules.order', '<=', $newOrder)
->where('rules.order', '>', $oldOrder)
->where('rules.id', '!=', $rule->id)
->decrement('rules.order', 1);
$rule->order = $newOrder;
Log::debug(sprintf('Order of rule #%d ("%s") is now %d', $rule->id, $rule->title, $newOrder));
$rule->save();
return;
}
$this->user->rules()
->where('rules.rule_group_id', $groupId)
->where('rules.order', '>=', $newOrder)
->where('rules.order', '<', $oldOrder)
->where('rules.id', '!=', $rule->id)
->increment('rules.order', 1);
$rule->order = $newOrder;
Log::debug(sprintf('Order of rule #%d ("%s") is now %d', $rule->id, $rule->title, $newOrder));
$rule->save();
}
/** /**
* @param array $data * @param array $data
* *
@@ -414,31 +443,48 @@ class RuleRepository implements RuleRepositoryInterface
*/ */
public function store(array $data): Rule public function store(array $data): Rule
{ {
/** @var RuleGroup $ruleGroup */ $ruleGroup = null;
$ruleGroup = $this->user->ruleGroups()->find($data['rule_group_id']); if (array_key_exists('rule_group_id', $data)) {
$ruleGroup = $this->user->ruleGroups()->find($data['rule_group_id']);
// get max order: }
$order = $this->getHighestOrderInRuleGroup($ruleGroup); if (array_key_exists('rule_group_title', $data)) {
$ruleGroup = $this->user->ruleGroups()->where('title', $data['rule_group_title'])->first();
}
if (null === $ruleGroup) {
throw new FireflyException('No such rule group.');
}
// start by creating a new rule: // start by creating a new rule:
$rule = new Rule; $rule = new Rule;
$rule->user()->associate($this->user->id); $rule->user()->associate($this->user->id);
$rule->rule_group_id = $data['rule_group_id']; $rule->rule_group_id = $ruleGroup->id;
$rule->order = ($order + 1); $rule->order = 31337;
$rule->active = $data['active']; $rule->active = array_key_exists('active', $data) ? $data['active'] : true;
$rule->strict = $data['strict']; $rule->strict = array_key_exists('strict', $data) ? $data['strict'] : false;
$rule->stop_processing = $data['stop_processing']; $rule->stop_processing = array_key_exists('stop_processing', $data) ? $data['stop_processing'] : false;
$rule->title = $data['title']; $rule->title = $data['title'];
$rule->description = strlen($data['description']) > 0 ? $data['description'] : null; $rule->description = array_key_exists('stop_processing', $data) ? $data['stop_processing'] : null;
$rule->save(); $rule->save();
$rule->refresh();
// save update trigger:
$this->setRuleTrigger($data['trigger'] ?? 'store-journal', $rule);
// reset order:
$this->resetRuleOrder($ruleGroup);
Log::debug('Done with resetting.');
if (array_key_exists('order', $data)) {
Log::debug(sprintf('User has submitted order %d', $data['order']));
$this->setOrder($rule, $data['order']);
}
// start storing triggers: // start storing triggers:
$this->storeTriggers($rule, $data); $this->storeTriggers($rule, $data);
// same for actions. // same for actions.
$this->storeActions($rule, $data); $this->storeActions($rule, $data);
$rule->refresh();
return $rule; return $rule;
} }
@@ -614,4 +660,12 @@ class RuleRepository implements RuleRepositoryInterface
$trigger->stop_processing = false; $trigger->stop_processing = false;
$trigger->save(); $trigger->save();
} }
/**
* @inheritDoc
*/
public function maxOrder(RuleGroup $ruleGroup): int
{
return (int)$ruleGroup->rules()->max('order');
}
} }

View File

@@ -189,6 +189,19 @@ interface RuleRepositoryInterface
*/ */
public function store(array $data): Rule; public function store(array $data): Rule;
/**
* @param Rule $rule
* @param int $newOrder
*/
public function setOrder(Rule $rule, int $newOrder): void;
/**
* @param RuleGroup $ruleGroup
*
* @return int
*/
public function maxOrder(RuleGroup $ruleGroup): int;
/** /**
* @param Rule $rule * @param Rule $rule
* @param array $values * @param array $values

View File

@@ -24,7 +24,9 @@ namespace FireflyIII\Repositories\RuleGroup;
use Exception; use Exception;
use FireflyIII\Models\Rule; use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\RuleGroup; use FireflyIII\Models\RuleGroup;
use FireflyIII\Models\RuleTrigger;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -365,10 +367,13 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
$count = 1; $count = 1;
/** @var Rule $entry */ /** @var Rule $entry */
foreach ($set as $entry) { foreach ($set as $entry) {
if ($entry->order !== $count) { if ((int)$entry->order !== $count) {
Log::debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count));
$entry->order = $count; $entry->order = $count;
$entry->save(); $entry->save();
} }
$this->resetRuleActionOrder($entry);
$this->resetRuleTriggerOrder($entry);
++$count; ++$count;
} }
@@ -376,6 +381,51 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
return true; return true;
} }
/**
* @param Rule $rule
*/
private function resetRuleActionOrder(Rule $rule): void
{
$actions = $rule->ruleActions()
->orderBy('order', 'ASC')
->orderBy('active', 'DESC')
->orderBy('action_type', 'ASC')
->get();
$index = 1;
/** @var RuleAction $action */
foreach ($actions as $action) {
if ((int)$action->order !== $index) {
$action->order = $index;
$action->save();
Log::debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index));
}
$index++;
}
}
/**
* @param Rule $rule
*/
private function resetRuleTriggerOrder(Rule $rule): void
{
$triggers = $rule->ruleTriggers()
->orderBy('order', 'ASC')
->orderBy('active', 'DESC')
->orderBy('trigger_type', 'ASC')
->get();
$index = 1;
/** @var RuleTrigger $trigger */
foreach ($triggers as $trigger) {
$order = (int) $trigger->order;
if ($order !== $index) {
$trigger->order = $index;
$trigger->save();
Log::debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index));
}
$index++;
}
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@@ -412,12 +462,14 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface
'title' => $data['title'], 'title' => $data['title'],
'description' => $data['description'], 'description' => $data['description'],
'order' => 31337, 'order' => 31337,
'active' => $data['active'], 'active' => array_key_exists('active', $data) ? $data['active'] : true,
] ]
); );
$newRuleGroup->save(); $newRuleGroup->save();
$this->resetOrder(); $this->resetOrder();
$this->setOrder($newRuleGroup, $data['order']); if (array_key_exists('order', $data)) {
$this->setOrder($newRuleGroup, $data['order']);
}
return $newRuleGroup; return $newRuleGroup;
} }

View File

@@ -32,6 +32,7 @@ use FireflyIII\Factory\PiggyBankFactory;
use FireflyIII\Factory\TransactionCurrencyFactory; use FireflyIII\Factory\TransactionCurrencyFactory;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\Note;
use FireflyIII\Models\Recurrence; use FireflyIII\Models\Recurrence;
use FireflyIII\Models\RecurrenceMeta; use FireflyIII\Models\RecurrenceMeta;
use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceRepetition;
@@ -61,7 +62,7 @@ trait RecurringTransactionTrait
'recurrence_id' => $recurrence->id, 'recurrence_id' => $recurrence->id,
'repetition_type' => $array['type'], 'repetition_type' => $array['type'],
'repetition_moment' => $array['moment'] ?? '', 'repetition_moment' => $array['moment'] ?? '',
'repetition_skip' => $array['skip'], 'repetition_skip' => $array['skip'] ?? 0,
'weekend' => $array['weekend'] ?? 1, 'weekend' => $array['weekend'] ?? 1,
] ]
); );
@@ -69,6 +70,38 @@ trait RecurringTransactionTrait
} }
} }
/**
* @param Recurrence $recurrence
* @param string $note
*
* @return bool
*/
public function updateNote(Recurrence $recurrence, string $note): bool
{
if ('' === $note) {
$dbNote = $recurrence->notes()->first();
if (null !== $dbNote) {
try {
$dbNote->delete();
} catch (Exception $e) {
Log::debug(sprintf('Error deleting note: %s', $e->getMessage()));
}
}
return true;
}
$dbNote = $recurrence->notes()->first();
if (null === $dbNote) {
$dbNote = new Note();
$dbNote->noteable()->associate($recurrence);
}
$dbNote->text = trim($note);
$dbNote->save();
return true;
}
/** /**
* Store transactions of a recurring transactions. It's complex but readable. * Store transactions of a recurring transactions. It's complex but readable.
* *
@@ -82,8 +115,8 @@ trait RecurringTransactionTrait
foreach ($transactions as $array) { foreach ($transactions as $array) {
$sourceTypes = config(sprintf('firefly.expected_source_types.source.%s', $recurrence->transactionType->type)); $sourceTypes = config(sprintf('firefly.expected_source_types.source.%s', $recurrence->transactionType->type));
$destTypes = config(sprintf('firefly.expected_source_types.destination.%s', $recurrence->transactionType->type)); $destTypes = config(sprintf('firefly.expected_source_types.destination.%s', $recurrence->transactionType->type));
$source = $this->findAccount($sourceTypes, $array['source_id'], $array['source_name']); $source = $this->findAccount($sourceTypes, $array['source_id'], null);
$destination = $this->findAccount($destTypes, $array['destination_id'], $array['destination_name']); $destination = $this->findAccount($destTypes, $array['destination_id'], null);
/** @var TransactionCurrencyFactory $factory */ /** @var TransactionCurrencyFactory $factory */
$factory = app(TransactionCurrencyFactory::class); $factory = app(TransactionCurrencyFactory::class);
@@ -107,7 +140,6 @@ trait RecurringTransactionTrait
} }
// TODO typeOverrule: the account validator may have another opinion on the transaction type. // TODO typeOverrule: the account validator may have another opinion on the transaction type.
$transaction = new RecurrenceTransaction( $transaction = new RecurrenceTransaction(
[ [
'recurrence_id' => $recurrence->id, 'recurrence_id' => $recurrence->id,
@@ -116,30 +148,36 @@ trait RecurringTransactionTrait
'source_id' => $source->id, 'source_id' => $source->id,
'destination_id' => $destination->id, 'destination_id' => $destination->id,
'amount' => $array['amount'], 'amount' => $array['amount'],
'foreign_amount' => '' === (string)$array['foreign_amount'] ? null : (string)$array['foreign_amount'], 'foreign_amount' => array_key_exists('foreign_amount', $array) ? (string)$array['foreign_amount'] : null,
'description' => $array['description'], 'description' => $array['description'],
] ]
); );
$transaction->save(); $transaction->save();
/** @var BudgetFactory $budgetFactory */ $budget = null;
$budgetFactory = app(BudgetFactory::class); if (array_key_exists('budget_id', $array)) {
$budgetFactory->setUser($recurrence->user); /** @var BudgetFactory $budgetFactory */
$budget = $budgetFactory->find($array['budget_id'], $array['budget_name']); $budgetFactory = app(BudgetFactory::class);
$budgetFactory->setUser($recurrence->user);
$budget = $budgetFactory->find($array['budget_id'], null);
}
/** @var CategoryFactory $categoryFactory */ $category = null;
$categoryFactory = app(CategoryFactory::class); if (array_key_exists('category_id', $array)) {
$categoryFactory->setUser($recurrence->user); /** @var CategoryFactory $categoryFactory */
$category = $categoryFactory->findOrCreate($array['category_id'], $array['category_name']); $categoryFactory = app(CategoryFactory::class);
$categoryFactory->setUser($recurrence->user);
$category = $categoryFactory->findOrCreate($array['category_id'], null);
}
// same for piggy bank // same for piggy bank
$piggyId = (int)($array['piggy_bank_id'] ?? 0.0); if (array_key_exists('piggy_bank_id', $array)) {
$piggyName = $array['piggy_bank_name'] ?? ''; $this->updatePiggyBank($transaction, (int)$array['piggy_bank_id']);
$this->updatePiggyBank($transaction, $piggyId, $piggyName); }
// same for tags if(array_key_exists('tags', $array)) {
$tags = $array['tags'] ?? []; $this->updateTags($transaction, $array['tags']);
$this->updateTags($transaction, $tags); }
// create recurrence transaction meta: // create recurrence transaction meta:
if (null !== $budget) { if (null !== $budget) {
@@ -246,14 +284,13 @@ trait RecurringTransactionTrait
/** /**
* @param RecurrenceTransaction $transaction * @param RecurrenceTransaction $transaction
* @param int $piggyId * @param int $piggyId
* @param string $piggyName
*/ */
protected function updatePiggyBank(RecurrenceTransaction $transaction, int $piggyId, string $piggyName): void protected function updatePiggyBank(RecurrenceTransaction $transaction, int $piggyId): void
{ {
/** @var PiggyBankFactory $factory */ /** @var PiggyBankFactory $factory */
$factory = app(PiggyBankFactory::class); $factory = app(PiggyBankFactory::class);
$factory->setUser($transaction->recurrence->user); $factory->setUser($transaction->recurrence->user);
$piggyBank = $factory->find($piggyId, $piggyName); $piggyBank = $factory->find($piggyId, null);
if (null !== $piggyBank) { if (null !== $piggyBank) {
/** @var RecurrenceMeta $entry */ /** @var RecurrenceMeta $entry */
$entry = $transaction->recurrenceTransactionMeta()->where('name', 'piggy_bank_id')->first(); $entry = $transaction->recurrenceTransactionMeta()->where('name', 'piggy_bank_id')->first();

View File

@@ -33,31 +33,58 @@ trait GetRecurrenceData
* *
* @return array * @return array
*/ */
protected function getSingleRecurrenceData(array $transaction): array protected function getSingleTransactionData(array $transaction): array
{ {
return [ $return = [];
'amount' => $transaction['amount'],
'currency_id' => isset($transaction['currency_id']) ? (int) $transaction['currency_id'] : null,
'currency_code' => $transaction['currency_code'] ?? null,
'foreign_amount' => $transaction['foreign_amount'] ?? null,
'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int) $transaction['foreign_currency_id'] : null,
'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null,
'source_id' => isset($transaction['source_id']) ? (int) $transaction['source_id'] : null,
'source_name' => isset($transaction['source_name']) ? (string) $transaction['source_name'] : null,
'destination_id' => isset($transaction['destination_id']) ? (int) $transaction['destination_id'] : null,
'destination_name' => isset($transaction['destination_name']) ? (string) $transaction['destination_name'] : null,
'description' => $transaction['description'],
'type' => $this->string('type'),
// new and updated fields: // amount + currency
'piggy_bank_id' => isset($transaction['piggy_bank_id']) ? (int) $transaction['piggy_bank_id'] : null, if (array_key_exists('amount', $transaction)) {
'piggy_bank_name' => $transaction['piggy_bank_name'] ?? null, $return['amount'] = $transaction['amount'];
'tags' => $transaction['tags'] ?? [], }
'budget_id' => isset($transaction['budget_id']) ? (int) $transaction['budget_id'] : null, if (array_key_exists('currency_id', $transaction)) {
'budget_name' => $transaction['budget_name'] ?? null, $return['currency_id'] = (int)$transaction['currency_id'];
'category_id' => isset($transaction['category_id']) ? (int) $transaction['category_id'] : null, }
'category_name' => $transaction['category_name'] ?? null, if (array_key_exists('currency_code', $transaction)) {
]; $return['currency_code'] = $transaction['currency_code'];
}
// foreign amount + currency
if (array_key_exists('foreign_amount', $transaction)) {
$return['foreign_amount'] = $transaction['foreign_amount'];
}
if (array_key_exists('foreign_currency_id', $transaction)) {
$return['foreign_currency_id'] = (int)$transaction['foreign_currency_id'];
}
if (array_key_exists('foreign_currency_code', $transaction)) {
$return['foreign_currency_code'] = $transaction['foreign_currency_code'];
}
// source + dest
if (array_key_exists('source_id', $transaction)) {
$return['source_id'] = (int)$transaction['source_id'];
}
if (array_key_exists('destination_id', $transaction)) {
$return['destination_id'] = (int)$transaction['destination_id'];
}
// description
if (array_key_exists('description', $transaction)) {
$return['description'] = $transaction['description'];
}
if (array_key_exists('piggy_bank_id', $transaction)) {
$return['piggy_bank_id'] = (int)$transaction['piggy_bank_id'];
}
if (array_key_exists('tags', $transaction)) {
$return['tags'] = $transaction['tags'];
}
if (array_key_exists('budget_id', $transaction)) {
$return['budget_id'] = (int)$transaction['budget_id'];
}
if (array_key_exists('category_id', $transaction)) {
$return['category_id'] = (int)$transaction['category_id'];
}
return $return;
} }
} }

View File

@@ -36,7 +36,22 @@ use Log;
*/ */
trait RecurrenceValidation trait RecurrenceValidation
{ {
public function validateRecurringConfig(Validator $validator) {
$data = $validator->getData();
$reps = array_key_exists('nr_of_repetitions', $data) ? (int)$data['nr_of_repetitions'] : null;
$repeatUntil = array_key_exists('repeat_until', $data) ? new Carbon($data['repeat_until']) : null;
if(null === $reps && null === $repeatUntil) {
$validator->errors()->add('nr_of_repetitions', trans('validation.require_repeat_until'));
$validator->errors()->add('repeat_until', trans('validation.require_repeat_until'));
return;
}
if($reps > 0 && null !== $repeatUntil) {
$validator->errors()->add('nr_of_repetitions', trans('validation.require_repeat_until'));
$validator->errors()->add('repeat_until', trans('validation.require_repeat_until'));
return;
}
}
/** /**
* Validate account information input for recurrences which are being updated. * Validate account information input for recurrences which are being updated.

View File

@@ -92,7 +92,7 @@ return [
'driver' => 'single', 'driver' => 'single',
'path' => 'php://stdout', 'path' => 'php://stdout',
'tap' => [AuditLogger::class], 'tap' => [AuditLogger::class],
'level' => envNonEmpty('APP_LOG_LEVEL', 'info'), 'level' => envNonEmpty('AUDIT_LOG_LEVEL', 'info'),
], ],
'dailytest' => [ 'dailytest' => [
'driver' => 'daily', 'driver' => 'daily',

View File

@@ -0,0 +1,205 @@
<?php
/*
* StoreControllerTest.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Tests\Api\Models\Recurrence;
use Faker\Factory;
use Laravel\Passport\Passport;
use Log;
use Tests\TestCase;
use Tests\Traits\CollectsValues;
use Tests\Traits\RandomValues;
use Tests\Traits\TestHelpers;
/**
* Class StoreControllerTest
*/
class StoreControllerTest extends TestCase
{
use RandomValues, TestHelpers, CollectsValues;
/**
*
*/
public function setUp(): void
{
parent::setUp();
Passport::actingAs($this->user());
Log::info(sprintf('Now in %s.', get_class($this)));
}
/**
* @param array $submission
*
* emptyDataProvider / storeDataProvider
*
* @dataProvider storeDataProvider
*/
public function testStore(array $submission): void
{
if ([] === $submission) {
$this->markTestSkipped('Empty data provider');
}
$route = 'api.v1.recurrences.store';
$this->storeAndCompare($route, $submission);
}
/**
* @return array
*/
public function emptyDataProvider(): array
{
return [[[]]];
}
/**
* @return array
*/
public function storeDataProvider(): array
{
$minimalSets = $this->minimalSets();
$optionalSets = $this->optionalSets();
$regenConfig = [
'title' => function () {
$faker = Factory::create();
return $faker->uuid;
},
];
return $this->genericDataProvider($minimalSets, $optionalSets, $regenConfig);
}
/**
* @return array
*/
private function minimalSets(): array
{
$faker = Factory::create();
// three sets:
$combis = [
['withdrawal', 1, 8],
['deposit', 9, 1],
['transfer', 1, 2],
];
$types = [
['daily', ''],
['weekly', (string)$faker->numberBetween(1, 7)],
['ndom', (string)$faker->numberBetween(1, 4) . ',' . $faker->numberBetween(1, 7)],
['monthly', (string)$faker->numberBetween(1, 31)],
['yearly', $faker->date()],
];
$set = [];
foreach ($combis as $combi) {
foreach ($types as $type) {
$set[] = [
'parameters' => [],
'fields' => [
'type' => $combi[0],
'title' => $faker->uuid,
'first_date' => $faker->date(),
'repeat_until' => $faker->date(),
'repetitions' => [
[
'type' => $type[0],
'moment' => $type[1],
],
],
'transactions' => [
[
'description' => $faker->uuid,
'amount' => number_format($faker->randomFloat(2, 10, 100), 2),
'source_id' => $combi[1],
'destination_id' => $combi[2],
],
],
],
];
}
}
return $set;
}
/**
* @return \array[][]
*/
private function optionalSets(): array
{
$faker = Factory::create();
return [
'description' => [
'fields' => [
'description' => $faker->uuid,
],
],
'nr_of_repetitions' => [
'fields' => [
'nr_of_repetitions' => $faker->numberBetween(1, 2),
],
'remove_fields' => ['repeat_until'],
],
'apply_rules' => [
'fields' => [
'apply_rules' => $faker->boolean,
],
],
'active' => [
'fields' => [
'active' => $faker->boolean,
],
],
'notes' => [
'fields' => [
'notes' => $faker->uuid,
],
],
'repetitions_skip' => [
'fields' => [
'repetitions' => [
// first entry, set field:
[
'skip' => $faker->numberBetween(1,3),
],
],
],
],
'repetitions_weekend' => [
'fields' => [
'repetitions' => [
// first entry, set field:
[
'weekend' => $faker->numberBetween(1,4),
],
],
],
]
];
}
}

View File

@@ -0,0 +1,237 @@
<?php
/*
* StoreControllerTest.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Tests\Api\Models\Rule;
use Faker\Factory;
use Laravel\Passport\Passport;
use Log;
use Tests\TestCase;
use Tests\Traits\CollectsValues;
use Tests\Traits\RandomValues;
use Tests\Traits\TestHelpers;
/**
* Class StoreControllerTest
*/
class StoreControllerTest extends TestCase
{
use RandomValues, TestHelpers, CollectsValues;
/**
*
*/
public function setUp(): void
{
parent::setUp();
Passport::actingAs($this->user());
Log::info(sprintf('Now in %s.', get_class($this)));
}
/**
* @param array $submission
*
* emptyDataProvider / storeDataProvider
*
* @dataProvider storeDataProvider
*/
public function testStore(array $submission): void
{
if ([] === $submission) {
$this->markTestSkipped('Empty data provider');
}
$route = 'api.v1.rules.store';
$this->storeAndCompare($route, $submission);
}
/**
* @return array
*/
public function emptyDataProvider(): array
{
return [[[]]];
}
/**
* @return array
*/
public function storeDataProvider(): array
{
$minimalSets = $this->minimalSets();
$optionalSets = $this->optionalSets();
$regenConfig = [
'title' => function () {
$faker = Factory::create();
return $faker->uuid;
},
];
return $this->genericDataProvider($minimalSets, $optionalSets, $regenConfig);
}
/**
* @return array
*/
private function minimalSets(): array
{
$faker = Factory::create();
// - title
// - rule_group_id
// - trigger
// - triggers
// - actions
$set = [
'default_by_id' => [
'parameters' => [],
'fields' => [
'title' => $faker->uuid,
'rule_group_id' => (string)$faker->randomElement([1, 2]),
'trigger' => $faker->randomElement(['store-journal', 'update-journal']),
'triggers' => [
[
'type' => $faker->randomElement(['from_account_starts', 'from_account_is', 'description_ends', 'description_is']),
'value' => $faker->uuid,
],
],
'actions' => [
[
'type' => $faker->randomElement(['set_category', 'add_tag', 'set_description']),
'value' => $faker->uuid,
],
],
],
],
'default_by_title' => [
'parameters' => [],
'fields' => [
'title' => $faker->uuid,
'rule_group_title' => sprintf('Rule group %d', $faker->randomElement([1, 2])),
'trigger' => $faker->randomElement(['store-journal', 'update-journal']),
'triggers' => [
[
'type' => $faker->randomElement(['from_account_starts', 'from_account_is', 'description_ends', 'description_is']),
'value' => $faker->uuid,
],
],
'actions' => [
[
'type' => $faker->randomElement(['set_category', 'add_tag', 'set_description']),
'value' => $faker->uuid,
],
],
],
],
];
// leave it like this for now.
return $set;
}
/**
* @return \array[][]
*/
private function optionalSets(): array
{
$faker = Factory::create();
return [
'order' => [
'fields' => [
'order' => $faker->numberBetween(1, 2),
],
],
'active' => [
'fields' => [
'active' => $faker->boolean,
],
],
'strict' => [
'fields' => [
'strict' => $faker->boolean,
],
],
'stop_processing' => [
'fields' => [
'stop_processing' => $faker->boolean,
],
],
'triggers_order' => [
'fields' => [
'triggers' => [
// first entry, set field:
[
'order' => 1,
],
],
],
],
'triggers_active' => [
'fields' => [
'triggers' => [
// first entry, set field:
[
'active' => false,
],
],
],
],
'triggers_not_active' => [
'fields' => [
'triggers' => [
// first entry, set field:
[
'active' => true,
],
],
],
],
'triggers_processing' => [
'fields' => [
'triggers' => [
// first entry, set field:
[
'stop_processing' => true,
],
],
],
],
'triggers_not_processing' => [
'fields' => [
'triggers' => [
// first entry, set field:
[
'stop_processing' => false,
],
],
],
],
];
}
}

View File

@@ -0,0 +1,167 @@
<?php
/*
* StoreControllerTest.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Tests\Api\Models\Transaction;
use Faker\Factory;
use Laravel\Passport\Passport;
use Log;
use Tests\TestCase;
use Tests\Traits\CollectsValues;
use Tests\Traits\RandomValues;
use Tests\Traits\TestHelpers;
/**
* Class StoreControllerTest
*/
class StoreControllerTest extends TestCase
{
use RandomValues, TestHelpers, CollectsValues;
/**
*
*/
public function setUp(): void
{
parent::setUp();
Passport::actingAs($this->user());
Log::info(sprintf('Now in %s.', get_class($this)));
}
/**
* @param array $submission
*
* emptyDataProvider / storeDataProvider
*
* @dataProvider storeDataProvider
*/
public function testStore(array $submission): void
{
if ([] === $submission) {
$this->markTestSkipped('Empty data provider');
}
$route = 'api.v1.transactions.store';
$this->storeAndCompare($route, $submission);
}
/**
* @return array
*/
public function emptyDataProvider(): array
{
return [[[]]];
}
/**
* @return array
*/
public function storeDataProvider(): array
{
$minimalSets = $this->minimalSets();
$optionalSets = $this->optionalSets();
$regenConfig = [
'title' => function () {
$faker = Factory::create();
return $faker->uuid;
},
'transactions' => [
[
'description' => function () {
$faker = Factory::create();
return $faker->uuid;
},
],
],
];
return $this->genericDataProvider($minimalSets, $optionalSets, $regenConfig);
}
/**
* @return array
*/
private function minimalSets(): array
{
$faker = Factory::create();
// 3 sets:
$combis = [
['withdrawal', 1, 8],
['deposit', 9, 1],
['transfer', 1, 2],
];
$set = [];
foreach ($combis as $combi) {
$set[] = [
'parameters' => [],
'fields' => [
// not even required but OK.
'error_if_duplicate_hash' => $faker->boolean,
'transactions' => [
[
'type' => $combi[0],
'date' => $faker->dateTime(null, 'Europe/Amsterdam')->format(\DateTimeInterface::RFC3339),
'amount' => number_format($faker->randomFloat(2, 10, 100), 12),
'description' => $faker->uuid,
'source_id' => $combi[1],
'destination_id' => $combi[2],
],
],
],
];
}
return $set;
}
/**
* @return \array[][]
*/
private function optionalSets(): array
{
$faker = Factory::create();
return [
'title' => [
'fields' => [
'title' => $faker->uuid,
],
],
'order' => [
'fields' => [
'order' => $faker->numberBetween(1, 2),
],
],
'active' => [
'fields' => [
'active' => $faker->boolean,
],
],
];
}
}

View File

@@ -43,13 +43,13 @@ trait TestHelpers
{ {
$submissions = []; $submissions = [];
/** /**
* @var string $name * @var string $i
* @var array $set * @var array $set
*/ */
foreach ($minimalSets as $name => $set) { foreach ($minimalSets as $i => $set) {
$body = []; $body = [];
foreach ($set['fields'] as $field => $value) { foreach ($set['fields'] as $ii => $value) {
$body[$field] = $value; $body[$ii] = $value;
} }
// minimal set is part of all submissions: // minimal set is part of all submissions:
$submissions[] = [[ $submissions[] = [[
@@ -62,16 +62,32 @@ trait TestHelpers
$optionalSets = $startOptionalSets; $optionalSets = $startOptionalSets;
$keys = array_keys($optionalSets); $keys = array_keys($optionalSets);
$count = count($keys) > self::MAX_ITERATIONS ? self::MAX_ITERATIONS : count($keys); $count = count($keys) > self::MAX_ITERATIONS ? self::MAX_ITERATIONS : count($keys);
for ($i = 1; $i <= $count; $i++) { for ($iii = 1; $iii <= $count; $iii++) {
$combinations = $this->combinationsOf($i, $keys); $combinations = $this->combinationsOf($iii, $keys);
// expand body with N extra fields: // expand body with N extra fields:
foreach ($combinations as $extraFields) { foreach ($combinations as $iv => $extraFields) {
$second = $body; $second = $body;
$ignore = $set['ignore'] ?? []; // unused atm. $ignore = $set['ignore'] ?? []; // unused atm.
foreach ($extraFields as $extraField) { foreach ($extraFields as $v => $extraField) {
// now loop optional sets on $extraField and add whatever the config is: // now loop optional sets on $extraField and add whatever the config is:
foreach ($optionalSets[$extraField]['fields'] as $newField => $newValue) { foreach ($optionalSets[$extraField]['fields'] as $vi => $newValue) {
$second[$newField] = $newValue; // if the newValue is an array, we must merge it with whatever may
// or may not already be there. Its the optional field for one of the
// (maybe existing?) fields:
if (is_array($newValue) && array_key_exists($vi, $second) && is_array($second[$vi])) {
// loop $second[$vi] and merge it with whatever is in $newValue[$someIndex]
foreach ($second[$vi] as $vii => $iiValue) {
$second[$vi][$vii] = $iiValue + $newValue[$vii];
}
}
if (!is_array($newValue)) {
$second[$vi] = $newValue;
}
}
if (array_key_exists('remove_fields', $optionalSets[$extraField])) {
foreach ($optionalSets[$extraField]['remove_fields'] as $removed) {
unset($second[$removed]);
}
} }
} }
@@ -113,9 +129,20 @@ trait TestHelpers
*/ */
protected function regenerateValues($set, $opts): array protected function regenerateValues($set, $opts): array
{ {
foreach ($opts as $key => $func) { foreach ($opts as $i => $func) {
if (array_key_exists($key, $set)) { if (array_key_exists($i, $set)) {
$set[$key] = $func(); if(!is_array($set[$i])) {
$set[$i] = $func();
}
if(is_array($set[$i])) {
foreach($set[$i] as $ii => $lines) {
foreach($lines as $iii => $value) {
if(isset($opts[$i][$ii][$iii])) {
$set[$i][$ii][$iii] = $opts[$i][$ii][$iii]();
}
}
}
}
} }
} }
@@ -132,7 +159,7 @@ trait TestHelpers
{ {
// get original values: // get original values:
$response = $this->get($route, ['Accept' => 'application/json']); $response = $this->get($route, ['Accept' => 'application/json']);
$status = $response->getStatusCode(); $status = $response->getStatusCode();
$this->assertEquals($status, 200, sprintf(sprintf('%s failed with 404.', $route))); $this->assertEquals($status, 200, sprintf(sprintf('%s failed with 404.', $route)));
$response->assertStatus(200); $response->assertStatus(200);
$originalString = $response->content(); $originalString = $response->content();
@@ -238,13 +265,49 @@ trait TestHelpers
if ($this->ignoreCombination($route, $submission['type'] ?? 'blank', $returnName)) { if ($this->ignoreCombination($route, $submission['type'] ?? 'blank', $returnName)) {
continue; continue;
} }
// check if is array, if so we need something smart:
if (is_array($returnValue) && is_array($submission[$returnName])) {
$this->compareArray($returnName, $submission[$returnName], $returnValue);
}
if (!is_array($returnValue) && !is_array($submission[$returnName])) {
$message = sprintf(
"Main: Return value '%s' of key '%s' does not match submitted value '%s'.\n%s\n%s", $returnValue, $returnName, $submission[$returnName],
json_encode($submission), $responseBody
);
$this->assertEquals($returnValue, $submission[$returnName], $message);
}
$message = sprintf( }
"Return value '%s' of key '%s' does not match submitted value '%s'.\n%s\n%s", $returnValue, $returnName, $submission[$returnName], }
json_encode($submission), $responseBody }
);
$this->assertEquals($returnValue, $submission[$returnName], $message);
/**
* @param string $key
* @param array $original
* @param array $returned
*/
protected function compareArray(string $key, array $original, array $returned)
{
$ignore = ['id', 'created_at', 'updated_at'];
foreach ($returned as $objectKey => $object) {
// each object is a transaction, a rule trigger, a rule action, whatever.
// assume the original also contains this key:
if (!array_key_exists($objectKey, $original)) {
$message = sprintf('Sub: Original array "%s" does not have returned key %d.', $key, $objectKey);
$this->assertTrue(false, $message);
}
foreach ($object as $returnKey => $returnValue) {
if (in_array($returnKey, $ignore, true)) {
continue;
}
if (array_key_exists($returnKey, $original[$objectKey])) {
$message = sprintf(
'Sub: sub-array "%s" returned value %s does not match sent X value %s.',
$key, var_export($returnValue, true), var_export($original[$objectKey][$returnKey], true)
);
$this->assertEquals($original[$objectKey][$returnKey], $returnValue, $message);
}
} }
} }
} }