diff --git a/README.md b/README.md
index e564654..631c255 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,8 @@ Easier handling of Import in Sonata Admin.
Built on top of [Importer](https://github.com/kunicmarko20/importer).
[![PHP Version](https://img.shields.io/badge/php-%5E7.1-blue.svg)](https://img.shields.io/badge/php-%5E7.1-blue.svg)
-[![Latest Stable Version](https://poser.pugx.org/kunicmarko/SonataImporterBundle/v/stable)](https://packagist.org/packages/kunicmarko/SonataImporterBundle)
-[![Latest Unstable Version](https://poser.pugx.org/kunicmarko/SonataImporterBundle/v/unstable)](https://packagist.org/packages/kunicmarko/SonataImporterBundle)
+[![Latest Stable Version](https://poser.pugx.org/kunicmarko/sonata-importer-bundle/v/stable)](https://packagist.org/packages/kunicmarko/sonata-importer-bundle)
+[![Latest Unstable Version](https://poser.pugx.org/kunicmarko/sonata-importer-bundle/v/unstable)](https://packagist.org/packages/kunicmarko/sonata-importer-bundle)
[![Build Status](https://travis-ci.org/kunicmarko20/SonataImporterBundle.svg?branch=master)](https://travis-ci.org/kunicmarko20/SonataImporterBundle)
[![Coverage Status](https://coveralls.io/repos/github/kunicmarko20/SonataImporterBundle/badge.svg?branch=master)](https://coveralls.io/github/kunicmarko20/SonataImporterBundle?branch=master)
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index dac926f..0ac0a9e 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -21,6 +21,7 @@
./src/
./vendor/
+ ./src/Exception/
diff --git a/src/Admin/ImportAdminExtension.php b/src/Admin/AdminImportExtension.php
similarity index 94%
rename from src/Admin/ImportAdminExtension.php
rename to src/Admin/AdminImportExtension.php
index d10a199..ebd520e 100644
--- a/src/Admin/ImportAdminExtension.php
+++ b/src/Admin/AdminImportExtension.php
@@ -11,7 +11,7 @@
/**
* @author Marko Kunic
*/
-final class ImportAdminExtension extends AbstractAdminExtension
+final class AdminImportExtension extends AbstractAdminExtension
{
/**
* @var array
diff --git a/src/Resources/config/extensions.xml b/src/Resources/config/extensions.xml
index 1bd204f..7b4c6d2 100644
--- a/src/Resources/config/extensions.xml
+++ b/src/Resources/config/extensions.xml
@@ -4,7 +4,7 @@
%sonata.importer.templates%
-
+
diff --git a/tests/Admin/AdminImportExtensionTest.php b/tests/Admin/AdminImportExtensionTest.php
new file mode 100644
index 0000000..61302dd
--- /dev/null
+++ b/tests/Admin/AdminImportExtensionTest.php
@@ -0,0 +1,72 @@
+
+ */
+class AdminImportExtensionTest extends TestCase
+{
+ /**
+ * @var AdminImportExtension
+ */
+ private $extension;
+
+ public function setUp()
+ {
+ $this->extension = new AdminImportExtension([
+ 'action_button' => 'action_button_template',
+ 'dashboard_action' => 'dashboard_action_template',
+ ]);
+ }
+
+ public function testConfigureRoutes()
+ {
+ $routeCollection = $this->prophesize(RouteCollection::class);
+ $routeCollection->add(Argument::type('string'))->shouldBeCalled();
+
+ $this->extension->configureRoutes(new class('', '', '') extends AbstractAdmin implements AdminWithImport {
+
+ }, $routeCollection->reveal());
+ }
+
+ public function testConfigureRoutesNoCall()
+ {
+ $routeCollection = $this->prophesize(RouteCollection::class);
+ $routeCollection->add()->shouldNotBeCalled();
+
+ $this->extension->configureRoutes($this->getAdmin(), $routeCollection->reveal());
+ }
+
+ private function getAdmin(): AdminInterface
+ {
+ return new class('', '', '') extends AbstractAdmin {
+ };
+ }
+
+ public function testConfigureActionButtons(): void
+ {
+ $result = $this->extension->configureActionButtons($this->getAdmin(), [], null, null);
+
+ $this->assertArrayHasKey('import', $result);
+ $this->assertArrayHasKey('template', $result['import']);
+ $this->assertSame('action_button_template', $result['import']['template']);
+ }
+
+ public function testConfigureDashboardActions(): void
+ {
+ $result = $this->extension->configureDashboardActions($this->getAdmin(), []);
+
+ $this->assertArrayHasKey('import', $result);
+ $this->assertArrayHasKey('template', $result['import']);
+ $this->assertSame('dashboard_action_template', $result['import']['template']);
+ }
+}
diff --git a/tests/Controller/ImportCRUDControllerTest.php b/tests/Controller/ImportCRUDControllerTest.php
new file mode 100644
index 0000000..dc1ada3
--- /dev/null
+++ b/tests/Controller/ImportCRUDControllerTest.php
@@ -0,0 +1,136 @@
+
+ */
+class ImportCRUDControllerTest extends TestCase
+{
+ /**
+ * @var CRUDController
+ */
+ private $controller;
+
+ /**
+ * @var FormInterface
+ */
+ private $form;
+
+ /**
+ * @var ContainerInterface
+ */
+ private $container;
+
+ /**
+ * @var Admin
+ */
+ private $admin;
+
+ /**
+ * @var UploadedFile|MockObject
+ */
+ private $file;
+
+ public function setUp()
+ {
+ $this->form = $this->prophesize(FormInterface::class);
+ $this->form->handleRequest(Argument::cetera())->shouldBeCalled();
+
+ $this->container = $this->prophesize(ContainerInterface::class);
+ $this->container->getParameter(Argument::cetera())->willReturn(['form' => 'form.template']);
+
+ $this->admin = new Admin();
+
+ $this->file = $this->getMockBuilder(UploadedFile::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->controller = new CRUDController(
+ $this->form->reveal(),
+ $this->container->reveal(),
+ $this->admin,
+ $this->file
+ );
+
+ $importerFactory = new ImporterFactory();
+ $importerFactory->addReader(new CsvReader());
+ $this->controller->setImporterFactory($importerFactory);
+ }
+
+ public function testNotSubmitted()
+ {
+ $this->form->createView(Argument::cetera())->shouldBeCalled();
+ $this->form->isSubmitted()->willReturn(false);
+
+ $this->controller->importAction(new Request());
+ }
+
+ public function testNotValid()
+ {
+ $this->form->createView(Argument::cetera())->shouldBeCalled();
+ $this->form->isSubmitted()->willReturn(true);
+ $this->form->isValid()->willReturn(false);
+
+ $this->controller->importAction(new Request());
+ }
+
+ public function testNoImportConfiguration()
+ {
+ $this->form->isSubmitted()->willReturn(true);
+ $this->form->isValid()->willReturn(true);
+
+ $this->file->expects($this->once())
+ ->method('getClientOriginalExtension')
+ ->willReturn($type = 'csv');
+
+ $this->controller->importAction(new Request());
+ }
+
+ public function testMissingImportConfigurationForType()
+ {
+ $this->form->isSubmitted()->willReturn(true);
+ $this->form->isValid()->willReturn(true);
+
+ $this->file->expects($this->once())
+ ->method('getClientOriginalExtension')
+ ->willReturn($type = 'csv');
+
+
+ $this->controller->setImportConfigurations([Admin::class => []]);
+ $this->controller->importAction(new Request());
+ }
+
+ public function testValid()
+ {
+ $this->form->isSubmitted()->willReturn(true);
+ $this->form->isValid()->willReturn(true);
+
+ $this->file->expects($this->once())
+ ->method('getClientOriginalExtension')
+ ->willReturn($type = 'csv');
+
+ $this->file->expects($this->once())
+ ->method('getPathname')
+ ->willReturn(__DIR__ . '/../Fixtures/fake.csv');
+
+ $this->controller->flashType = 'success';
+ $this->admin->route = 'list';
+
+ $this->controller->setImportConfigurations([Admin::class => [$type => new ImportConfiguration()]]);
+ $this->controller->importAction(new Request());
+ }
+}
diff --git a/tests/DependencyInjection/Compiler/AutoConfigureCompilerPassTest.php b/tests/DependencyInjection/Compiler/AutoConfigureCompilerPassTest.php
new file mode 100644
index 0000000..6295001
--- /dev/null
+++ b/tests/DependencyInjection/Compiler/AutoConfigureCompilerPassTest.php
@@ -0,0 +1,140 @@
+
+ */
+class AutoConfigureCompilerPassTest extends TestCase
+{
+ /**
+ * @var AutoConfigureCompilerPass
+ */
+ private $compilerPass;
+
+ /**
+ * @var ContainerBuilder
+ */
+ private $containerBuilder;
+
+ public function setUp(): void
+ {
+ $this->compilerPass = new AutoConfigureCompilerPass();
+ $this->containerBuilder = new ContainerBuilder();
+ }
+
+ public function testProcess(): void
+ {
+ $this->containerBuilder->setDefinition(
+ 'admin',
+ ($adminDefinition = new Definition(
+ Admin::class,
+ [
+ 0,
+ 1,
+ 'SonataAdminBundle:CRUD'
+ ]
+ )
+ )->setTags([
+ 'sonata.importer.admin' => [],
+ 'sonata.admin' => [],
+ ])
+ );
+
+ $this->containerBuilder->setDefinition(
+ 'controller',
+ ($controllerDefinition = new Definition(
+ Controller::class
+ )
+ )->setTags([
+ 'sonata.importer.controller' => [],
+ ])
+ );
+
+ $this->containerBuilder->setDefinition(
+ 'importConfiguration',
+ (new Definition(
+ ImportConfiguration::class
+ )
+ )->setTags([
+ 'sonata.importer.configuration' => [],
+ ])
+ );
+
+ $this->compilerPass->process($this->containerBuilder);
+
+ $this->assertSame(ImportCRUDController::class, $adminDefinition->getArgument(2));
+
+ $methodCall = $controllerDefinition->getMethodCalls()[0];
+ $methodName = $methodCall[0];
+ $methodArguments = $methodCall[1][0];
+
+ $this->assertSame('setImportConfigurations', $methodName);
+ $this->assertCount(1, $methodArguments);
+ $this->assertSame(Admin::class, key($methodArguments));
+ $this->assertSame('csv', key($methodArguments[Admin::class]));
+ $this->assertSame(ImportConfiguration::class, $methodArguments[Admin::class]['csv']->getClass());
+ }
+
+ /**
+ * @expectedException \KunicMarko\SonataImporterBundle\Exception\ImportConfigurationMissingInterface
+ */
+ public function testProcessImportConfigurationMissing(): void
+ {
+ $this->containerBuilder->setDefinition(
+ 'importConfigurationMissing',
+ (new Definition(
+ self::class
+ )
+ )->setTags([
+ 'sonata.importer.configuration' => [],
+ ])
+ );
+
+ $this->compilerPass->process($this->containerBuilder);
+ }
+
+ /**
+ * @expectedException \KunicMarko\SonataImporterBundle\Exception\ControllerMissingMethod
+ */
+ public function testProcessControllerMissingMethod(): void
+ {
+ $this->containerBuilder->setDefinition(
+ 'import admin that isn\'t sonata admin',
+ (new Definition(
+ Admin::class,
+ [
+ 0,
+ 1,
+ 'SonataAdminBundle:CRUD'
+ ]
+ )
+ )->setTags([
+ 'sonata.importer.admin' => [],
+ ])
+ );
+
+ $this->containerBuilder->setDefinition(
+ 'controllerMissingMethod',
+ (new Definition(
+ self::class
+ )
+ )->setTags([
+ 'sonata.importer.controller' => [],
+ ])
+ );
+
+ $this->compilerPass->process($this->containerBuilder);
+ }
+}
diff --git a/tests/DependencyInjection/SonataImporterExtensionTest.php b/tests/DependencyInjection/SonataImporterExtensionTest.php
new file mode 100644
index 0000000..e0097b5
--- /dev/null
+++ b/tests/DependencyInjection/SonataImporterExtensionTest.php
@@ -0,0 +1,31 @@
+
+ */
+final class SonataImporterExtensionTest extends AbstractExtensionTestCase
+{
+ public function testLoadsFormServiceDefinition(): void
+ {
+ $this->container->setParameter('kernel.project_dir', $param = 'test');
+
+ $this->load();
+
+ $this->assertContainerBuilderHasService(
+ ImportCRUDController::class
+ );
+ }
+
+ protected function getContainerExtensions(): array
+ {
+ return [new SonataImporterExtension()];
+ }
+}
diff --git a/tests/Fixtures/Admin.php b/tests/Fixtures/Admin.php
new file mode 100644
index 0000000..07fe2bd
--- /dev/null
+++ b/tests/Fixtures/Admin.php
@@ -0,0 +1,25 @@
+
+ */
+class Admin extends TestCase
+{
+ /**
+ * @var string
+ */
+ public $route = 'import';
+
+ public function generateUrl(string $route)
+ {
+ $this->assertSame($this->route, $route);
+
+ return 'test';
+ }
+}
diff --git a/tests/Fixtures/CRUDController.php b/tests/Fixtures/CRUDController.php
new file mode 100644
index 0000000..35df39a
--- /dev/null
+++ b/tests/Fixtures/CRUDController.php
@@ -0,0 +1,80 @@
+
+ */
+class CRUDController extends TestCase
+{
+ use ImportActionTrait;
+
+ /**
+ * @var FormInterface
+ */
+ private $form;
+
+ /**
+ * @var ContainerInterface
+ */
+ public $container;
+
+ /**
+ * @var AdminInterface
+ */
+ public $admin;
+
+ /**
+ * @var string
+ */
+ public $flashType = 'error';
+
+ /**
+ * @var UploadedFile
+ */
+ private $file;
+
+ public function __construct(
+ FormInterface $form,
+ ContainerInterface $container,
+ $admin,
+ UploadedFile $file
+ ) {
+ parent::__construct();
+ $this->form = $form;
+ $this->container = $container;
+ $this->admin = $admin;
+ $this->file = $file;
+ }
+
+ public function createForm(string $formName, AdminImportDTO $object): FormInterface
+ {
+ $this->assertSame(AdminImportForm::class, $formName);
+
+ $object->file = $this->file;
+
+ return $this->form;
+ }
+
+ public function renderWithExtraParams(string $template, array $parameters)
+ {
+ $this->assertSame('form.template', $template);
+
+ return new Response();
+ }
+
+ public function addFlash(string $type, string $message)
+ {
+ $this->assertSame($this->flashType, $type);
+ }
+}
diff --git a/tests/Fixtures/Controller.php b/tests/Fixtures/Controller.php
new file mode 100644
index 0000000..0ded0b8
--- /dev/null
+++ b/tests/Fixtures/Controller.php
@@ -0,0 +1,17 @@
+
+ */
+class Controller extends TestCase
+{
+ public function setImportConfigurations(): void
+ {
+ }
+}
diff --git a/tests/Fixtures/ImportConfiguration.php b/tests/Fixtures/ImportConfiguration.php
new file mode 100644
index 0000000..f138065
--- /dev/null
+++ b/tests/Fixtures/ImportConfiguration.php
@@ -0,0 +1,32 @@
+
+ */
+class ImportConfiguration extends TestCase implements SonataImportConfiguration
+{
+ public function map(array $item, array $additionalData)
+ {
+ }
+
+ public function save(array $items, array $additionalData): void
+ {
+ }
+
+ public static function adminClass(): string
+ {
+ return Admin::class;
+ }
+
+ public static function format(): string
+ {
+ return 'csv';
+ }
+}
diff --git a/tests/Fixtures/fake.csv b/tests/Fixtures/fake.csv
new file mode 100644
index 0000000..3a606e4
--- /dev/null
+++ b/tests/Fixtures/fake.csv
@@ -0,0 +1,6 @@
+1,G.E. Longer-Life Indoor Recessed Floodlight Bulbs,Carlos Soltero,515,4.43,6.64,4.95,Nunavut,Office Furnishings,0.37
+2,"Angle-D Binders with Locking Rings, Label Holders",Carl Jackson,613,-54.04,7.3,7.72,Nunavut,Binders and Binder Accessories,0.38
+3,"SAFCO Mobile Desk Side File, Wire Frame",Carl Jackson,613,127.70,42.76,6.22,Nunavut,Storage & Organization,
+4,"SAFCO Commercial Wire Shelving, Black",Monica Federle,643,-695.26,138.14,35,Nunavut,Storage & Organization,
+5,Xerox 198,Dorothy Badders,678,-226.36,4.98,8.33,Nunavut,Paper,0.38
+6,Xerox 1980,Neola Schneider,807,-166.85,4.28,6.18,Nunavut,Paper,0.4
diff --git a/tests/Form/AdminImportTest.php b/tests/Form/AdminImportTest.php
new file mode 100644
index 0000000..a90b094
--- /dev/null
+++ b/tests/Form/AdminImportTest.php
@@ -0,0 +1,53 @@
+
+ */
+class AdminImportTest extends TypeTestCase
+{
+ /**
+ * @var UploadedFile
+ */
+ private $uploadedFile;
+
+ private $file;
+
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->uploadedFile = new UploadedFile(
+ $this->file = tempnam(sys_get_temp_dir(), 'test'),
+ 'imp.csv'
+ );
+ }
+
+ public function testSubmitValidData(): void
+ {
+ $form = $this->factory->create(
+ AdminImportForm::class,
+ new AdminImportDTO()
+ );
+
+ $form->submit([
+ 'file' => $this->uploadedFile
+ ]);
+
+ $this->assertTrue($form->isSynchronized());
+ $view = $form->createView();
+
+ $this->assertArrayHasKey('file', $view->children);
+ }
+
+ public function tearDown()
+ {
+ unlink($this->file);
+ }
+}
diff --git a/tests/SonataImporterBundleTest.php b/tests/SonataImporterBundleTest.php
new file mode 100644
index 0000000..1a01d61
--- /dev/null
+++ b/tests/SonataImporterBundleTest.php
@@ -0,0 +1,45 @@
+
+ */
+final class SonataImporterBundleTest extends TestCase
+{
+ /**
+ * @var SonataImporterBundle
+ */
+ private $bundle;
+
+ protected function setUp()
+ {
+ $this->bundle = new SonataImporterBundle();
+ }
+
+ public function testBundle(): void
+ {
+ $this->assertInstanceOf(Bundle::class, $this->bundle);
+ }
+
+ public function testCompilerPasses(): void
+ {
+ $containerBuilder = $this->prophesize(ContainerBuilder::class);
+
+ $containerBuilder->addCompilerPass(
+ Argument::type(AutoConfigureCompilerPass::class),
+ Argument::cetera()
+ )->shouldBeCalledTimes(1);
+
+ $this->bundle->build($containerBuilder->reveal());
+ }
+}