diff --git a/app/Import/FileProcessor/CsvProcessor.php b/app/Import/FileProcessor/CsvProcessor.php index 1ad9d6b973..22327c9445 100644 --- a/app/Import/FileProcessor/CsvProcessor.php +++ b/app/Import/FileProcessor/CsvProcessor.php @@ -26,7 +26,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Import\Object\ImportJournal; use FireflyIII\Import\Specifics\SpecificInterface; use FireflyIII\Models\ImportJob; -use FireflyIII\Models\TransactionJournalMeta; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use Illuminate\Support\Collection; use Iterator; use League\Csv\Reader; @@ -43,6 +43,8 @@ class CsvProcessor implements FileProcessorInterface private $job; /** @var Collection */ private $objects; + /** @var ImportJobRepositoryInterface */ + private $repository; /** @var array */ private $validConverters = []; /** @var array */ @@ -60,9 +62,14 @@ class CsvProcessor implements FileProcessorInterface /** * @return Collection + * @throws FireflyException */ public function getObjects(): Collection { + if (is_null($this->job)) { + throw new FireflyException('Cannot call getObjects() without a job.'); + } + return $this->objects; } @@ -73,9 +80,13 @@ class CsvProcessor implements FileProcessorInterface * * @throws \League\Csv\Exception * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException + * @throws FireflyException */ public function run(): bool { + if (is_null($this->job)) { + throw new FireflyException('Cannot call run() without a job.'); + } Log::debug('Now in CsvProcessor run(). Job is now running...'); $entries = new Collection($this->getImportArray()); @@ -86,8 +97,8 @@ class CsvProcessor implements FileProcessorInterface $row = array_values($row); if ($this->rowAlreadyImported($row)) { $message = sprintf('Row #%d has already been imported.', $index); - $this->job->addError($index, $message); - $this->job->addStepsDone(5); // all steps. + $this->repository->addStepsDone($this->job, 5); + $this->addError($index, $message); Log::info($message); return null; @@ -99,23 +110,34 @@ class CsvProcessor implements FileProcessorInterface Log::debug(sprintf('Number of entries left: %d', $notImported->count())); // set (new) number of steps: - $status = $this->job->extended_status; - $status['steps'] = $notImported->count() * 5; - $this->job->extended_status = $status; - $this->job->save(); - Log::debug(sprintf('Number of steps: %d', $notImported->count() * 5)); + $extended = $this->getExtendedStatus(); + $steps = $notImported->count() * 5; + $extended['steps'] = $steps; + $this->setExtendedStatus($extended); + Log::debug(sprintf('Number of steps: %d', $steps)); $notImported->each( function (array $row, int $index) { $journal = $this->importRow($index, $row); $this->objects->push($journal); - $this->job->addStepsDone(1); + $this->repository->addStepsDone($this->job, 1); } ); return true; } + /** + * @codeCoverageIgnore + * Shorthand method + * + * @param array $array + */ + public function setExtendedStatus(array $array) + { + $this->repository->setExtendedStatus($this->job, $array); + } + /** * Set import job for this processor. * @@ -125,11 +147,30 @@ class CsvProcessor implements FileProcessorInterface */ public function setJob(ImportJob $job): FileProcessorInterface { - $this->job = $job; + $this->job = $job; + $this->repository = app(ImportJobRepositoryInterface::class); + $this->repository->setUser($job->user); return $this; } + /** + * Shorthand method. + * + * @codeCoverageIgnore + * + * @param int $index + * @param string $message + */ + private function addError(int $index, string $message): void + { + $extended = $this->getExtendedStatus(); + $extended['errors'][$index][] = $message; + $this->setExtendedStatus($extended); + + return; + } + /** * Add meta data to the individual value and verify that it can be handled in a later stage. * @@ -142,7 +183,7 @@ class CsvProcessor implements FileProcessorInterface */ private function annotateValue(int $index, string $value) { - $config = $this->job->configuration; + $config = $this->getConfig(); $role = $config['column-roles'][$index] ?? '_ignore'; $mapped = $config['column-mapping-config'][$index][$value] ?? null; @@ -160,6 +201,29 @@ class CsvProcessor implements FileProcessorInterface return $entry; } + /** + * @codeCoverageIgnore + * Shorthand method. + * + * @return array + */ + private function getConfig(): array + { + return $this->repository->getConfiguration($this->job); + } + + /** + * @codeCoverageIgnore + * Shorthand method. + * + * @return array + */ + private function getExtendedStatus(): array + { + return $this->repository->getExtendedStatus($this->job); + } + + /** * @return Iterator * @@ -169,16 +233,17 @@ class CsvProcessor implements FileProcessorInterface */ private function getImportArray(): Iterator { - $content = $this->job->uploadFileContents(); - $config = $this->job->configuration; - $reader = Reader::createFromString($content); - $delimiter = $config['delimiter']; + $content = $this->repository->uploadFileContents($this->job); + $config = $this->getConfig(); + $reader = Reader::createFromString($content); + $delimiter = $config['delimiter'] ?? ','; + $hasHeaders = isset($config['has-headers']) ? $config['has-headers'] : false; if ('tab' === $delimiter) { - $delimiter = "\t"; + $delimiter = "\t"; // @codeCoverageIgnore } $reader->setDelimiter($delimiter); - if ($config['has-headers']) { - $reader->setHeaderOffset(0); + if ($hasHeaders) { + $reader->setHeaderOffset(0); // @codeCoverageIgnore } $results = $reader->getRecords(); Log::debug('Created a CSV reader.'); @@ -191,6 +256,7 @@ class CsvProcessor implements FileProcessorInterface * * @param int $jsonError * + * @codeCoverageIgnore * @return string */ private function getJsonError(int $jsonError): string @@ -230,7 +296,7 @@ class CsvProcessor implements FileProcessorInterface $jsonError = json_last_error(); if (false === $json) { - throw new FireflyException(sprintf('Error while encoding JSON for CSV row: %s', $this->getJsonError($jsonError))); + throw new FireflyException(sprintf('Error while encoding JSON for CSV row: %s', $this->getJsonError($jsonError))); // @codeCoverageIgnore } $hash = hash('sha256', $json); @@ -251,8 +317,9 @@ class CsvProcessor implements FileProcessorInterface { $row = array_values($row); Log::debug(sprintf('Now at row %d', $index)); - $row = $this->specifics($row); - $hash = $this->getRowHash($row); + $row = $this->specifics($row); + $hash = $this->getRowHash($row); + $config = $this->getConfig(); $journal = new ImportJournal; $journal->setUser($this->job->user); @@ -271,7 +338,8 @@ class CsvProcessor implements FileProcessorInterface } } // set some extra info: - $journal->asset->setDefaultAccountId($this->job->configuration['import-account']); + $importAccount = intval($config['import-account'] ?? 0); + $journal->asset->setDefaultAccountId($importAccount); return $journal; } @@ -288,12 +356,8 @@ class CsvProcessor implements FileProcessorInterface private function rowAlreadyImported(array $array): bool { $hash = $this->getRowHash($array); - $json = json_encode($hash); - $entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') - ->where('data', $json) - ->where('name', 'importHash') - ->first(); - if (null !== $entry) { + $count = $this->repository->countByHash($hash); + if ($count > 0) { return true; } diff --git a/app/Import/Mapper/AssetAccountIbans.php b/app/Import/Mapper/AssetAccountIbans.php index a782638ca1..634280ce32 100644 --- a/app/Import/Mapper/AssetAccountIbans.php +++ b/app/Import/Mapper/AssetAccountIbans.php @@ -44,19 +44,17 @@ class AssetAccountIbans implements MapperInterface /** @var Account $account */ foreach ($set as $account) { - $iban = $account->iban ?? ''; + $iban = $account->iban ?? ''; + $accountId = intval($account->id); if (strlen($iban) > 0) { - $topList[$account->id] = $account->iban . ' (' . $account->name . ')'; + $topList[$accountId] = $account->iban . ' (' . $account->name . ')'; } if (0 === strlen($iban)) { - $list[$account->id] = $account->name; + $list[$accountId] = $account->name; } } - asort($topList); - asort($list); - - $list = $topList + $list; - $list = [0 => trans('import.map_do_not_map')] + $list; + $list = array_merge($topList, $list); + $list = array_merge([0 => trans('import.map_do_not_map')], $list); return $list; } diff --git a/app/Import/Mapper/AssetAccounts.php b/app/Import/Mapper/AssetAccounts.php index 99b07bd40d..1a3e114e50 100644 --- a/app/Import/Mapper/AssetAccounts.php +++ b/app/Import/Mapper/AssetAccounts.php @@ -43,17 +43,15 @@ class AssetAccounts implements MapperInterface /** @var Account $account */ foreach ($set as $account) { - $name = $account->name; - $iban = $account->iban ?? ''; + $accountId = intval($account->id); + $name = $account->name; + $iban = $account->iban ?? ''; if (strlen($iban) > 0) { - $name .= ' (' . $account->iban . ')'; + $name .= ' (' . $iban . ')'; } - $list[$account->id] = $name; + $list[$accountId] = $name; } - - asort($list); - - $list = [0 => trans('import.map_do_not_map')] + $list; + $list = array_merge([0 => trans('import.map_do_not_map')], $list); return $list; } diff --git a/app/Import/Mapper/Bills.php b/app/Import/Mapper/Bills.php index d15544c414..6355fcc42e 100644 --- a/app/Import/Mapper/Bills.php +++ b/app/Import/Mapper/Bills.php @@ -42,11 +42,10 @@ class Bills implements MapperInterface /** @var Bill $bill */ foreach ($result as $bill) { - $list[$bill->id] = $bill->name . ' [' . $bill->match . ']'; + $billId = intval($bill->id); + $list[$billId] = $bill->name . ' [' . $bill->match . ']'; } - asort($list); - - $list = [0 => trans('import.map_do_not_map')] + $list; + $list = array_merge([0 => trans('import.map_do_not_map')], $list); return $list; } diff --git a/app/Import/Mapper/Budgets.php b/app/Import/Mapper/Budgets.php index f546e68049..123233444a 100644 --- a/app/Import/Mapper/Budgets.php +++ b/app/Import/Mapper/Budgets.php @@ -37,16 +37,15 @@ class Budgets implements MapperInterface { /** @var BudgetRepositoryInterface $repository */ $repository = app(BudgetRepositoryInterface::class); - $result = $repository->getBudgets(); + $result = $repository->getActiveBudgets(); $list = []; /** @var Budget $budget */ foreach ($result as $budget) { - $list[$budget->id] = $budget->name; + $budgetId = intval($budget->id); + $list[$budgetId] = $budget->name; } - asort($list); - - $list = [0 => trans('import.map_do_not_map')] + $list; + $list = array_merge([0 => trans('import.map_do_not_map')], $list); return $list; } diff --git a/app/Import/Mapper/Categories.php b/app/Import/Mapper/Categories.php index fae3276f94..4c34da3771 100644 --- a/app/Import/Mapper/Categories.php +++ b/app/Import/Mapper/Categories.php @@ -42,11 +42,10 @@ class Categories implements MapperInterface /** @var Category $category */ foreach ($result as $category) { - $list[$category->id] = $category->name; + $categoryId = intval($category->id); + $list[$categoryId] = $category->name; } - asort($list); - - $list = [0 => trans('import.map_do_not_map')] + $list; + $list = array_merge([0 => trans('import.map_do_not_map')], $list); return $list; } diff --git a/app/Import/Mapper/OpposingAccountIbans.php b/app/Import/Mapper/OpposingAccountIbans.php index 4471dcbf22..cb167e0f62 100644 --- a/app/Import/Mapper/OpposingAccountIbans.php +++ b/app/Import/Mapper/OpposingAccountIbans.php @@ -50,19 +50,17 @@ class OpposingAccountIbans implements MapperInterface /** @var Account $account */ foreach ($set as $account) { - $iban = $account->iban ?? ''; + $iban = $account->iban ?? ''; + $accountId = intval($account->id); if (strlen($iban) > 0) { - $topList[$account->id] = $account->iban . ' (' . $account->name . ')'; + $topList[$accountId] = $account->iban . ' (' . $account->name . ')'; } if (0 === strlen($iban)) { - $list[$account->id] = $account->name; + $list[$accountId] = $account->name; } } - asort($topList); - asort($list); - - $list = $topList + $list; - $list = [0 => trans('import.map_do_not_map')] + $list; + $list = array_merge($topList, $list); + $list = array_merge([0 => trans('import.map_do_not_map')], $list); return $list; } diff --git a/app/Import/Mapper/OpposingAccounts.php b/app/Import/Mapper/OpposingAccounts.php index d524530986..24c5c6a973 100644 --- a/app/Import/Mapper/OpposingAccounts.php +++ b/app/Import/Mapper/OpposingAccounts.php @@ -49,17 +49,15 @@ class OpposingAccounts implements MapperInterface /** @var Account $account */ foreach ($set as $account) { - $name = $account->name; - $iban = $account->iban ?? ''; + $accountId = intval($account->id); + $name = $account->name; + $iban = $account->iban ?? ''; if (strlen($iban) > 0) { - $name .= ' (' . $account->iban . ')'; + $name .= ' (' . $iban . ')'; } - $list[$account->id] = $name; + $list[$accountId] = $name; } - - asort($list); - - $list = [0 => trans('import.map_do_not_map')] + $list; + $list = array_merge([0 => trans('import.map_do_not_map')], $list); return $list; } diff --git a/app/Import/Mapper/Tags.php b/app/Import/Mapper/Tags.php index 1e36239b0e..f8be4a253e 100644 --- a/app/Import/Mapper/Tags.php +++ b/app/Import/Mapper/Tags.php @@ -42,11 +42,10 @@ class Tags implements MapperInterface /** @var Tag $tag */ foreach ($result as $tag) { - $list[$tag->id] = $tag->tag; + $tagId = intval($tag->id); + $list[$tagId] = $tag->tag; } - asort($list); - - $list = [0 => trans('import.map_do_not_map')] + $list; + $list = array_merge([0 => trans('import.map_do_not_map')], $list); return $list; } diff --git a/app/Import/Mapper/TransactionCurrencies.php b/app/Import/Mapper/TransactionCurrencies.php index 4e58b26285..4dcbd44296 100644 --- a/app/Import/Mapper/TransactionCurrencies.php +++ b/app/Import/Mapper/TransactionCurrencies.php @@ -22,7 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Import\Mapper; -use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; /** * Class TransactionCurrencies. @@ -34,15 +34,16 @@ class TransactionCurrencies implements MapperInterface */ public function getMap(): array { - $currencies = TransactionCurrency::get(); + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $currencies = $repository->get(); $list = []; foreach ($currencies as $currency) { - $list[$currency->id] = $currency->name . ' (' . $currency->code . ')'; + $currencyId = intval($currency->id); + $list[$currencyId] = $currency->name . ' (' . $currency->code . ')'; } - asort($list); - - $list = [0 => trans('import.map_do_not_map')] + $list; + $list = array_merge([0 => trans('import.map_do_not_map')], $list); return $list; } diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php index 57015f946b..f2bc9bc314 100644 --- a/app/Models/ImportJob.php +++ b/app/Models/ImportJob.php @@ -161,7 +161,7 @@ class ImportJob extends Model */ public function getExtendedStatusAttribute($value) { - if (0 === strlen($value)) { + if (0 === strlen(strval($value))) { return []; } diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index c0c45958bb..a8cc181ff6 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\ImportJob; use Crypt; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\ImportJob; +use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\User; use Illuminate\Support\Str; @@ -41,6 +42,37 @@ class ImportJobRepository implements ImportJobRepositoryInterface /** @var User */ private $user; + /** + * @param ImportJob $job + * @param int $steps + * + * @return ImportJob + */ + public function addStepsDone(ImportJob $job, int $steps = 1): ImportJob + { + $job->addStepsDone($steps); + + return $job; + } + + /** + * Return number of imported rows with this hash value. + * + * @param string $hash + * + * @return int + */ + public function countByHash(string $hash): int + { + $json = json_encode($hash); + $count = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') + ->where('data', $json) + ->where('name', 'importHash') + ->count(); + + return intval($count); + } + /** * @param string $type * @@ -112,6 +144,23 @@ class ImportJobRepository implements ImportJobRepositoryInterface return []; } + /** + * Return extended status of job. + * + * @param ImportJob $job + * + * @return array + */ + public function getExtendedStatus(ImportJob $job): array + { + $status = $job->extended_status; + if (is_array($status)) { + return $status; + } + + return []; + } + /** * @param ImportJob $job * @param UploadedFile $file @@ -204,6 +253,24 @@ class ImportJobRepository implements ImportJobRepositoryInterface return $job; } + /** + * @param ImportJob $job + * @param array $array + * + * @return ImportJob + */ + public function setExtendedStatus(ImportJob $job, array $array): ImportJob + { + Log::debug(sprintf('Incoming extended status for job "%s" is: ', $job->key), $array); + $currentStatus = $job->extended_status; + $newStatus = array_merge($currentStatus, $array); + $job->extended_status = $newStatus; + $job->save(); + Log::debug(sprintf('Set extended status of job "%s" to: ', $job->key), $newStatus); + + return $job; + } + /** * @param User $user */ diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index 43fd4efb25..658befe70a 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -31,6 +31,23 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; */ interface ImportJobRepositoryInterface { + /** + * @param ImportJob $job + * @param int $steps + * + * @return ImportJob + */ + public function addStepsDone(ImportJob $job, int $steps = 1): ImportJob; + + /** + * Return number of imported rows with this hash value. + * + * @param string $hash + * + * @return int + */ + public function countByHash(string $hash): int; + /** * @param string $type * @@ -38,15 +55,6 @@ interface ImportJobRepositoryInterface */ public function create(string $type): ImportJob; - /** - * Return import file content. - * - * @param ImportJob $job - * - * @return string - */ - public function uploadFileContents(ImportJob $job): string; - /** * @param string $key * @@ -63,6 +71,15 @@ interface ImportJobRepositoryInterface */ public function getConfiguration(ImportJob $job): array; + /** + * Return extended status of job. + * + * @param ImportJob $job + * + * @return array + */ + public function getExtendedStatus(ImportJob $job): array; + /** * @param ImportJob $job * @param UploadedFile $file @@ -87,6 +104,14 @@ interface ImportJobRepositoryInterface */ public function setConfiguration(ImportJob $job, array $configuration): ImportJob; + /** + * @param ImportJob $job + * @param array $array + * + * @return void + */ + public function setExtendedStatus(ImportJob $job, array $array): ImportJob; + /** * @param User $user */ @@ -99,4 +124,13 @@ interface ImportJobRepositoryInterface * @return ImportJob */ public function updateStatus(ImportJob $job, string $status): ImportJob; + + /** + * Return import file content. + * + * @param ImportJob $job + * + * @return string + */ + public function uploadFileContents(ImportJob $job): string; } diff --git a/tests/Unit/Import/Converter/AmountTest.php b/tests/Unit/Import/Converter/AmountTest.php index aa63fc1c2b..1b02d37ac6 100644 --- a/tests/Unit/Import/Converter/AmountTest.php +++ b/tests/Unit/Import/Converter/AmountTest.php @@ -32,6 +32,7 @@ class AmountTest extends TestCase { /** * @covers \FireflyIII\Import\Converter\Amount::convert() + * @covers \FireflyIII\Import\Converter\Amount::stripAmount() */ public function testConvert() { diff --git a/tests/Unit/Import/FileProcessor/CsvProcessorTest.php b/tests/Unit/Import/FileProcessor/CsvProcessorTest.php new file mode 100644 index 0000000000..515f804642 --- /dev/null +++ b/tests/Unit/Import/FileProcessor/CsvProcessorTest.php @@ -0,0 +1,300 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\FileProcessor; + +use FireflyIII\Import\FileProcessor\CsvProcessor; +use FireflyIII\Import\Specifics\AbnAmroDescription; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use Mockery; +use Tests\TestCase; + +/** + * Class CsvProcessorTest + */ +class CsvProcessorTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::__construct + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getObjects + * @expectedException \FireflyIII\Exceptions\FireflyException + * @expectedExceptionMessage Cannot call getObjects() without a job. + */ + public function testGetObjectsNoJob() + { + + $processor = new CsvProcessor(); + $processor->getObjects(); + } + + /** + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::run + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getImportArray + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getObjects + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::setJob + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::importRow + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::specifics + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getRowHash + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::annotateValue + * @expectedException \FireflyIII\Exceptions\FireflyException + * @expectedExceptionMessage "bad-role" is not a valid role. + */ + public function testRunBadRole() + { + // data + $config = [ + 'column-roles' => [ + 0 => 'bad-role', + ], + ]; + $job = $this->getJob($config); + $csvFile = '20170101,-12.34,"Some description"'; + + // mock stuff + $repository = $this->mock(ImportJobRepositoryInterface::class); + $repository->shouldReceive('setUser')->withArgs([Mockery::any()])->once(); + $repository->shouldReceive('getConfiguration')->andReturn($config); + $repository->shouldReceive('uploadFileContents')->withArgs([Mockery::any()])->andReturn($csvFile)->once(); + $repository->shouldReceive('getExtendedStatus')->once()->andReturn([]); + $repository->shouldReceive('setExtendedStatus')->once()->andReturn($job); + // mock stuff for this single row: + $repository->shouldReceive('countByHash')->once()->withArgs([Mockery::any()])->andReturn(0); + $processor = new CsvProcessor(); + $processor->setJob($job); + $processor->run(); + } + + /** + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::run + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getImportArray + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getObjects + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::setJob + */ + public function testRunBasic() + { + // data + $config = []; + $job = $this->getJob($config); + $csvFile = ''; + + // mock stuff + $repository = $this->mock(ImportJobRepositoryInterface::class); + $repository->shouldReceive('setUser')->withArgs([Mockery::any()])->once(); + $repository->shouldReceive('getConfiguration')->andReturn($config); + $repository->shouldReceive('uploadFileContents')->withArgs([Mockery::any()])->andReturn($csvFile)->once(); + $repository->shouldReceive('getExtendedStatus')->once()->andReturn([]); + $repository->shouldReceive('setExtendedStatus')->once()->andReturn($job); + + $processor = new CsvProcessor(); + $processor->setJob($job); + $processor->run(); + + $objects = $processor->getObjects(); + $this->assertCount(0, $objects); + } + + /** + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::run + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getImportArray + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getObjects + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::setJob + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::importRow + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::specifics + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getRowHash + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::annotateValue + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::rowAlreadyImported + */ + public function testRunExisting() + { + // data + $config = []; + $job = $this->getJob($config); + $csvFile = '20170101,-12.34,"Some description"'; + + // mock stuff + $repository = $this->mock(ImportJobRepositoryInterface::class); + $repository->shouldReceive('setUser')->withArgs([Mockery::any()])->once(); + $repository->shouldReceive('getConfiguration')->andReturn($config); + $repository->shouldReceive('uploadFileContents')->withArgs([Mockery::any()])->andReturn($csvFile)->once(); + $repository->shouldReceive('getExtendedStatus')->twice()->andReturn([]); // twice for update errors. + $repository->shouldReceive('setExtendedStatus')->twice()->andReturn($job); + // mock stuff for this single row: + $repository->shouldReceive('countByHash')->once()->withArgs([Mockery::any()])->andReturn(1); + $repository->shouldReceive('addStepsDone')->once()->withArgs([Mockery::any(), 5]); + $processor = new CsvProcessor(); + $processor->setJob($job); + $processor->run(); + + $objects = $processor->getObjects(); + $this->assertCount(0, $objects); + } + + /** + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::run + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getImportArray + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getObjects + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::setJob + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::importRow + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::specifics + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getRowHash + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::annotateValue + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::rowAlreadyImported + * @expectedException \FireflyIII\Exceptions\FireflyException + * @expectedExceptionMessage "GoodBankDescription" is not a valid class name + */ + public function testRunInvalidSpecific() + { + // data + $config = [ + 'specifics' => ['GoodBankDescription' => 1], + ]; + $job = $this->getJob($config); + $csvFile = '20170101,-12.34,descr'; + $row = explode(',', $csvFile); + + // mock stuff + $repository = $this->mock(ImportJobRepositoryInterface::class); + $repository->shouldReceive('setUser')->withArgs([Mockery::any()])->once(); + $repository->shouldReceive('getConfiguration')->andReturn($config); + $repository->shouldReceive('uploadFileContents')->withArgs([Mockery::any()])->andReturn($csvFile)->once(); + $repository->shouldReceive('getExtendedStatus')->once()->andReturn([]); + $repository->shouldReceive('setExtendedStatus')->once()->andReturn($job); + // mock stuff for this single row: + $repository->shouldReceive('countByHash')->once()->withArgs([Mockery::any()])->andReturn(0); + + // mock specific: + $processor = new CsvProcessor(); + $processor->setJob($job); + $processor->run(); + } + + /** + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::run + * @expectedException \FireflyIII\Exceptions\FireflyException + * @expectedExceptionMessage Cannot call run() without a job. + */ + public function testRunNoJob() + { + + $processor = new CsvProcessor(); + $processor->run(); + } + + /** + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::run + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getImportArray + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getObjects + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::setJob + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::importRow + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::specifics + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getRowHash + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::annotateValue + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::rowAlreadyImported + */ + public function testRunSingle() + { + // data + $config = []; + $job = $this->getJob($config); + $csvFile = '20170101,-12.34,"Some description"'; + + // mock stuff + $repository = $this->mock(ImportJobRepositoryInterface::class); + $repository->shouldReceive('setUser')->withArgs([Mockery::any()])->once(); + $repository->shouldReceive('getConfiguration')->andReturn($config); + $repository->shouldReceive('uploadFileContents')->withArgs([Mockery::any()])->andReturn($csvFile)->once(); + $repository->shouldReceive('getExtendedStatus')->once()->andReturn([]); + $repository->shouldReceive('setExtendedStatus')->once()->andReturn($job); + // mock stuff for this single row: + $repository->shouldReceive('countByHash')->once()->withArgs([Mockery::any()])->andReturn(0); + $repository->shouldReceive('addStepsDone')->once()->withArgs([Mockery::any(), 1]); + $processor = new CsvProcessor(); + $processor->setJob($job); + $processor->run(); + + $objects = $processor->getObjects(); + $this->assertCount(1, $objects); + } + + /** + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::run + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getImportArray + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getObjects + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::setJob + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::importRow + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::specifics + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::getRowHash + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::annotateValue + * @covers \FireflyIII\Import\FileProcessor\CsvProcessor::rowAlreadyImported + */ + public function testRunSpecific() + { + // data + $config = [ + 'specifics' => ['AbnAmroDescription' => 1], + ]; + $job = $this->getJob($config); + $csvFile = '20170101,-12.34,descr'; + $row = explode(',', $csvFile); + + // mock stuff + $repository = $this->mock(ImportJobRepositoryInterface::class); + $repository->shouldReceive('setUser')->withArgs([Mockery::any()])->once(); + $repository->shouldReceive('getConfiguration')->andReturn($config); + $repository->shouldReceive('uploadFileContents')->withArgs([Mockery::any()])->andReturn($csvFile)->once(); + $repository->shouldReceive('getExtendedStatus')->once()->andReturn([]); + $repository->shouldReceive('setExtendedStatus')->once()->andReturn($job); + // mock stuff for this single row: + $repository->shouldReceive('countByHash')->once()->withArgs([Mockery::any()])->andReturn(0); + $repository->shouldReceive('addStepsDone')->once()->withArgs([Mockery::any(), 1]); + + // mock specific: + $specific = $this->mock(AbnAmroDescription::class); + $specific->shouldReceive('run')->once()->andReturn($row); + + $processor = new CsvProcessor(); + $processor->setJob($job); + $processor->run(); + + $objects = $processor->getObjects(); + $this->assertCount(1, $objects); + } + + /** + * @param array $config + * + * @return ImportJob + */ + protected function getJob(array $config): ImportJob + { + $job = new ImportJob; + $job->file_type = 'file'; + $job->status = 'new'; + $job->key = 'x' . rand(1, 100000); + $job->user()->associate($this->user()); + $job->configuration = $config; + + return $job; + } +} \ No newline at end of file diff --git a/tests/Unit/Import/Mapper/AssetAccountIbansTest.php b/tests/Unit/Import/Mapper/AssetAccountIbansTest.php new file mode 100644 index 0000000000..dab4138175 --- /dev/null +++ b/tests/Unit/Import/Mapper/AssetAccountIbansTest.php @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\Mapper; + +use FireflyIII\Import\Mapper\AssetAccountIbans; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Illuminate\Support\Collection; +use Tests\TestCase; + +/** + * Class AssetAccountIbansTest + */ +class AssetAccountIbansTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\Mapper\AssetAccountIbans::getMap() + */ + public function testGetMapBasic() + { + $one = new Account; + $one->id = 1; + $one->name = 'Something'; + $one->iban = 'IBAN'; + $two = new Account; + $two->id = 2; + $two->name = 'Else'; + $collection = new Collection([$one, $two]); + + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->andReturn($collection)->once(); + + $mapper = new AssetAccountIbans(); + $mapping = $mapper->getMap(); + $this->assertCount(3, $mapping); + // assert this is what the result looks like: + $result = [ + 0 => strval(trans('import.map_do_not_map')), + 1 => 'IBAN (Something)', + 2 => 'Else', + ]; + $this->assertEquals($result, $mapping); + } + +} \ No newline at end of file diff --git a/tests/Unit/Import/Mapper/AssetAccountsTest.php b/tests/Unit/Import/Mapper/AssetAccountsTest.php new file mode 100644 index 0000000000..0b1cec1e39 --- /dev/null +++ b/tests/Unit/Import/Mapper/AssetAccountsTest.php @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\Mapper; + +use FireflyIII\Import\Mapper\AssetAccounts; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Illuminate\Support\Collection; +use Tests\TestCase; + +/** + * Class AssetAccountsTest + */ +class AssetAccountsTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\Mapper\AssetAccounts::getMap() + */ + public function testGetMapBasic() + { + $one = new Account; + $one->id = 1; + $one->name = 'Something'; + $one->iban = 'IBAN'; + $two = new Account; + $two->id = 2; + $two->name = 'Else'; + $collection = new Collection([$one, $two]); + + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getAccountsByType')->withArgs([[AccountType::DEFAULT, AccountType::ASSET]])->andReturn($collection)->once(); + + $mapper = new AssetAccounts(); + $mapping = $mapper->getMap(); + $this->assertCount(3, $mapping); + // assert this is what the result looks like: + $result = [ + 0 => strval(trans('import.map_do_not_map')), + 1 => 'Something (IBAN)', + 2 => 'Else', + ]; + $this->assertEquals($result, $mapping); + } + +} diff --git a/tests/Unit/Import/Mapper/BillsTest.php b/tests/Unit/Import/Mapper/BillsTest.php new file mode 100644 index 0000000000..1593ff726d --- /dev/null +++ b/tests/Unit/Import/Mapper/BillsTest.php @@ -0,0 +1,68 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\Mapper; + +use FireflyIII\Import\Mapper\Bills; +use FireflyIII\Models\Account; +use FireflyIII\Models\Bill; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use Illuminate\Support\Collection; +use Tests\TestCase; + +/** + * Class BillsTest + */ +class BillsTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\Mapper\Bills::getMap() + */ + public function testGetMapBasic() + { + $one = new Bill(); + $one->id = 1; + $one->name = 'Something'; + $one->match = 'hi,bye'; + $two = new Account; + $two->id = 2; + $two->name = 'Else'; + $two->match = 'match'; + $collection = new Collection([$one, $two]); + + $repository = $this->mock(BillRepositoryInterface::class); + $repository->shouldReceive('getBills')->andReturn($collection)->once(); + + $mapper = new Bills(); + $mapping = $mapper->getMap(); + $this->assertCount(3, $mapping); + // assert this is what the result looks like: + $result = [ + 0 => strval(trans('import.map_do_not_map')), + 1 => 'Something [hi,bye]', + 2 => 'Else [match]', + ]; + $this->assertEquals($result, $mapping); + } + +} \ No newline at end of file diff --git a/tests/Unit/Import/Mapper/BudgetsTest.php b/tests/Unit/Import/Mapper/BudgetsTest.php new file mode 100644 index 0000000000..98c492840d --- /dev/null +++ b/tests/Unit/Import/Mapper/BudgetsTest.php @@ -0,0 +1,65 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\Mapper; + +use FireflyIII\Import\Mapper\Budgets; +use FireflyIII\Models\Budget; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use Illuminate\Support\Collection; +use Tests\TestCase; + +/** + * Class BudgetsTest + */ +class BudgetsTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\Mapper\Budgets::getMap() + */ + public function testGetMapBasic() + { + $one = new Budget; + $one->id = 1; + $one->name = 'Something'; + $two = new Budget; + $two->id = 2; + $two->name = 'Else'; + $collection = new Collection([$one, $two]); + + $repository = $this->mock(BudgetRepositoryInterface::class); + $repository->shouldReceive('getActiveBudgets')->andReturn($collection)->once(); + + $mapper = new Budgets(); + $mapping = $mapper->getMap(); + $this->assertCount(3, $mapping); + // assert this is what the result looks like: + $result = [ + 0 => strval(trans('import.map_do_not_map')), + 1 => 'Something', + 2 => 'Else', + ]; + $this->assertEquals($result, $mapping); + } + +} \ No newline at end of file diff --git a/tests/Unit/Import/Mapper/CategoriesTest.php b/tests/Unit/Import/Mapper/CategoriesTest.php new file mode 100644 index 0000000000..19bae979f5 --- /dev/null +++ b/tests/Unit/Import/Mapper/CategoriesTest.php @@ -0,0 +1,65 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\Mapper; + +use FireflyIII\Import\Mapper\Categories; +use FireflyIII\Models\Category; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use Illuminate\Support\Collection; +use Tests\TestCase; + +/** + * Class CategoriesTest + */ +class CategoriesTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\Mapper\Categories::getMap() + */ + public function testGetMapBasic() + { + $one = new Category; + $one->id = 1; + $one->name = 'Something'; + $two = new Category; + $two->id = 2; + $two->name = 'Else'; + $collection = new Collection([$one, $two]); + + $repository = $this->mock(CategoryRepositoryInterface::class); + $repository->shouldReceive('getCategories')->andReturn($collection)->once(); + + $mapper = new Categories(); + $mapping = $mapper->getMap(); + $this->assertCount(3, $mapping); + // assert this is what the result looks like: + $result = [ + 0 => strval(trans('import.map_do_not_map')), + 1 => 'Something', + 2 => 'Else', + ]; + $this->assertEquals($result, $mapping); + } + +} \ No newline at end of file diff --git a/tests/Unit/Import/Mapper/OpposingAccountIbansTest.php b/tests/Unit/Import/Mapper/OpposingAccountIbansTest.php new file mode 100644 index 0000000000..b54cda0f2a --- /dev/null +++ b/tests/Unit/Import/Mapper/OpposingAccountIbansTest.php @@ -0,0 +1,69 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\Mapper; + +use FireflyIII\Import\Mapper\OpposingAccountIbans; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Illuminate\Support\Collection; +use Tests\TestCase; + +/** + * Class OpposingAccountIbansTest + */ +class OpposingAccountIbansTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\Mapper\OpposingAccountIbans::getMap() + */ + public function testGetMapBasic() + { + $one = new Account; + $one->id = 1; + $one->name = 'Something'; + $one->iban = 'IBAN'; + $two = new Account; + $two->id = 2; + $two->name = 'Else'; + $collection = new Collection([$one, $two]); + + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getAccountsByType')->withArgs( + [[AccountType::DEFAULT, AccountType::ASSET, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::REVENUE,]] + )->andReturn($collection)->once(); + + $mapper = new OpposingAccountIbans(); + $mapping = $mapper->getMap(); + $this->assertCount(3, $mapping); + // assert this is what the result looks like: + $result = [ + 0 => strval(trans('import.map_do_not_map')), + 1 => 'IBAN (Something)', + 2 => 'Else', + ]; + $this->assertEquals($result, $mapping); + } + +} \ No newline at end of file diff --git a/tests/Unit/Import/Mapper/OpposingAccountsTest.php b/tests/Unit/Import/Mapper/OpposingAccountsTest.php new file mode 100644 index 0000000000..170926ade5 --- /dev/null +++ b/tests/Unit/Import/Mapper/OpposingAccountsTest.php @@ -0,0 +1,69 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\Mapper; + +use FireflyIII\Import\Mapper\OpposingAccounts; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Illuminate\Support\Collection; +use Tests\TestCase; + +/** + * Class OpposingAccountsTest + */ +class OpposingAccountsTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\Mapper\OpposingAccounts::getMap() + */ + public function testGetMapBasic() + { + $one = new Account; + $one->id = 1; + $one->name = 'Something'; + $one->iban = 'IBAN'; + $two = new Account; + $two->id = 2; + $two->name = 'Else'; + $collection = new Collection([$one, $two]); + + $repository = $this->mock(AccountRepositoryInterface::class); + $repository->shouldReceive('getAccountsByType')->withArgs( + [[AccountType::DEFAULT, AccountType::ASSET, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::REVENUE,]] + )->andReturn($collection)->once(); + + $mapper = new OpposingAccounts(); + $mapping = $mapper->getMap(); + $this->assertCount(3, $mapping); + // assert this is what the result looks like: + $result = [ + 0 => strval(trans('import.map_do_not_map')), + 1 => 'Something (IBAN)', + 2 => 'Else', + ]; + $this->assertEquals($result, $mapping); + } + +} diff --git a/tests/Unit/Import/Mapper/TagsTest.php b/tests/Unit/Import/Mapper/TagsTest.php new file mode 100644 index 0000000000..2a98475a0e --- /dev/null +++ b/tests/Unit/Import/Mapper/TagsTest.php @@ -0,0 +1,65 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\Mapper; + +use FireflyIII\Import\Mapper\Tags; +use FireflyIII\Models\Tag; +use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use Illuminate\Support\Collection; +use Tests\TestCase; + +/** + * Class TagsTest + */ +class TagsTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\Mapper\Tags::getMap() + */ + public function testGetMapBasic() + { + $one = new Tag; + $one->id = 1; + $one->tag = 'Something'; + $two = new Tag; + $two->id = 2; + $two->tag = 'Else'; + $collection = new Collection([$one, $two]); + + $repository = $this->mock(TagRepositoryInterface::class); + $repository->shouldReceive('get')->andReturn($collection)->once(); + + $mapper = new Tags(); + $mapping = $mapper->getMap(); + $this->assertCount(3, $mapping); + // assert this is what the result looks like: + $result = [ + 0 => strval(trans('import.map_do_not_map')), + 1 => 'Something', + 2 => 'Else', + ]; + $this->assertEquals($result, $mapping); + } + +} \ No newline at end of file diff --git a/tests/Unit/Import/Mapper/TransactionCurrenciesTest.php b/tests/Unit/Import/Mapper/TransactionCurrenciesTest.php new file mode 100644 index 0000000000..295cb3c1de --- /dev/null +++ b/tests/Unit/Import/Mapper/TransactionCurrenciesTest.php @@ -0,0 +1,67 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Unit\Import\Mapper; + +use FireflyIII\Import\Mapper\TransactionCurrencies; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use Illuminate\Support\Collection; +use Tests\TestCase; + +/** + * Class TransactionCurrenciesTest + */ +class TransactionCurrenciesTest extends TestCase +{ + /** + * @covers \FireflyIII\Import\Mapper\TransactionCurrencies::getMap() + */ + public function testGetMapBasic() + { + $one = new TransactionCurrency; + $one->id = 1; + $one->name = 'Something'; + $one->code = 'ABC'; + $two = new TransactionCurrency; + $two->id = 2; + $two->name = 'Else'; + $two->code = 'DEF'; + $collection = new Collection([$one, $two]); + + $repository = $this->mock(CurrencyRepositoryInterface::class); + $repository->shouldReceive('get')->andReturn($collection)->once(); + + $mapper = new TransactionCurrencies(); + $mapping = $mapper->getMap(); + $this->assertCount(3, $mapping); + // assert this is what the result looks like: + $result = [ + 0 => strval(trans('import.map_do_not_map')), + 1 => 'Something (ABC)', + 2 => 'Else (DEF)', + ]; + $this->assertEquals($result, $mapping); + } + +} \ No newline at end of file