From 18e90af5aa4c138dc7b8dd1a55f5c6717ec5a0a9 Mon Sep 17 00:00:00 2001 From: Toby Twigger Date: Wed, 8 Apr 2020 16:54:28 +0100 Subject: [PATCH] Create the exporter framework --- CHANGELOG.md | 8 +- composer.json | 4 +- config/control.php | 55 +++++ src/ControlDBServiceProvider.php | 9 +- src/Export/ExportControlCommand.php | 73 ++++++ src/Export/ExportManager.php | 173 ++++++++++++++ src/Export/Exporter.php | 26 +++ src/Export/FormattedItem.php | 89 ++++++++ src/Export/Formatter/Formatter.php | 94 ++++++++ .../Formatter/Group/SimpleGroupFormatter.php | 23 ++ .../Role/AddGroupInformationToRoles.php | 25 ++ .../Role/AddPositionInformationToRoles.php | 24 ++ .../Role/AddRoleHoldersAsNewItems.php | 38 ++++ .../Formatter/Role/SimpleRoleFormatter.php | 23 ++ src/Export/Formatter/Shared/SortByColumn.php | 60 +++++ .../Formatter/User/SimpleUserFormatter.php | 27 +++ src/Export/Handler/DumpHandler.php | 17 ++ src/Export/Handler/Handler.php | 80 +++++++ src/Export/Handler/SaveCsvHandler.php | 21 ++ src/Export/Handler/UsesCsv.php | 65 ++++++ .../Unit/Export/ExportControlCommandTest.php | 150 ++++++++++++ tests/Unit/Export/ExportManagerTest.php | 214 ++++++++++++++++++ tests/Unit/Export/ExporterTest.php | 22 ++ tests/Unit/Export/FormattedItemTest.php | 127 +++++++++++ tests/Unit/Export/Formatter/FormatterTest.php | 164 ++++++++++++++ .../Group/SimpleGroupFormatterTest.php | 38 ++++ .../Role/AddGroupInformationToRolesTest.php | 43 ++++ .../Role/AddPostionInformationToRolesTest.php | 43 ++++ .../Role/AddRoleHoldersAsNewItemsTest.php | 53 +++++ .../Role/SimpleRoleFormatterTest.php | 38 ++++ .../Formatter/Shared/SortByColumnTest.php | 69 ++++++ .../User/SimpleUserFormatterTest.php | 40 ++++ tests/Unit/Export/Handler/HandlerTest.php | 125 ++++++++++ .../Export/Handler/SaveCsvHandlerTest.php | 105 +++++++++ tests/Unit/Export/Handler/UsesCsvTest.php | 139 ++++++++++++ 35 files changed, 2300 insertions(+), 4 deletions(-) create mode 100644 src/Export/ExportControlCommand.php create mode 100644 src/Export/ExportManager.php create mode 100644 src/Export/Exporter.php create mode 100644 src/Export/FormattedItem.php create mode 100644 src/Export/Formatter/Formatter.php create mode 100644 src/Export/Formatter/Group/SimpleGroupFormatter.php create mode 100644 src/Export/Formatter/Role/AddGroupInformationToRoles.php create mode 100644 src/Export/Formatter/Role/AddPositionInformationToRoles.php create mode 100644 src/Export/Formatter/Role/AddRoleHoldersAsNewItems.php create mode 100644 src/Export/Formatter/Role/SimpleRoleFormatter.php create mode 100644 src/Export/Formatter/Shared/SortByColumn.php create mode 100644 src/Export/Formatter/User/SimpleUserFormatter.php create mode 100644 src/Export/Handler/DumpHandler.php create mode 100644 src/Export/Handler/Handler.php create mode 100644 src/Export/Handler/SaveCsvHandler.php create mode 100644 src/Export/Handler/UsesCsv.php create mode 100644 tests/Unit/Export/ExportControlCommandTest.php create mode 100644 tests/Unit/Export/ExportManagerTest.php create mode 100644 tests/Unit/Export/ExporterTest.php create mode 100644 tests/Unit/Export/FormattedItemTest.php create mode 100644 tests/Unit/Export/Formatter/FormatterTest.php create mode 100644 tests/Unit/Export/Formatter/Group/SimpleGroupFormatterTest.php create mode 100644 tests/Unit/Export/Formatter/Role/AddGroupInformationToRolesTest.php create mode 100644 tests/Unit/Export/Formatter/Role/AddPostionInformationToRolesTest.php create mode 100644 tests/Unit/Export/Formatter/Role/AddRoleHoldersAsNewItemsTest.php create mode 100644 tests/Unit/Export/Formatter/Role/SimpleRoleFormatterTest.php create mode 100644 tests/Unit/Export/Formatter/Shared/SortByColumnTest.php create mode 100644 tests/Unit/Export/Formatter/User/SimpleUserFormatterTest.php create mode 100644 tests/Unit/Export/Handler/HandlerTest.php create mode 100644 tests/Unit/Export/Handler/SaveCsvHandlerTest.php create mode 100644 tests/Unit/Export/Handler/UsesCsvTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e0af4..0675c5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.3.0] - (08/04/2020) + +### Added +- Create the exporter framework for exporting control information + ## [1.2.4] - (03/04/2020) ### Changed @@ -77,7 +82,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - model/repository contracts -[Unreleased]: https://github.com/bristol-su/control/compare/v1.2.4...HEAD +[Unreleased]: https://github.com/bristol-su/control/compare/v1.3.0...HEAD +[1.3.0]: https://github.com/bristol-su/control/compare/v1.2.4...v1.3.0 [1.2.4]: https://github.com/bristol-su/control/compare/v1.2.3...v1.2.4 [1.2.3]: https://github.com/bristol-su/control/compare/v1.2.2...v1.2.3 [1.2.2]: https://github.com/bristol-su/control/compare/v1.2.1...v1.2.2 diff --git a/composer.json b/composer.json index a3637f1..f9a9d01 100644 --- a/composer.json +++ b/composer.json @@ -13,9 +13,9 @@ "php": "^7.2", "ext-json": "*" }, - "require-dev" : { + "require-dev": { "orchestra/testbench": "^4.0", - "phpstan/phpstan": "^0.12.8" + "phpstan/phpstan": "^0.12.8" }, "extra": { "laravel": { diff --git a/config/control.php b/config/control.php index 0dc156c..98d332e 100644 --- a/config/control.php +++ b/config/control.php @@ -4,5 +4,60 @@ 'api_prefix' => env('CONTROL_API_PREFIX', '/api/control'), 'api_middleware' => [ 'api' + ], + + 'export' => [ + + /* + |-------------------------------------------------------------------------- + | Default Export Method + |-------------------------------------------------------------------------- + | + | This option defines the default export method for exporting control data. + | The name specified in this option should match one of the configuration options + | listed below. + | + */ + 'default' => 'contact-details', + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the export drivers for control. Each option must + | reference a driver to use, and may additionally register any formatters + | or configuration for that driver. + | + | Available Drivers: + | - save-csv Save a csv to a given disk system + | - dump Dump the results using the symfony dumper + | + */ + 'local-roles' => [ + 'driver' => 'save-csv', + 'formatters' => [ + \BristolSU\ControlDB\Export\Formatter\Role\SimpleRoleFormatter::class => [], + \BristolSU\ControlDB\Export\Formatter\Role\AddGroupInformationToRoles::class => [], + \BristolSU\ControlDB\Export\Formatter\Role\AddPositionInformationToRoles::class => [] + ], + 'disk' => 'local', + 'filename' => 'export.csv', + ], + + 'contact-details' => [ + 'driver' => 'save-csv', + 'formatters' => [ + \BristolSU\ControlDB\Export\Formatter\Role\SimpleRoleFormatter::class => [], + \BristolSU\ControlDB\Export\Formatter\Role\AddGroupInformationToRoles::class => [], + \BristolSU\ControlDB\Export\Formatter\Role\AddPositionInformationToRoles::class => [], + \BristolSU\ControlDB\Export\Formatter\Role\AddRoleHoldersAsNewItems::class => [], + \BristolSU\ControlDB\Export\Formatter\Shared\SortByColumn::class => [ + 'column' => 'Group Name' + ] + ], + 'disk' => 'local', + 'filename' => 'contact_details.csv', + ] ] ]; \ No newline at end of file diff --git a/src/ControlDBServiceProvider.php b/src/ControlDBServiceProvider.php index 6d749c1..8a8bca3 100644 --- a/src/ControlDBServiceProvider.php +++ b/src/ControlDBServiceProvider.php @@ -20,6 +20,8 @@ use BristolSU\ControlDB\Contracts\Repositories\Pivots\Tags\PositionPositionTag as PositionPositionTagContract; use BristolSU\ControlDB\Contracts\Repositories\Pivots\UserGroup as UserGroupContract; use BristolSU\ControlDB\Contracts\Repositories\Pivots\UserRole as UserRoleContract; +use BristolSU\ControlDB\Export\ExportControlCommand; +use BristolSU\ControlDB\Export\ExportManager; use BristolSU\ControlDB\Models\DataUser; use BristolSU\ControlDB\Models\DataGroup; use BristolSU\ControlDB\Models\DataRole; @@ -99,6 +101,10 @@ public function register() $this->registerMigrations(); $this->registerConfig(); $this->registerFactories(); + + $this->app->singleton('control-exporter', function() { + return new ExportManager($this->app); + }); } public function boot() @@ -197,7 +203,8 @@ public function bindContracts() public function registerCommands() { $this->commands([ - SeedDatabase::class + SeedDatabase::class, + ExportControlCommand::class ]); } diff --git a/src/Export/ExportControlCommand.php b/src/Export/ExportControlCommand.php new file mode 100644 index 0000000..3570a26 --- /dev/null +++ b/src/Export/ExportControlCommand.php @@ -0,0 +1,73 @@ +option('exporter'))->export($this->exportData()); + $this->info('Export complete'); + } + + private function exportData() + { + switch($this->argument('type')) { + case 'user': + return app(User::class)->all(); + break; + case 'group': + return app(Group::class)->all(); + break; + case 'role': + return app(Role::class)->all(); + break; + case 'position': + return app(Position::class)->all(); + break; + default: + throw new InvalidArgumentException(sprintf('The type option %s is not allowed.', $this->argument('type'))); + break; + } + } +} \ No newline at end of file diff --git a/src/Export/ExportManager.php b/src/Export/ExportManager.php new file mode 100644 index 0000000..2b53e2a --- /dev/null +++ b/src/Export/ExportManager.php @@ -0,0 +1,173 @@ +container = $container; + } + + /** + * Get a export driver instance. + * + * @param string|null $driver + * @return mixed + */ + public function driver($driver = null) + { + return $this->resolve($driver ?? $this->getDefaultDriver()); + } + + /** + * Resolve the given export instance by name. + * + * @param string $name + * @return Handler + * + * @throws \InvalidArgumentException + */ + protected function resolve($name) + { + $config = $this->configurationFor($name); + + if (is_null($config)) { + throw new InvalidArgumentException("Exporter [{$name}] is not defined."); + } + if (isset($this->customCreators[$config['driver']])) { + return $this->callCustomCreator($config); + } + + $driverMethod = 'create'.Str::studly($config['driver']).'Driver'; + + if (method_exists($this, $driverMethod)) { + return $this->{$driverMethod}($config); + } + + throw new InvalidArgumentException("Driver [{$config['driver']}] is not supported."); + } + + /** + * Call a custom driver creator. + * + * @param array $config + * @return mixed + */ + protected function callCustomCreator(array $config) + { + return $this->customCreators[$config['driver']]($this->container, $config); + } + + /** + * Get the export connection configuration. + * + * @param string $name + * @return array + */ + protected function configurationFor($name) + { + $config = $this->container['config']["control.export.{$name}"]; + if(is_null($config)) { + return null; + } + if(!isset($config['formatters'])) { + $config['formatters'] = []; + } + foreach($this->formatters() as $formatter => $formatterConfig) { + $config['formatters'][$formatter] = $formatterConfig; + } + + return $config; + } + + /** + * Get the default export driver name. + * + * @return string + */ + public function getDefaultDriver() + { + return $this->container['config']['control.export.default']; + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param \Closure $callback + * @return $this + */ + public function extend($driver, Closure $callback) + { + $this->customCreators[$driver] = $callback->bindTo($this, $this); + + return $this; + } + + public function withFormatter(string $formatter, array $config = []) + { + $this->formatters[$formatter] = $config; + + return $this; + } + + /** + * Dynamically call the default driver instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->driver()->$method(...$parameters); + } + + public function createDumpDriver(array $config) + { + return new DumpHandler($config); + } + + public function createSaveCsvDriver(array $config) + { + return new SaveCsvHandler($config); + } + + protected function formatters() + { + return $this->formatters; + } + +} \ No newline at end of file diff --git a/src/Export/Exporter.php b/src/Export/Exporter.php new file mode 100644 index 0000000..632943f --- /dev/null +++ b/src/Export/Exporter.php @@ -0,0 +1,26 @@ +original = $original; + } + + public static function create($original) + { + return new static($original); + } + + public function original() + { + return $this->original; + } + + public function isType(string $type) + { + return $this->original() instanceof $type; + } + + + public function addRow($key, $value) + { + $this->prepared[$key] = $value; + } + + public function getColumnNames() + { + return array_keys($this->prepared); + } + + public function getItem($column, $default = null) + { + if(array_key_exists($column, $this->prepared) && $this->prepared[$column] !== null) { + return $this->prepared[$column]; + } + return $default; + } + + public function preparedItems(): array + { + return $this->prepared; + } + + /** + * @inheritDoc + */ + public function toArray() + { + return $this->preparedItems(); + } + + /** + * @inheritDoc + */ + public function toJson($options = 0) + { + return json_encode($this->toArray(), 0); + } + + public function __toString() + { + return $this->toJson(); + } +} \ No newline at end of file diff --git a/src/Export/Formatter/Formatter.php b/src/Export/Formatter/Formatter.php new file mode 100644 index 0000000..a9e9e34 --- /dev/null +++ b/src/Export/Formatter/Formatter.php @@ -0,0 +1,94 @@ +config = $config; + } + + /** + * @param string $key + * @param null $default + * + * @return mixed|null + */ + protected function config(string $key, $default = null) + { + if(array_key_exists($key, $this->config)) { + return $this->config[$key]; + } + return $default; + } + + /** + * @param array|FormattedItem[] $items Items ready to format + * @return array + */ + public function format($items) + { + return array_map(function($item) { + return $this->canHandle($item) ? $this->formatItem($item) : $item; + }, $items); + } + + abstract public function formatItem(FormattedItem $formattedItem): FormattedItem; + + abstract public function handles(): string; + + protected function canHandle(FormattedItem $item): bool + { + if($this->handles() === self::ALL) { + return true; + } + return $item->isType($this->handles()); + } + +} \ No newline at end of file diff --git a/src/Export/Formatter/Group/SimpleGroupFormatter.php b/src/Export/Formatter/Group/SimpleGroupFormatter.php new file mode 100644 index 0000000..f193bb5 --- /dev/null +++ b/src/Export/Formatter/Group/SimpleGroupFormatter.php @@ -0,0 +1,23 @@ +addRow('Group ID', $formattedItem->original()->id()); + $formattedItem->addRow('Group Name', $formattedItem->original()->data()->name()); + $formattedItem->addRow('Group Email', $formattedItem->original()->data()->email()); + return $formattedItem; + } + + public function handles(): string + { + return static::GROUPS; + } +} \ No newline at end of file diff --git a/src/Export/Formatter/Role/AddGroupInformationToRoles.php b/src/Export/Formatter/Role/AddGroupInformationToRoles.php new file mode 100644 index 0000000..c03da9e --- /dev/null +++ b/src/Export/Formatter/Role/AddGroupInformationToRoles.php @@ -0,0 +1,25 @@ +original()->group(); + $dataGroup = $group->data(); + $formattedItem->addRow('Group ID', $group->id()); + $formattedItem->addRow('Group Name', $dataGroup->name()); + $formattedItem->addRow('Group Email', $dataGroup->email()); + return $formattedItem; + } + + public function handles(): string + { + return static::ROLES; + } +} \ No newline at end of file diff --git a/src/Export/Formatter/Role/AddPositionInformationToRoles.php b/src/Export/Formatter/Role/AddPositionInformationToRoles.php new file mode 100644 index 0000000..7cf6a7c --- /dev/null +++ b/src/Export/Formatter/Role/AddPositionInformationToRoles.php @@ -0,0 +1,24 @@ +original()->position(); + $dataPosition = $position->data(); + $formattedItem->addRow('Position ID', $position->id()); + $formattedItem->addRow('Position Name', $dataPosition->name()); + return $formattedItem; + } + + public function handles(): string + { + return static::ROLES; + } +} \ No newline at end of file diff --git a/src/Export/Formatter/Role/AddRoleHoldersAsNewItems.php b/src/Export/Formatter/Role/AddRoleHoldersAsNewItems.php new file mode 100644 index 0000000..421fe7e --- /dev/null +++ b/src/Export/Formatter/Role/AddRoleHoldersAsNewItems.php @@ -0,0 +1,38 @@ +original()->users() as $user) { + $newItem = clone $item; + $dataUser = $user->data(); + $newItem->addRow('User ID', $user->id()); + $newItem->addRow('User First Name', $dataUser->firstName()); + $newItem->addRow('User Last Name', $dataUser->lastName()); + $newItem->addRow('User Preferred Name', $dataUser->preferredName()); + $newItem->addRow('User Email', $dataUser->email()); + $newItems[] = $newItem; + } + } + return $newItems; + } + + public function formatItem(FormattedItem $formattedItem): FormattedItem + { + return $formattedItem; + } + + public function handles(): string + { + return static::ROLES; + } +} \ No newline at end of file diff --git a/src/Export/Formatter/Role/SimpleRoleFormatter.php b/src/Export/Formatter/Role/SimpleRoleFormatter.php new file mode 100644 index 0000000..da71ec8 --- /dev/null +++ b/src/Export/Formatter/Role/SimpleRoleFormatter.php @@ -0,0 +1,23 @@ +addRow('Role ID', $formattedItem->original()->id()); + $formattedItem->addRow('Role Name', $formattedItem->original()->data()->roleName()); + $formattedItem->addRow('Role Email', $formattedItem->original()->data()->email()); + return $formattedItem; + } + + public function handles(): string + { + return static::ROLES; + } +} \ No newline at end of file diff --git a/src/Export/Formatter/Shared/SortByColumn.php b/src/Export/Formatter/Shared/SortByColumn.php new file mode 100644 index 0000000..911adc1 --- /dev/null +++ b/src/Export/Formatter/Shared/SortByColumn.php @@ -0,0 +1,60 @@ +config('column', 'id'); + + usort($items, function($a, $b) use ($column) { + $aVal = $a->getItem($column); + $bVal = $b->getItem($column); + if(is_null($aVal) && is_null($bVal)) { + return 0; + } + if(is_null($aVal) && !is_null($bVal)) { + return 1; + } + if(!is_null($aVal) && is_null($bVal)) { + return -1; + } + if(is_string($aVal) || is_string($bVal)) { + return $this->compareStrings($aVal, $bVal); + } + if(is_int($aVal) || is_int($bVal)) { + return $this->compareInts($aVal, $bVal); + } + return 0; + }); + return $items; + } + + private function compareStrings(string $a, string $b): int + { + return strcmp($a, $b); + } + + public function compareInts(int $a, int $b): int + { + if ($a == $b) { + return 0; + } + return ($a < $b) ? -1 : 1; + } + + public function formatItem(FormattedItem $formattedItem): FormattedItem + { + return $formattedItem; + } + + public function handles(): string + { + return static::ALL; + } +} \ No newline at end of file diff --git a/src/Export/Formatter/User/SimpleUserFormatter.php b/src/Export/Formatter/User/SimpleUserFormatter.php new file mode 100644 index 0000000..cc34bed --- /dev/null +++ b/src/Export/Formatter/User/SimpleUserFormatter.php @@ -0,0 +1,27 @@ +original()->data(); + $formattedItem->addRow('User ID', $formattedItem->original()->id()); + $formattedItem->addRow('User First Name', $dataUser->firstName()); + $formattedItem->addRow('User Last Name', $dataUser->lastName()); + $formattedItem->addRow('User Preferred Name', $dataUser->preferredName()); + $formattedItem->addRow('User Email', $dataUser->email()); + $formattedItem->addRow('User DoB', $dataUser->dob()); + return $formattedItem; + } + + public function handles(): string + { + return static::USERS; + } +} \ No newline at end of file diff --git a/src/Export/Handler/DumpHandler.php b/src/Export/Handler/DumpHandler.php new file mode 100644 index 0000000..02c6038 --- /dev/null +++ b/src/Export/Handler/DumpHandler.php @@ -0,0 +1,17 @@ +toArray()); + } +} \ No newline at end of file diff --git a/src/Export/Handler/Handler.php b/src/Export/Handler/Handler.php new file mode 100644 index 0000000..4084848 --- /dev/null +++ b/src/Export/Handler/Handler.php @@ -0,0 +1,80 @@ +config = $config; + } + + /** + * Prepare items by transforming them to formattable items + * + * @param $items + * @return FormattedItem[] + */ + protected function prepareItems($items) + { + $formattedItems = []; + foreach($items as $item) { + $formattedItems[] = FormattedItem::create($item); + } + return $formattedItems; + } + + public function export($items = []) + { + if($items instanceof Collection) { + $items = $items->all(); + } + $formattedItems = $this->prepareItems($items); + foreach($this->getFormatters() as $formatter) { + $formattedItems = $formatter->format($formattedItems); + } + $this->save($formattedItems); + } + + /** + * @return Formatter[] + */ + protected function getFormatters() + { + return array_map(function($className) { + if(class_exists($className)) { + return new $className($this->config('formatters')[$className]); + } + throw new \Exception(sprintf('Formatter %s does not exist', $className)); + }, array_keys($this->config('formatters', []))); + } + + /** + * @param FormattedItem[] $items + * @return mixed + */ + abstract protected function save(array $items); + + /** + * @param string $key + * @param null $default + * + * @return mixed|null + */ + protected function config(string $key, $default = null) + { + if(array_key_exists($key, $this->config)) { + return $this->config[$key]; + } + return $default; + } +} \ No newline at end of file diff --git a/src/Export/Handler/SaveCsvHandler.php b/src/Export/Handler/SaveCsvHandler.php new file mode 100644 index 0000000..caf6ebb --- /dev/null +++ b/src/Export/Handler/SaveCsvHandler.php @@ -0,0 +1,21 @@ +generateCsv($items, $this->config('defaultIfNull', 'N/A')); + + Storage::disk($this->config('disk', null)) + ->put($this->config('filename', 'export.csv'), $csv); + } +} \ No newline at end of file diff --git a/src/Export/Handler/UsesCsv.php b/src/Export/Handler/UsesCsv.php new file mode 100644 index 0000000..6f7b6bf --- /dev/null +++ b/src/Export/Handler/UsesCsv.php @@ -0,0 +1,65 @@ +getColumnNames() as $column) { + if(!in_array($column, $headers)) { + $headers[] = $column; + } + } + } + return $headers; + } + + /** + * @param array|FormattedItem[] $items + * @param null $headers + * @param $defaultIfNull + * @return array + */ + public function getRows($items, $headers = null, $defaultIfNull = null) + { + $headers = $headers ?? $this->getHeaders($items); + + $rows = []; + foreach($items as $item) { + $row = []; + foreach($headers as $header) { + $row[] = $item->getItem($header, $defaultIfNull); + } + $rows[] = $row; + } + + return $rows; + } + + public function generateCsv($items, $defaultIfNull = null) + { + $csv = tmpfile(); + + $headers = $this->getHeaders($items); + + fputcsv($csv, $this->getHeaders($items)); + + foreach($this->getRows($items, $headers, $defaultIfNull) as $row) { + fputcsv($csv, $row); + } + + return $csv; + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/ExportControlCommandTest.php b/tests/Unit/Export/ExportControlCommandTest.php new file mode 100644 index 0000000..cd62d48 --- /dev/null +++ b/tests/Unit/Export/ExportControlCommandTest.php @@ -0,0 +1,150 @@ +create(), + factory(User::class)->create(), + factory(User::class)->create(), + factory(User::class)->create() + ]); + + Exporter::extend('test', function($app, $config) { + return $app->make(TestDriver::class, ['config' => $config]); + }); + + config()->set('control.export.default', 'test-setup'); + config()->set('control.export.test-setup', [ + 'driver' => 'test', + 'shouldExport' => $users + ]); + + $this->artisan(ExportControlCommand::class, ['type' => 'user']); + } + + /** @test */ + public function it_exports_all_groups_if_the_data_type_is_a_group(){ + $groups = collect([ + factory(Group::class)->create(), + factory(Group::class)->create(), + factory(Group::class)->create(), + factory(Group::class)->create() + ]); + + Exporter::extend('test', function($app, $config) { + return $app->make(TestDriver::class, ['config' => $config]); + }); + + config()->set('control.export.default', 'test-setup'); + config()->set('control.export.test-setup', [ + 'driver' => 'test', + 'shouldExport' => $groups + ]); + + $this->artisan(ExportControlCommand::class, ['type' => 'group']); + } + + /** @test */ + public function it_exports_all_roles_if_the_data_type_is_a_role(){ + $roles = collect([ + factory(Role::class)->create(), + factory(Role::class)->create(), + factory(Role::class)->create(), + factory(Role::class)->create() + ]); + + Exporter::extend('test', function($app, $config) { + return $app->make(TestDriver::class, ['config' => $config]); + }); + + config()->set('control.export.default', 'test-setup'); + config()->set('control.export.test-setup', [ + 'driver' => 'test', + 'shouldExport' => $roles + ]); + + $this->artisan(ExportControlCommand::class, ['type' => 'role']); + } + + /** @test */ + public function it_exports_all_positions_if_the_data_type_is_a_position(){ + $positions = collect([ + factory(Position::class)->create(), + factory(Position::class)->create(), + factory(Position::class)->create(), + factory(Position::class)->create() + ]); + + Exporter::extend('test', function($app, $config) { + return $app->make(TestDriver::class, ['config' => $config]); + }); + + config()->set('control.export.default', 'test-setup'); + config()->set('control.export.test-setup', [ + 'driver' => 'test', + 'shouldExport' => $positions + ]); + + $this->artisan(ExportControlCommand::class, ['type' => 'position']); + } + + /** @test */ + public function it_uses_the_driver_if_given(){ + $users = collect([ + factory(User::class)->create(), + factory(User::class)->create(), + factory(User::class)->create(), + factory(User::class)->create() + ]); + + Exporter::extend('test', function($app, $config) { + return $app->make(TestDriver::class, ['config' => $config]); + }); + + config()->set('control.export.default', 'default'); + config()->set('control.export.test-setup', [ + 'driver' => 'test', + 'shouldExport' => $users + ]); + + $this->artisan(ExportControlCommand::class, ['type' => 'user', '--exporter' => 'test-setup']); + } + +} + +class TestDriver extends Handler +{ + + public function export($items = []) + { + foreach($items as $key => $item) { + Assert::assertTrue($item->is($this->config('shouldExport')[$key])); + } + } + + /** + * @inheritDoc + */ + protected function save(array $items) + { + // TODO: Implement save() method. + } +} \ No newline at end of file diff --git a/tests/Unit/Export/ExportManagerTest.php b/tests/Unit/Export/ExportManagerTest.php new file mode 100644 index 0000000..a18664f --- /dev/null +++ b/tests/Unit/Export/ExportManagerTest.php @@ -0,0 +1,214 @@ +expectException(Exception::class); + $this->expectExceptionMessage('Exporter [test-setup] is not defined.'); + + $exportManager = new ExportManager($this->app); + $exportManager->driver('test-setup'); + } + + /** @test */ + public function driver_throws_an_exception_if_the_driver_cannot_be_created(){ + $this->expectException(Exception::class); + $this->expectExceptionMessage('Driver [test-driver] is not supported.'); + + $this->app['config']->set('control.export.test-setup', [ + 'driver' => 'test-driver' + ]); + + $exportManager = new ExportManager($this->app); + $exportManager->driver('test-setup'); + } + + /** @test */ + public function a_custom_driver_function_is_invoked_if_manager_is_extended(){ + $handler = $this->prophesize(Handler::class); + + $this->app['config']->set('control.export.test-setup', [ + 'driver' => 'test-driver', + 'setting' => 'Val1' + ]); + + $exportManager = new ExportManager($this->app); + + $exportManager->extend('test-driver', function($app, $config) use ($handler) { + Assert::assertInstanceOf(Container::class, $app); + Assert::assertIsArray($config); + Assert::assertEquals([ + 'driver' => 'test-driver', + 'setting' => 'Val1', + 'formatters' => [] + ], $config); + return $handler->reveal(); + }); + $resolvedDriver = $exportManager->driver('test-setup'); + $this->assertEquals($handler->reveal(), $resolvedDriver); + } + + /** @test */ + public function a_driver_function_is_called_if_the_manager_knows_how_to_create_the_driver(){ + $handler = $this->prophesize(Handler::class); + + $this->app['config']->set('control.export.test-setup', [ + 'driver' => 'test', + 'setting' => 'Val1' + ]); + + $exportManager = new TestExportManager($this->app); + $exportManager->setTestDriverResult($handler->reveal()); + + $resolvedDriver = $exportManager->driver('test-setup'); + $this->assertEquals($handler->reveal(), $resolvedDriver); + } + + /** @test */ + public function additional_formatters_are_added_to_the_config(){ + $handler = $this->prophesize(Handler::class); + + $this->app['config']->set('control.export.test-setup', [ + 'driver' => 'test-driver', + 'setting' => 'Val1', + 'formatters' => [ + 'key1' => ['abc' => 'val1'], + 'key2' => ['key3' => 'val2'], + ] + ]); + + $exportManager = new ExportManager($this->app); + $exportManager->withFormatter('key3', ['akey' => 'aval', 'akey1' => 'aval1']); + + $exportManager->extend('test-driver', function($app, $config) use ($handler) { + Assert::assertInstanceOf(Container::class, $app); + Assert::assertIsArray($config); + Assert::assertEquals([ + 'driver' => 'test-driver', + 'setting' => 'Val1', + 'formatters' => [ + 'key1' => ['abc' => 'val1'], + 'key2' => ['key3' => 'val2'], + 'key3' => ['akey' => 'aval', 'akey1' => 'aval1'], + ] + ], $config); + return $handler->reveal(); + }); + $exportManager->driver('test-setup'); + } + + /** @test */ + public function additional_formatters_are_added_to_the_config_if_no_formatters_exist_so_far(){ + $handler = $this->prophesize(Handler::class); + + $this->app['config']->set('control.export.test-setup', [ + 'driver' => 'test-driver', + 'setting' => 'Val1', + ]); + + $exportManager = new ExportManager($this->app); + $exportManager->withFormatter('key3', ['akey' => 'aval', 'akey1' => 'aval1']); + + $exportManager->extend('test-driver', function($app, $config) use ($handler) { + Assert::assertInstanceOf(Container::class, $app); + Assert::assertIsArray($config); + Assert::assertEquals([ + 'driver' => 'test-driver', + 'setting' => 'Val1', + 'formatters' => [ + 'key3' => ['akey' => 'aval', 'akey1' => 'aval1'], + ] + ], $config); + return $handler->reveal(); + }); + $exportManager->driver('test-setup'); + } + + /** @test */ + public function it_returns_the_default_driver_if_no_driver_given(){ + $handler = $this->prophesize(Handler::class); + + $this->app['config']->set('control.export.test-setup', [ + 'driver' => 'test', + 'setting' => 'Val1' + ]); + + $this->app['config']->set('control.export.default', 'test-setup'); + + $exportManager = new TestExportManager($this->app); + $exportManager->setTestDriverResult($handler->reveal()); + + $resolvedDriver = $exportManager->driver(); + $this->assertEquals($handler->reveal(), $resolvedDriver); + } + + /** @test */ + public function it_calls_the_function_on_the_driver_if_function_called_directly(){ + $items = ['one', 'two']; + $handler = $this->prophesize(Handler::class); + $handler->export($items)->shouldBeCalled(); + + $this->app['config']->set('control.export.test-setup', [ + 'driver' => 'test', + 'setting' => 'Val1' + ]); + + $this->app['config']->set('control.export.default', 'test-setup'); + + $exportManager = new TestExportManager($this->app); + $exportManager->setTestDriverResult($handler->reveal()); + + $exportManager->export($items); + } + + /** @test */ + public function it_can_create_a_dump_driver(){ + $this->app['config']->set('control.export.test-setup', [ + 'driver' => 'dump' + ]); + + $exportManager = new ExportManager($this->app); + $driver = $exportManager->driver('test-setup'); + $this->assertInstanceOf(DumpHandler::class, $driver); + } + + /** @test */ + public function it_can_create_a_save_csv_driver(){ + $this->app['config']->set('control.export.test-setup', [ + 'driver' => 'save-csv' + ]); + + $exportManager = new ExportManager($this->app); + $driver = $exportManager->driver('test-setup'); + $this->assertInstanceOf(SaveCsvHandler::class, $driver); + } + +} + +class TestExportManager extends ExportManager +{ + protected $result; + + public function setTestDriverResult($result) + { + $this->result = $result; + } + + public function createTestDriver() + { + return $this->result; + } +} \ No newline at end of file diff --git a/tests/Unit/Export/ExporterTest.php b/tests/Unit/Export/ExporterTest.php new file mode 100644 index 0000000..05a4f35 --- /dev/null +++ b/tests/Unit/Export/ExporterTest.php @@ -0,0 +1,22 @@ +prophesize(ExportManager::class); + $instance->withFormatter('formatter', $config)->shouldBeCalled(); + $this->instance('control-exporter', $instance->reveal()); + + Exporter::withFormatter('formatter', $config); + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/FormattedItemTest.php b/tests/Unit/Export/FormattedItemTest.php new file mode 100644 index 0000000..47103c2 --- /dev/null +++ b/tests/Unit/Export/FormattedItemTest.php @@ -0,0 +1,127 @@ +create(); + $item = new FormattedItem($role); + + $this->assertInstanceOf(Role::class, $item->original()); + $this->assertTrue($role->is($item->original())); + } + + /** @test */ + public function the_simple_factory_creates_an_instance_of_the_formatted_item(){ + $role = factory(Role::class)->create(); + $item = FormattedItem::create($role); + + $this->assertInstanceOf(Role::class, $item->original()); + $this->assertTrue($role->is($item->original())); + } + + /** @test */ + public function isType_returns_true_if_the_original_type_is_the_same_as_given(){ + $role = factory(Role::class)->create(); + $item = new FormattedItem($role); + $this->assertTrue($item->isType(Role::class)); + } + + /** @test */ + public function isType_returns_false_if_the_original_type_is_different_to_the_given(){ + $role = factory(Role::class)->create(); + $item = new FormattedItem($role); + $this->assertFalse($item->isType(User::class)); + } + + /** @test */ + public function rows_can_be_added_and_retrieved(){ + $role = factory(Role::class)->create(); + $item = new FormattedItem($role); + + $item->addRow('row1', 'Value 1'); + $item->addRow('Row 2', 'Value 2'); + $this->assertEquals([ + 'row1' => 'Value 1', + 'Row 2' => 'Value 2' + ], $item->preparedItems()); + } + + /** @test */ + public function getColumnNames_returns_the_column_names(){ + $role = factory(Role::class)->create(); + $item = new FormattedItem($role); + + $item->addRow('row1', 'Value 1'); + $item->addRow('Row 2', 'Value 2'); + $this->assertEquals(['row1', 'Row 2'], $item->getColumnNames()); + } + + /** @test */ + public function getItem_returns_the_value_of_a_column(){ + $role = factory(Role::class)->create(); + $item = new FormattedItem($role); + + $item->addRow('row1', 'Value 1'); + $item->addRow('Row 2', 'Value 2'); + $this->assertEquals('Value 2', $item->getItem('Row 2')); + } + + /** @test */ + public function getItem_returns_the_default_value_if_the_column_is_not_given(){ + $role = factory(Role::class)->create(); + $item = new FormattedItem($role); + + $item->addRow('row1', 'Value 1'); + $item->addRow('Row 2', 'Value 2'); + $this->assertEquals('N/A', $item->getItem('Row 3', 'N/A')); + } + + /** @test */ + public function toArray_returns_the_prepared_items_as_an_array(){ + $role = factory(Role::class)->create(); + $item = new FormattedItem($role); + + $item->addRow('row1', 'Value 1'); + $item->addRow('Row 2', 'Value 2'); + $this->assertEquals([ + 'row1' => 'Value 1', + 'Row 2' => 'Value 2' + ], $item->toArray()); + } + + /** @test */ + public function toJson_returns_the_prepared_items_as_a_json_string(){ + $role = factory(Role::class)->create(); + $item = new FormattedItem($role); + + $item->addRow('row1', 'Value 1'); + $item->addRow('Row 2', 'Value 2'); + $this->assertEquals(json_encode([ + 'row1' => 'Value 1', + 'Row 2' => 'Value 2' + ]), $item->toJson()); + } + + /** @test */ + public function __toString_returns_the_prepared_items_as_a_json_string(){ + $role = factory(Role::class)->create(); + $item = new FormattedItem($role); + + $item->addRow('row1', 'Value 1'); + $item->addRow('Row 2', 'Value 2'); + $this->assertEquals(json_encode([ + 'row1' => 'Value 1', + 'Row 2' => 'Value 2' + ]), (string) $item); + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/Formatter/FormatterTest.php b/tests/Unit/Export/Formatter/FormatterTest.php new file mode 100644 index 0000000..4d53c78 --- /dev/null +++ b/tests/Unit/Export/Formatter/FormatterTest.php @@ -0,0 +1,164 @@ +create()), + FormattedItem::create(factory(User::class)->create()), + FormattedItem::create(factory(User::class)->create()), + ]; + + $ids = array_map(function($item) { + return $item->original()->id(); + }, $items); + + $called = 0; + + $formatter = new TestFormatter([]); + $formatter->handlesShouldReturn(Formatter::USERS); + $formatter->formatItemCallback(function($formattedItem) use ($ids, &$called) { + Assert::assertContains($formattedItem->original()->id(), $ids); + $called++; + return $formattedItem; + }); + $formatter->format($items); + $this->assertEquals(3, $called); + } + + /** @test */ + public function it_calls_formatItem_if_the_formatter_handles_all(){ + $items = [ + FormattedItem::create(factory(User::class)->create()), + FormattedItem::create(factory(User::class)->create()), + FormattedItem::create(factory(User::class)->create()), + ]; + + $ids = array_map(function($item) { + return $item->original()->id(); + }, $items); + + $called = 0; + + $formatter = new TestFormatter([]); + $formatter->handlesShouldReturn(Formatter::ALL); + $formatter->formatItemCallback(function($formattedItem) use ($ids, &$called) { + Assert::assertContains($formattedItem->original()->id(), $ids); + $called++; + return $formattedItem; + }); + $newItems = $formatter->format($items); + $this->assertEquals(3, $called); + } + + /** @test */ + public function it_does_not_call_formatItem_if_the_formatter_cannot_handle_the_item(){ + $items = [ + FormattedItem::create(factory(User::class)->create()), + FormattedItem::create(factory(User::class)->create()), + FormattedItem::create(factory(User::class)->create()), + ]; + + $formatter = new TestFormatter([]); + $formatter->handlesShouldReturn(Formatter::ROLES); + $called = 0; + $formatter->formatItemCallback(function($formattedItem) use (&$called) { + Assert::assertFalse(true, 'The function should not have been called'); + $called++; + return $formattedItem; + }); + $newItems = $formatter->format($items); + $this->assertEquals(0, $called); + } + + /** @test */ + public function it_returns_the_formatted_items(){ + $items = [ + FormattedItem::create(factory(User::class)->create()), + FormattedItem::create(factory(User::class)->create()), + FormattedItem::create(factory(User::class)->create()), + ]; + + $ids = array_map(function($item) { + return $item->original()->id(); + }, $items); + + $formatter = new TestFormatter([]); + $formatter->handlesShouldReturn(Formatter::USERS); + $formatter->formatItemCallback(function($formattedItem) use ($ids) { + Assert::assertContains($formattedItem->original()->id(), $ids); + $formattedItem->addRow('testrow', 'Val'); + return $formattedItem; + }); + $newItems = $formatter->format($items); + foreach($newItems as $item) { + $this->assertEquals(['testrow'], $item->getColumnNames()); + } + } + + /** @test */ + public function config_returns_a_config_item() + { + $formatter = new TestFormatter(['key1' => 'val1']); + $this->assertEquals('val1', $formatter->config('key1')); + } + + /** @test */ + public function config_returns_default_if_config_not_given() + { + $formatter = new TestFormatter(['key1' => 'val1']); + $this->assertEquals('defaultval', $formatter->config('key2', 'defaultval')); + } + + + +} + +class TestFormatter extends Formatter +{ + + /** + * @var string + */ + private $handles; + + /** + * @var \Closure + */ + private $callback; + + public function handlesShouldReturn(string $handles) + { + $this->handles = $handles; + } + + public function formatItemCallback(\Closure $callback) + { + $this->callback = $callback; + } + + public function formatItem(FormattedItem $formattedItem): FormattedItem + { + return ($this->callback)($formattedItem); + } + + public function handles(): string + { + return $this->handles; + } + + public function config(string $key, $default = null) + { + return parent::config($key, $default); + } +} \ No newline at end of file diff --git a/tests/Unit/Export/Formatter/Group/SimpleGroupFormatterTest.php b/tests/Unit/Export/Formatter/Group/SimpleGroupFormatterTest.php new file mode 100644 index 0000000..41ab37e --- /dev/null +++ b/tests/Unit/Export/Formatter/Group/SimpleGroupFormatterTest.php @@ -0,0 +1,38 @@ +create(['name' => 'Group1', 'email' => 'group1@example.com']); + $dataGroup2 = factory(DataGroup::class)->create(['name' => 'Group2', 'email' => 'group2@example.com']); + $dataGroup3 = factory(DataGroup::class)->create(['name' => 'Group3', 'email' => 'group3@example.com']); + $group1 = factory(Group::class)->create(['data_provider_id' => $dataGroup1->id()]); + $group2 = factory(Group::class)->create(['data_provider_id' => $dataGroup2->id()]); + $group3 = factory(Group::class)->create(['data_provider_id' => $dataGroup3->id()]); + + $formatter = new SimpleGroupFormatter([]); + $items = $formatter->format([FormattedItem::create($group1), FormattedItem::create($group2), FormattedItem::create($group3)]); + + $this->assertCount(3, $items); + $this->assertEquals([ + 'Group ID' => $group1->id(), 'Group Name' => 'Group1', 'Group Email' => 'group1@example.com' + ], $items[0]->preparedItems()); + $this->assertEquals([ + 'Group ID' => $group2->id(), 'Group Name' => 'Group2', 'Group Email' => 'group2@example.com' + ], $items[1]->preparedItems()); + $this->assertEquals([ + 'Group ID' => $group3->id(), 'Group Name' => 'Group3', 'Group Email' => 'group3@example.com' + ], $items[2]->preparedItems()); + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/Formatter/Role/AddGroupInformationToRolesTest.php b/tests/Unit/Export/Formatter/Role/AddGroupInformationToRolesTest.php new file mode 100644 index 0000000..a096fe3 --- /dev/null +++ b/tests/Unit/Export/Formatter/Role/AddGroupInformationToRolesTest.php @@ -0,0 +1,43 @@ +create(['name' => 'Group1', 'email' => 'group1@example.com']); + $dataGroup2 = factory(DataGroup::class)->create(['name' => 'Group2', 'email' => 'group2@example.com']); + $dataGroup3 = factory(DataGroup::class)->create(['name' => 'Group3', 'email' => 'group3@example.com']); + $group1 = factory(Group::class)->create(['data_provider_id' => $dataGroup1->id()]); + $group2 = factory(Group::class)->create(['data_provider_id' => $dataGroup2->id()]); + $group3 = factory(Group::class)->create(['data_provider_id' => $dataGroup3->id()]); + + $formatter = new AddGroupInformationToRoles([]); + $items = $formatter->format([ + FormattedItem::create(factory(Role::class)->create(['group_id' => $group1])), + FormattedItem::create(factory(Role::class)->create(['group_id' => $group2])), + FormattedItem::create(factory(Role::class)->create(['group_id' => $group3])), + ]); + + $this->assertCount(3, $items); + $this->assertEquals([ + 'Group ID' => $group1->id(), 'Group Name' => 'Group1', 'Group Email' => 'group1@example.com' + ], $items[0]->preparedItems()); + $this->assertEquals([ + 'Group ID' => $group2->id(), 'Group Name' => 'Group2', 'Group Email' => 'group2@example.com' + ], $items[1]->preparedItems()); + $this->assertEquals([ + 'Group ID' => $group3->id(), 'Group Name' => 'Group3', 'Group Email' => 'group3@example.com' + ], $items[2]->preparedItems()); + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/Formatter/Role/AddPostionInformationToRolesTest.php b/tests/Unit/Export/Formatter/Role/AddPostionInformationToRolesTest.php new file mode 100644 index 0000000..6531204 --- /dev/null +++ b/tests/Unit/Export/Formatter/Role/AddPostionInformationToRolesTest.php @@ -0,0 +1,43 @@ +create(['name' => 'Position1']); + $dataPosition2 = factory(DataPosition::class)->create(['name' => 'Position2']); + $dataPosition3 = factory(DataPosition::class)->create(['name' => 'Position3']); + $position1 = factory(Position::class)->create(['data_provider_id' => $dataPosition1->id()]); + $position2 = factory(Position::class)->create(['data_provider_id' => $dataPosition2->id()]); + $position3 = factory(Position::class)->create(['data_provider_id' => $dataPosition3->id()]); + + $formatter = new AddPositionInformationToRoles([]); + $items = $formatter->format([ + FormattedItem::create(factory(Role::class)->create(['position_id' => $position1])), + FormattedItem::create(factory(Role::class)->create(['position_id' => $position2])), + FormattedItem::create(factory(Role::class)->create(['position_id' => $position3])), + ]); + + $this->assertCount(3, $items); + $this->assertEquals([ + 'Position ID' => $position1->id(), 'Position Name' => 'Position1' + ], $items[0]->preparedItems()); + $this->assertEquals([ + 'Position ID' => $position2->id(), 'Position Name' => 'Position2' + ], $items[1]->preparedItems()); + $this->assertEquals([ + 'Position ID' => $position3->id(), 'Position Name' => 'Position3' + ], $items[2]->preparedItems()); + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/Formatter/Role/AddRoleHoldersAsNewItemsTest.php b/tests/Unit/Export/Formatter/Role/AddRoleHoldersAsNewItemsTest.php new file mode 100644 index 0000000..65dbf16 --- /dev/null +++ b/tests/Unit/Export/Formatter/Role/AddRoleHoldersAsNewItemsTest.php @@ -0,0 +1,53 @@ +create(['first_name' => 'User1', 'last_name' => 'UserLast1', 'preferred_name' => 'UserPreferred1', 'email' => 'user1@example.com']); + $dataUser2 = factory(DataUser::class)->create(['first_name' => 'User2', 'last_name' => 'UserLast2', 'preferred_name' => 'UserPreferred2', 'email' => 'user2@example.com']); + $dataUser3 = factory(DataUser::class)->create(['first_name' => 'User3', 'last_name' => 'UserLast3', 'preferred_name' => 'UserPreferred3', 'email' => 'user3@example.com']); + $user1 = factory(User::class)->create(['data_provider_id' => $dataUser1->id()]); + $user2 = factory(User::class)->create(['data_provider_id' => $dataUser2->id()]); + $user3 = factory(User::class)->create(['data_provider_id' => $dataUser3->id()]); + $role1 = factory(Role::class)->create(); + $role2 = factory(Role::class)->create(); + app(UserRole::class)->addUserToRole($user1, $role1); + app(UserRole::class)->addUserToRole($user2, $role2); + app(UserRole::class)->addUserToRole($user3, $role2); + app(UserRole::class)->addUserToRole($user3, $role1); + + $formatter = new AddRoleHoldersAsNewItems([]); + $items = $formatter->format([ + FormattedItem::create($role1), + FormattedItem::create($role2) + ]); + + $this->assertCount(4, $items); + $this->assertEquals([ + 'User ID' => $user1->id(), 'User First Name' => 'User1', 'User Last Name' => 'UserLast1', 'User Preferred Name' => 'UserPreferred1', 'User Email' => 'user1@example.com' + ], $items[0]->preparedItems()); + $this->assertEquals([ + 'User ID' => $user3->id(), 'User First Name' => 'User3', 'User Last Name' => 'UserLast3', 'User Preferred Name' => 'UserPreferred3', 'User Email' => 'user3@example.com' + ], $items[1]->preparedItems()); + $this->assertEquals([ + 'User ID' => $user2->id(), 'User First Name' => 'User2', 'User Last Name' => 'UserLast2', 'User Preferred Name' => 'UserPreferred2', 'User Email' => 'user2@example.com' + ], $items[2]->preparedItems()); + $this->assertEquals([ + 'User ID' => $user3->id(), 'User First Name' => 'User3', 'User Last Name' => 'UserLast3', 'User Preferred Name' => 'UserPreferred3', 'User Email' => 'user3@example.com' + ], $items[3]->preparedItems()); + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/Formatter/Role/SimpleRoleFormatterTest.php b/tests/Unit/Export/Formatter/Role/SimpleRoleFormatterTest.php new file mode 100644 index 0000000..e9de14d --- /dev/null +++ b/tests/Unit/Export/Formatter/Role/SimpleRoleFormatterTest.php @@ -0,0 +1,38 @@ +create(['role_name' => 'Role1', 'email' => 'role1@example.com']); + $dataRole2 = factory(DataRole::class)->create(['role_name' => 'Role2', 'email' => 'role2@example.com']); + $dataRole3 = factory(DataRole::class)->create(['role_name' => 'Role3', 'email' => 'role3@example.com']); + $role1 = factory(Role::class)->create(['data_provider_id' => $dataRole1->id()]); + $role2 = factory(Role::class)->create(['data_provider_id' => $dataRole2->id()]); + $role3 = factory(Role::class)->create(['data_provider_id' => $dataRole3->id()]); + + $formatter = new SimpleRoleFormatter([]); + $items = $formatter->format([FormattedItem::create($role1), FormattedItem::create($role2), FormattedItem::create($role3)]); + + $this->assertCount(3, $items); + $this->assertEquals([ + 'Role ID' => $role1->id(), 'Role Name' => 'Role1', 'Role Email' => 'role1@example.com' + ], $items[0]->preparedItems()); + $this->assertEquals([ + 'Role ID' => $role2->id(), 'Role Name' => 'Role2', 'Role Email' => 'role2@example.com' + ], $items[1]->preparedItems()); + $this->assertEquals([ + 'Role ID' => $role3->id(), 'Role Name' => 'Role3', 'Role Email' => 'role3@example.com' + ], $items[2]->preparedItems()); + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/Formatter/Shared/SortByColumnTest.php b/tests/Unit/Export/Formatter/Shared/SortByColumnTest.php new file mode 100644 index 0000000..38f6c79 --- /dev/null +++ b/tests/Unit/Export/Formatter/Shared/SortByColumnTest.php @@ -0,0 +1,69 @@ +create()); + $formatted2 = FormattedItem::create(factory(Role::class)->create()); + $formatted3 = FormattedItem::create(factory(Role::class)->create()); + $formatted1->addRow('col', 'Alpha'); + $formatted2->addRow('col', 'Charlie'); + $formatted3->addRow('col', 'Beta'); + + $formatter = new SortByColumn(['column' => 'col']); + $items = $formatter->format([$formatted1, $formatted2, $formatted3]); + + $this->assertCount(3, $items); + $this->assertEquals($formatted1, $items[0]); + $this->assertEquals($formatted3, $items[1]); + $this->assertEquals($formatted2, $items[2]); + } + + /** @test */ + public function it_returns_the_items_sorted_by_number_if_the_column_is_an_integer(){ + $formatted1 = FormattedItem::create(factory(Role::class)->create()); + $formatted2 = FormattedItem::create(factory(Role::class)->create()); + $formatted3 = FormattedItem::create(factory(Role::class)->create()); + $formatted1->addRow('col', 5000); + $formatted2->addRow('col', 300); + $formatted3->addRow('col', 500); + + $formatter = new SortByColumn(['column' => 'col']); + $items = $formatter->format([$formatted1, $formatted2, $formatted3]); + + $this->assertCount(3, $items); + $this->assertEquals($formatted2, $items[0]); + $this->assertEquals($formatted3, $items[1]); + $this->assertEquals($formatted1, $items[2]); + } + + /** @test */ + public function null_values_appear_last(){ + $formatted1 = FormattedItem::create(factory(Role::class)->create()); + $formatted2 = FormattedItem::create(factory(Role::class)->create()); + $formatted3 = FormattedItem::create(factory(Role::class)->create()); + $formatted4 = FormattedItem::create(factory(Role::class)->create()); + $formatted1->addRow('col', 5000); + $formatted2->addRow('col', 300); + $formatted3->addRow('col', 500); + + $formatter = new SortByColumn(['column' => 'col']); + $items = $formatter->format([$formatted1, $formatted2, $formatted3, $formatted4]); + + $this->assertCount(4, $items); + $this->assertEquals($formatted2, $items[0]); + $this->assertEquals($formatted3, $items[1]); + $this->assertEquals($formatted1, $items[2]); + $this->assertEquals($formatted4, $items[3]); + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/Formatter/User/SimpleUserFormatterTest.php b/tests/Unit/Export/Formatter/User/SimpleUserFormatterTest.php new file mode 100644 index 0000000..4acd504 --- /dev/null +++ b/tests/Unit/Export/Formatter/User/SimpleUserFormatterTest.php @@ -0,0 +1,40 @@ +create(['first_name' => 'User1', 'last_name' => 'UserLast1', 'preferred_name' => 'UserPreferred1', 'email' => 'user1@example.com', 'dob' => $birthday->format('Y-m-d')]); + $dataUser2 = factory(DataUser::class)->create(['first_name' => 'User2', 'last_name' => 'UserLast2', 'preferred_name' => 'UserPreferred2', 'email' => 'user2@example.com', 'dob' => $birthday->format('Y-m-d')]); + $dataUser3 = factory(DataUser::class)->create(['first_name' => 'User3', 'last_name' => 'UserLast3', 'preferred_name' => 'UserPreferred3', 'email' => 'user3@example.com', 'dob' => $birthday->format('Y-m-d')]); + $user1 = factory(User::class)->create(['data_provider_id' => $dataUser1->id()]); + $user2 = factory(User::class)->create(['data_provider_id' => $dataUser2->id()]); + $user3 = factory(User::class)->create(['data_provider_id' => $dataUser3->id()]); + + $formatter = new SimpleUserFormatter([]); + $items = $formatter->format([FormattedItem::create($user1), FormattedItem::create($user2), FormattedItem::create($user3)]); + + $this->assertCount(3, $items); + $this->assertEquals([ + 'User ID' => $user1->id(), 'User First Name' => 'User1', 'User Last Name' => 'UserLast1', 'User Preferred Name' => 'UserPreferred1', 'User Email' => 'user1@example.com', 'User DoB' => $birthday + ], $items[0]->preparedItems()); + $this->assertEquals([ + 'User ID' => $user2->id(), 'User First Name' => 'User2', 'User Last Name' => 'UserLast2', 'User Preferred Name' => 'UserPreferred2', 'User Email' => 'user2@example.com', 'User DoB' => $birthday + ], $items[1]->preparedItems()); + $this->assertEquals([ + 'User ID' => $user3->id(), 'User First Name' => 'User3', 'User Last Name' => 'UserLast3', 'User Preferred Name' => 'UserPreferred3', 'User Email' => 'user3@example.com', 'User DoB' => $birthday + ], $items[2]->preparedItems()); + } + +} \ No newline at end of file diff --git a/tests/Unit/Export/Handler/HandlerTest.php b/tests/Unit/Export/Handler/HandlerTest.php new file mode 100644 index 0000000..f7160c7 --- /dev/null +++ b/tests/Unit/Export/Handler/HandlerTest.php @@ -0,0 +1,125 @@ +prophesize(User::class); + $user2 = $this->prophesize(User::class); + $user1->id()->shouldBeCalled(); + $user2->id()->shouldBeCalled(); + $items = [ + $user1->reveal(), + $user2->reveal() + ]; + + $handler = new TestHandler([ + 'formatters' => [ + TestFormatter::class => ['key' => 'val'] + ] + ]); + + $handler->export($items); + + $result = $handler->result; + $this->assertContainsOnlyInstancesOf(FormattedItem::class, $result); + $this->assertEquals($user1->reveal(), $result[0]->original()); + $this->assertEquals($user2->reveal(), $result[1]->original()); + } + + /** @test */ + public function it_throws_an_exception_if_the_formatter_does_not_exist(){ + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Formatter TestFormatter\DoesNotExist does not exist'); + + $user1 = $this->prophesize(User::class); + $user2 = $this->prophesize(User::class); + $items = [ + $user1->reveal(), + $user2->reveal() + ]; + + $handler = new TestHandler([ + 'formatters' => [ + 'TestFormatter\DoesNotExist' => ['key' => 'val'] + ] + ]); + + $handler->export($items); + + $result = $handler->result; + $this->assertContainsOnlyInstancesOf(FormattedItem::class, $result); + $this->assertEquals($user1->reveal(), $result[0]->original()); + $this->assertEquals($user2->reveal(), $result[1]->original()); + } + + /** @test */ + public function it_can_accept_a_collection(){ + $user1 = $this->prophesize(User::class); + $user2 = $this->prophesize(User::class); + $user1->id()->shouldBeCalled(); + $user2->id()->shouldBeCalled(); + $items = collect([ + $user1->reveal(), + $user2->reveal() + ]); + + $handler = new TestHandler([ + 'formatters' => [ + TestFormatter::class => ['key' => 'val'] + ] + ]); + + $handler->export($items); + + $result = $handler->result; + $this->assertContainsOnlyInstancesOf(FormattedItem::class, $result); + $this->assertEquals($user1->reveal(), $result[0]->original()); + $this->assertEquals($user2->reveal(), $result[1]->original()); + } + +} + +class TestFormatter extends Formatter +{ + + public function __construct(array $config) + { + Assert::assertEquals([ + 'key' => 'val' + ],$config); + parent::__construct($config); + } + + public function formatItem(FormattedItem $formattedItem): FormattedItem + { + Assert::assertInstanceOf(User::class, $formattedItem->original()); + $formattedItem->original()->id(); + return $formattedItem; + } + + public function handles(): string + { + return static::ALL; + } +} + +class TestHandler extends Handler +{ + public $result; + + protected function save(array $items) + { + $this->result = $items; + } +} \ No newline at end of file diff --git a/tests/Unit/Export/Handler/SaveCsvHandlerTest.php b/tests/Unit/Export/Handler/SaveCsvHandlerTest.php new file mode 100644 index 0000000..3f6ef9b --- /dev/null +++ b/tests/Unit/Export/Handler/SaveCsvHandlerTest.php @@ -0,0 +1,105 @@ +addRow('Row1', 'Value 1'); + $formattedItem2 = FormattedItem::create(''); + $formattedItem2->addRow('Row2', 'Value 2'); + + $items = [ + $formattedItem1, $formattedItem2 + ]; + + $handler = new SaveCsvHandler([ + 'formatters' => [TestFormatterForSaveCsv::class => ['items' => $items]], + 'filename' => 'export.csv' + ]); + + $handler->export($items); + + Storage::assertExists('export.csv'); + } + + /** @test */ + public function the_filename_can_be_changed(){ + Storage::fake(); + + $formattedItem1 = FormattedItem::create(''); + $formattedItem1->addRow('Row1', 'Value 1'); + $formattedItem2 = FormattedItem::create(''); + $formattedItem2->addRow('Row2', 'Value 2'); + + $items = [ + $formattedItem1, $formattedItem2 + ]; + + $handler = new SaveCsvHandler([ + 'formatters' => [TestFormatterForSaveCsv::class => ['items' => $items]], + 'filename' => 'export2.csv' + ]); + + $handler->export($items); + + Storage::assertExists('export2.csv'); + } + + /** @test */ + public function the_disk_can_be_changed(){ + Storage::fake('diskToFake'); + + $formattedItem1 = FormattedItem::create(''); + $formattedItem1->addRow('Row1', 'Value 1'); + $formattedItem2 = FormattedItem::create(''); + $formattedItem2->addRow('Row2', 'Value 2'); + + $items = [ + $formattedItem1, $formattedItem2 + ]; + + $handler = new SaveCsvHandler([ + 'formatters' => [TestFormatterForSaveCsv::class => ['items' => $items]], + 'filename' => 'export2.csv', + 'disk' => 'diskToFake' + ]); + + $handler->export($items); + + Storage::disk('diskToFake')->assertExists('export2.csv'); + } + + + +} + +class TestFormatterForSaveCsv extends Formatter +{ + + public function format($items) + { + return $this->config('items', []); + } + + public function formatItem(FormattedItem $formattedItem): FormattedItem + { + // TODO: Implement formatItem() method. + } + + public function handles(): string + { + // TODO: Implement handles() method. + } +} \ No newline at end of file diff --git a/tests/Unit/Export/Handler/UsesCsvTest.php b/tests/Unit/Export/Handler/UsesCsvTest.php new file mode 100644 index 0000000..0c46049 --- /dev/null +++ b/tests/Unit/Export/Handler/UsesCsvTest.php @@ -0,0 +1,139 @@ +addRow('Col1', 'Val1'); + $formattedItem1->addRow('Col2', 'Val2'); + $formattedItem2 = FormattedItem::create(''); + $formattedItem2->addRow('Col2', 'Val3'); + $formattedItem2->addRow('Col3', 'Val4'); + $formattedItem3 = FormattedItem::create(''); + $formattedItem3->addRow('Col1', 'Val5'); + $formattedItem3->addRow('Col4', 'Val6'); + + $items = [ + $formattedItem1, + $formattedItem2, + $formattedItem3, + ]; + + $this->assertEquals([ + 'Col1', 'Col2', 'Col3', 'Col4' + ], $this->getHeaders($items)); + } + + /** @test */ + public function getRows_returns_rows_for_the_headers(){ + $formattedItem1 = FormattedItem::create(''); + $formattedItem1->addRow('Col1', 'Val1'); + $formattedItem1->addRow('Col2', 'Val2'); + $formattedItem2 = FormattedItem::create(''); + $formattedItem2->addRow('Col2', 'Val3'); + $formattedItem2->addRow('Col3', 'Val4'); + $formattedItem3 = FormattedItem::create(''); + $formattedItem3->addRow('Col1', 'Val5'); + $formattedItem3->addRow('Col4', 'Val6'); + + $items = [ + $formattedItem1, + $formattedItem2, + $formattedItem3, + ]; + + $this->assertEquals([ + ['Val1', 'Val2', null], + [null, 'Val3', 'Val4'], + ['Val5', null, null], + ], $this->getRows($items, ['Col1', 'Col2', 'Col3'])); + } + + /** @test */ + public function headers_are_calculated_if_not_given(){ + $formattedItem1 = FormattedItem::create(''); + $formattedItem1->addRow('Col1', 'Val1'); + $formattedItem1->addRow('Col2', 'Val2'); + $formattedItem2 = FormattedItem::create(''); + $formattedItem2->addRow('Col2', 'Val3'); + $formattedItem2->addRow('Col3', 'Val4'); + $formattedItem3 = FormattedItem::create(''); + $formattedItem3->addRow('Col1', 'Val5'); + $formattedItem3->addRow('Col4', 'Val6'); + + $items = [ + $formattedItem1, + $formattedItem2, + $formattedItem3, + ]; + + $this->assertEquals([ + ['Val1', 'Val2', null, null], + [null, 'Val3', 'Val4', null], + ['Val5', null, null, 'Val6'], + ], $this->getRows($items)); + } + + /** @test */ + public function a_default_can_be_set(){ + $formattedItem1 = FormattedItem::create(''); + $formattedItem1->addRow('Col1', 'Val1'); + $formattedItem1->addRow('Col2', 'Val2'); + $formattedItem2 = FormattedItem::create(''); + $formattedItem2->addRow('Col2', 'Val3'); + $formattedItem2->addRow('Col3', 'Val4'); + $formattedItem3 = FormattedItem::create(''); + $formattedItem3->addRow('Col1', 'Val5'); + $formattedItem3->addRow('Col4', 'Val6'); + + $items = [ + $formattedItem1, + $formattedItem2, + $formattedItem3, + ]; + + $this->assertEquals([ + ['Val1', 'Val2', 'N/A', 'N/A'], + ['N/A', 'Val3', 'Val4', 'N/A'], + ['Val5', 'N/A', 'N/A', 'Val6'], + ], $this->getRows($items, null, 'N/A')); + } + + /** @test */ + public function generateCsv_creates_a_csv_with_the_correct_content(){ + $formattedItem1 = FormattedItem::create(''); + $formattedItem1->addRow('Col1', 'Val1'); + $formattedItem1->addRow('Col2', 'Val2'); + $formattedItem2 = FormattedItem::create(''); + $formattedItem2->addRow('Col2', 'Val3'); + $formattedItem2->addRow('Col3', 'Val 4'); + $formattedItem3 = FormattedItem::create(''); + $formattedItem3->addRow('Col1', 'Val5'); + $formattedItem3->addRow('Col4', 'Val6'); + + $items = [ + $formattedItem1, + $formattedItem2, + $formattedItem3, + ]; + + $csv = $this->generateCsv($items, 'N/A'); + rewind($csv); + $contents = stream_get_contents($csv); + $this->assertEquals('Col1,Col2,Col3,Col4 +Val1,Val2,N/A,N/A +N/A,Val3,"Val 4",N/A +Val5,N/A,N/A,Val6 +', $contents); + } + +} \ No newline at end of file