diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php index bff7f928..c4eb9a8b 100644 --- a/src/Controller/ScanController.php +++ b/src/Controller/ScanController.php @@ -22,7 +22,7 @@ namespace App\Controller; use App\Form\LabelSystem\ScanDialogType; -use App\Services\LabelSystem\BarcodeParser; +use App\Services\LabelSystem\Barcodes\BarcodeRedirector; use App\Services\LabelSystem\Barcodes\BarcodeNormalizer; use Doctrine\ORM\EntityNotFoundException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -40,7 +40,7 @@ class ScanController extends AbstractController protected $barcodeParser; protected $barcodeNormalizer; - public function __construct(BarcodeParser $barcodeParser, BarcodeNormalizer $barcodeNormalizer) + public function __construct(BarcodeRedirector $barcodeParser, BarcodeNormalizer $barcodeNormalizer) { $this->barcodeParser = $barcodeParser; $this->barcodeNormalizer = $barcodeNormalizer; @@ -61,7 +61,7 @@ class ScanController extends AbstractController try { [$type, $id] = $this->barcodeNormalizer->normalizeBarcodeContent($input); try { - return $this->redirect($this->barcodeParser->getQRRedirectTarget($type, $id)); + return $this->redirect($this->barcodeParser->getRedirectURL($type, $id)); } catch (EntityNotFoundException $exception) { $this->addFlash('success', 'scan.qr_not_found'); } @@ -84,7 +84,7 @@ class ScanController extends AbstractController { try { $this->addFlash('success', 'scan.qr_success'); - return $this->redirect($this->barcodeParser->getQRRedirectTarget($type, $id)); + return $this->redirect($this->barcodeParser->getRedirectURL($type, $id)); } catch (EntityNotFoundException $exception) { $this->addFlash('success', 'scan.qr_not_found'); return $this->redirectToRoute('homepage'); diff --git a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php index 83a056f0..90838d9a 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php @@ -76,7 +76,7 @@ final class BarcodeContentGenerator public function get1DBarcodeContent(AbstractDBElement $target): string { $prefix = $this->classToString(self::PREFIX_MAP, $target); - $id = sprintf('%04d', $target->getID()); + $id = sprintf('%04d', $target->getID() ?? 0); return $prefix . $id; } diff --git a/src/Services/LabelSystem/BarcodeParser.php b/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php similarity index 94% rename from src/Services/LabelSystem/BarcodeParser.php rename to src/Services/LabelSystem/Barcodes/BarcodeRedirector.php index 12fb66c8..8a3455f3 100644 --- a/src/Services/LabelSystem/BarcodeParser.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -namespace App\Services\LabelSystem; +namespace App\Services\LabelSystem\Barcodes; use App\Entity\Parts\PartLot; @@ -26,7 +26,7 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityNotFoundException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -final class BarcodeParser +final class BarcodeRedirector { private $urlGenerator; private $em; @@ -45,7 +45,7 @@ final class BarcodeParser * @return string The URL to which should be redirected. * @throws EntityNotFoundException */ - public function getQRRedirectTarget(string $type, int $id): string + public function getRedirectURL(string $type, int $id): string { switch ($type) { case 'part': diff --git a/src/Services/LabelSystem/LabelGenerator.php b/src/Services/LabelSystem/LabelGenerator.php index 2c8aa08c..8c8dd70e 100644 --- a/src/Services/LabelSystem/LabelGenerator.php +++ b/src/Services/LabelSystem/LabelGenerator.php @@ -62,8 +62,6 @@ final class LabelGenerator $elements = [$elements]; } - $elements_html = []; - foreach ($elements as $element) { if (!$this->supports($options, $element)) { throw new \InvalidArgumentException('The given options are not compatible with the given element!'); @@ -94,8 +92,14 @@ final class LabelGenerator return is_a($element, static::CLASS_SUPPORT_MAPPING[$supported_type]); } + /** + * Converts width and height given in mm to an size array, that can be used by DOMPDF for page size + * @param float $width The width of the paper + * @param float $height The height of the paper + * @return float[] + */ public function mmToPointsArray(float $width, float $height): array { - return [0, 0, $width * self::MM_TO_POINTS_FACTOR, $height * self::MM_TO_POINTS_FACTOR]; + return [0.0, 0.0, $width * self::MM_TO_POINTS_FACTOR, $height * self::MM_TO_POINTS_FACTOR]; } } \ No newline at end of file diff --git a/tests/Services/LabelSystem/BarcodeGeneratorTest.php b/tests/Services/LabelSystem/BarcodeGeneratorTest.php new file mode 100644 index 00000000..191d969d --- /dev/null +++ b/tests/Services/LabelSystem/BarcodeGeneratorTest.php @@ -0,0 +1,80 @@ +. + */ + +namespace App\Tests\Services\LabelSystem; + +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\Parts\Part; +use App\Services\LabelSystem\BarcodeGenerator; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +final class BarcodeGeneratorTest extends WebTestCase +{ + + /** @var BarcodeGenerator */ + protected $services; + + public function setUp(): void + { + self::bootKernel(); + $this->services = self::$container->get(BarcodeGenerator::class); + } + + public function testGetContent(): void + { + $part = new Part(); + $part->setName('Test'); + + //Test that all barcodes types are supported + foreach (LabelOptions::BARCODE_TYPES as $type) { + $options = new LabelOptions(); + $options->setBarcodeType($type); + $content = $this->services->generateSVG($options, $part); + + //When type is none, service must return null. + if ($type === 'none') { + $this->assertNull($content); + } else { + $this->assertIsString($content); + } + } + } + + public function testGenerateSVG(): void + { + $part = new Part(); + $part->setName('Test'); + + //Test that all barcodes types are supported + foreach (LabelOptions::BARCODE_TYPES as $type) { + $options = new LabelOptions(); + $options->setBarcodeType($type); + $svg = $this->services->generateSVG($options, $part); + + //When type is none, service must return null. + if ($type === "none") { + $this->assertNull($svg); + } else { + $this->assertStringContainsStringIgnoringCase("SVG", $svg); + } + } + } +} diff --git a/tests/Services/LabelSystem/BarcodeParserTest.php b/tests/Services/LabelSystem/BarcodeParserTest.php deleted file mode 100644 index aa62909b..00000000 --- a/tests/Services/LabelSystem/BarcodeParserTest.php +++ /dev/null @@ -1,36 +0,0 @@ -. - */ - -namespace App\Tests\Services\LabelSystem; - -use App\Services\LabelSystem\BarcodeParser; -use PHPUnit\Framework\TestCase; - -class BarcodeParserTest extends TestCase -{ - - - - public function testGetQRRedirectTarget() - { - - } - -} diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php new file mode 100644 index 00000000..b90dc809 --- /dev/null +++ b/tests/Services/LabelSystem/Barcodes/BarcodeContentGeneratorTest.php @@ -0,0 +1,79 @@ +. + */ + +namespace App\Tests\Services\LabelSystem\Barcodes; + +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; +use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; + +class BarcodeContentGeneratorTest extends KernelTestCase +{ + /** @var BarcodeContentGenerator */ + private $service; + + public function setUp(): void + { + self::bootKernel(); + $this->service = self::$container->get(BarcodeContentGenerator::class); + } + + + public function Barcode1DDataProvider(): array + { + return [ + ['P0000', Part::class], + ['L0000', PartLot::class], + ['S0000', Storelocation::class], + ]; + } + + public function Barcode2DDataProvider(): array + { + return [ + ['/scan/part/0', Part::class], + ['/scan/lot/0', PartLot::class], + ['/scan/location/0', Storelocation::class] + ]; + } + + /** + * @dataProvider Barcode1DDataProvider + */ + public function testGet1DBarcodeContent(string $expected, string $class): void + { + $this->assertSame($expected, $this->service->get1DBarcodeContent(new $class())); + } + + /** + * @dataProvider Barcode2DDataProvider + */ + public function testGetURLContent(string $expected, string $class): void + { + $url = $this->service->getURLContent(new $class()); + //URL must be absolute... + $this->assertStringStartsWith('http', $url); + + $this->assertStringEndsWith($expected, $url); + } +} diff --git a/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php b/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php new file mode 100644 index 00000000..2e0e35dd --- /dev/null +++ b/tests/Services/LabelSystem/Barcodes/BarcodeRedirectorTest.php @@ -0,0 +1,70 @@ +. + */ + +namespace App\Tests\Services\LabelSystem\Barcodes; + +use App\Services\LabelSystem\Barcodes\BarcodeRedirector; +use Doctrine\ORM\EntityNotFoundException; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; + +class BarcodeRedirectorTest extends KernelTestCase +{ + /** @var BarcodeRedirector */ + private $service; + + public function setUp(): void + { + self::bootKernel(); + $this->service = self::$container->get(BarcodeRedirector::class); + } + + public function urlDataProvider(): array + { + return [ + ['part', '/en/part/1'], + //Part lot redirects to Part info page (Part lot 1 is associated with part 3 + ['lot', '/en/part/3'], + ['location', '/en/store_location/1/parts'] + ]; + } + + /** + * @dataProvider urlDataProvider + * @group DB + */ + public function testGetRedirectURL(string $type, string $url): void + { + $this->assertSame($url, $this->service->getRedirectURL($type, 1)); + } + + public function testGetRedirectEntityNotFount(): void + { + $this->expectException(EntityNotFoundException::class); + //If we encounter an invalid lot, we must throw an exception + $this->service->getRedirectURL('lot', 12345678); + } + + public function testInvalidType(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->service->getRedirectURL('invalid', 1); + } +} diff --git a/tests/Services/LabelSystem/LabelGeneratorTest.php b/tests/Services/LabelSystem/LabelGeneratorTest.php new file mode 100644 index 00000000..b9266b56 --- /dev/null +++ b/tests/Services/LabelSystem/LabelGeneratorTest.php @@ -0,0 +1,100 @@ +. + */ + +namespace App\Tests\Services\LabelSystem; + +use App\Entity\Base\AbstractDBElement; +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; +use App\Services\LabelSystem\LabelGenerator; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +class LabelGeneratorTest extends WebTestCase +{ + /** @var LabelGenerator */ + protected $service; + + public function setUp(): void + { + self::bootKernel(); + $this->service = self::$container->get(LabelGenerator::class); + } + + public function supportsDataProvider(): array + { + return [ + ['part', Part::class], + ['part_lot', PartLot::class], + ['storelocation', Storelocation::class] + ]; + } + + /** + * @dataProvider supportsDataProvider + */ + public function testSupports(string $type, string $class) + { + $options = new LabelOptions(); + $options->setSupportedElement($type); + + //Ensure that the given class is supported + $this->assertTrue($this->service->supports($options, new $class())); + + //Ensure that another class is not supported + $not_supported = new class extends AbstractDBElement { + + public function getIDString(): string + { + return "not_important"; + } + }; + + $this->assertFalse($this->service->supports($options, $not_supported)); + } + + public function testMmToPointsArray() + { + $this->assertSame( + [0.0, 0.0, 141.7325, 85.0395], + $this->service->mmToPointsArray(50.0, 30.0) + ); + } + + public function testGenerateLabel() + { + $part = new Part(); + $options = new LabelOptions(); + $options->setLines('Test'); + $options->setSupportedElement('part'); + + //Test for a single passed element: + $pdf = $this->service->generateLabel($options, $part); + //Just a simple check if a PDF is returned + $this->assertStringStartsWith('%PDF-', $pdf); + + //Test for an array of elements + $pdf = $this->service->generateLabel($options, [$part, $part]); + //Just a simple check if a PDF is returned + $this->assertStringStartsWith('%PDF-', $pdf); + } +} diff --git a/tests/Services/LabelSystem/SandboxedTwigProviderTest.php b/tests/Services/LabelSystem/SandboxedTwigProviderTest.php new file mode 100644 index 00000000..43e1a8b3 --- /dev/null +++ b/tests/Services/LabelSystem/SandboxedTwigProviderTest.php @@ -0,0 +1,123 @@ +. + */ + +namespace App\Tests\Services\LabelSystem; + +use App\Entity\LabelSystem\LabelOptions; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; +use App\Services\LabelSystem\Barcodes\BarcodeExampleElementsGenerator; +use App\Services\LabelSystem\SandboxedTwigProvider; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; +use Twig\Sandbox\SecurityError; + +class SandboxedTwigProviderTest extends WebTestCase +{ + /** @var SandboxedTwigProvider */ + private $service; + + public function setUp(): void + { + self::bootKernel(); + $this->service = self::$container->get(SandboxedTwigProvider::class); + } + + public function twigDataProvider(): array + { + return [ + [' {% for i in range(1, 3) %} + {{ part.id }} + {{ part.name }} + {{ part.lastModified | format_datetime }} + {% endfor %} + '], + [' {% if part.category %} + {{ part.category }} + {% endif %} + '], + [' {% set a = random(1, 3) %} + {{ 1 + 2 | abs }} + {{ "test" | capitalize | escape | lower | raw }} + {{ "\n" | nl2br | trim | title | url_encode | reverse }} + '], + [' + {{ location.isRoot}} {{ location.isChildOf(location) }} {{ location.comment }} {{ location.level }} + {{ location.fullPath }} {% set arr = location.pathArray %} {% set child = location.children %} {{location.childrenNotSelectable}} + '], + [' + {{ part.reviewNeeded }} {{ part.tags }} {{ part.mass }} + '] + ]; + } + + public function twigNotAllowedDataProvider(): array + { + return [ + ["{% block test %} {% endblock %}"], + ["{% deprecated test %}"], + ["{% flush %}"], + ["{{ part.setName('test') }}"], + ["{{ part.setCategory(null) }}"] + ]; + } + + + /** + * @dataProvider twigDataProvider + */ + public function testTwigFeatures(string $twig) + { + $options = new LabelOptions(); + $options->setSupportedElement('part'); + $options->setLines($twig); + $options->setLinesMode('twig'); + + $twig = $this->service->getTwig($options); + $str = $twig->render('lines', [ + 'part' => new Part(), + 'lot' => new PartLot(), + 'location' => new Storelocation(), + ]); + + $this->assertIsString($str); + } + + /** + * @dataProvider twigNotAllowedDataProvider + */ + public function testTwigForbidden(string $twig) + { + $this->expectException(SecurityError::class); + + $options = new LabelOptions(); + $options->setSupportedElement('part'); + $options->setLines($twig); + $options->setLinesMode('twig'); + + $twig = $this->service->getTwig($options); + $str = $twig->render('lines', [ + 'part' => new Part(), + 'lot' => new PartLot(), + 'location' => new Storelocation(), + ]); + } +}