From 2c7ce3a0975da5a7297ff4e7262156c92dbec10c Mon Sep 17 00:00:00 2001 From: Christophe Ferreboeuf Date: Fri, 10 Jan 2025 16:27:09 +0100 Subject: [PATCH 1/2] Dev (#34) * add new tests - fix error in plugins processor - improve CI/CD --- .github/workflows/coding-standard.yml | 6 + .github/workflows/integration.yml | 2 +- .github/workflows/unit-tests.yml | 12 +- .gitignore | 2 + .../Files/Code/SpecificClassInjection.php | 6 +- Service/PDFWriter.php | 239 ++++++------------ Service/PDFWriter/StyleManager.php | 76 ++++++ Test/Integration/ProcessorTest.php | 192 ++++++++++++++ Test/Integration/Service/AuditTest.php | 36 ++- Test/Unit/Block/BackTest.php | 44 ++++ Test/Unit/Block/SaveTest.php | 30 +++ Test/Unit/Command/RunAuditCommandTest.php | 73 ++++++ .../Files/BlockViewModelRatioTest.php | 69 +++++ .../Processors/{ => Files}/CacheableTest.php | 3 +- .../Processors/Files/HardWrittenSQLTest.php | 111 ++++++++ Test/Unit/Processors/Files/PluginsTest.php | 188 ++++++++++++++ .../Files/SpecificClassInjectionTest.php | 184 ++++++++++++++ .../{ => Files}/UnusedModulesTest.php | 3 +- .../Processors/Files/UseOfRegistryTest.php | 102 ++++++++ Test/Unit/Service/LocalizationTest.php | 98 +++++++ Test/Unit/Service/PDFWriterTest.php | 218 ++++++++++++++++ phpunit.xml | 20 -- phpunit.xml.dist | 49 ++++ 23 files changed, 1566 insertions(+), 197 deletions(-) create mode 100644 Service/PDFWriter/StyleManager.php create mode 100644 Test/Integration/ProcessorTest.php create mode 100644 Test/Unit/Block/BackTest.php create mode 100644 Test/Unit/Block/SaveTest.php create mode 100644 Test/Unit/Command/RunAuditCommandTest.php create mode 100644 Test/Unit/Processors/Files/BlockViewModelRatioTest.php rename Test/Unit/Processors/{ => Files}/CacheableTest.php (97%) create mode 100644 Test/Unit/Processors/Files/HardWrittenSQLTest.php create mode 100644 Test/Unit/Processors/Files/PluginsTest.php create mode 100644 Test/Unit/Processors/Files/SpecificClassInjectionTest.php rename Test/Unit/Processors/{ => Files}/UnusedModulesTest.php (96%) create mode 100644 Test/Unit/Processors/Files/UseOfRegistryTest.php create mode 100644 Test/Unit/Service/LocalizationTest.php create mode 100644 Test/Unit/Service/PDFWriterTest.php delete mode 100644 phpunit.xml create mode 100644 phpunit.xml.dist diff --git a/.github/workflows/coding-standard.yml b/.github/workflows/coding-standard.yml index c4c8ed9..03c34e6 100644 --- a/.github/workflows/coding-standard.yml +++ b/.github/workflows/coding-standard.yml @@ -11,4 +11,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Run shell commands + run: | + echo "Current directory:" + pwd + echo "List files:" + ls -la - uses: extdn/github-actions-m2/magento-coding-standard/8.1@master \ No newline at end of file diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 82d8d7b..9bd87d0 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -37,4 +37,4 @@ jobs: composer_name: crealoz/easy-audit-free magento_version: '2.4.6' composer_version: 2 - phpunit_file: 'phpunit.xml' \ No newline at end of file + phpunit_file: 'phpunit.xml.dist' \ No newline at end of file diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index d1286e0..7b19dae 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -6,9 +6,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Run shell commands + run: | + echo "Current directory:" + pwd + echo "List files:" + ls -la + echo "List things in Test" + ls -la Test - uses: extdn/github-actions-m2/magento-unit-tests/8.1@master env: - MAGENTO_VERSION: '2.4.5' + MAGENTO_VERSION: '2.4.6' MODULE_NAME: Crealoz_EasyAudit COMPOSER_NAME: crealoz/easy-audit-free - PHPUNIT_FILE: 'phpunit.xml' \ No newline at end of file + PHPUNIT_FILE: 'phpunit.xml.dist' \ No newline at end of file diff --git a/.gitignore b/.gitignore index fc19cd9..ceca51f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea /.phpunit.result.cache +/coverage/ +phpunit.xml diff --git a/Processor/Files/Code/SpecificClassInjection.php b/Processor/Files/Code/SpecificClassInjection.php index ea755ce..e6e23a7 100644 --- a/Processor/Files/Code/SpecificClassInjection.php +++ b/Processor/Files/Code/SpecificClassInjection.php @@ -191,11 +191,11 @@ public function run(): void $this->results['warnings']['specificClassInjection']['files'][$className][] = $argumentName; } if ($fileErrorLevel > 10) { - $this->addErroneousFile($input, Audit::PRIORITY_HIGH); + $this->addErroneousFile($this->getFile(), Audit::PRIORITY_HIGH); } elseif ($fileErrorLevel > 5) { - $this->addErroneousFile($input, Audit::PRIORITY_AVERAGE); + $this->addErroneousFile($this->getFile(), Audit::PRIORITY_AVERAGE); } else { - $this->addErroneousFile($input, Audit::PRIORITY_LOW); + $this->addErroneousFile($this->getFile(), Audit::PRIORITY_LOW); } } diff --git a/Service/PDFWriter.php b/Service/PDFWriter.php index c943a67..7e06942 100644 --- a/Service/PDFWriter.php +++ b/Service/PDFWriter.php @@ -5,6 +5,7 @@ use Crealoz\EasyAudit\Api\Result\SectionInterface; use Crealoz\EasyAudit\Service\FileSystem\ModulePaths; use Crealoz\EasyAudit\Service\PDFWriter\SizeCalculation; +use Crealoz\EasyAudit\Service\PDFWriter\StyleManager; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Filesystem; @@ -36,6 +37,10 @@ class PDFWriter private int $annexNumber = 1; private array $annexes = []; + /** + * @var true + */ + private bool $logoInitialized = false; public function __construct( private readonly Filesystem $filesystem, @@ -43,6 +48,7 @@ public function __construct( private readonly \Psr\Log\LoggerInterface $logger, private readonly Reader $moduleReader, private readonly ModulePaths $modulePaths, + private readonly StyleManager $styleManager, private readonly array $specificSections = [], public int $x = 50, public int $columnCount = 1 @@ -62,15 +68,7 @@ public function createdPDF($results, $filename): string { $this->logger->debug('Starting to create the PDF ' . $filename); $this->pdf = new \Zend_Pdf(); - $imagePath = $this->moduleReader->getModuleDir(\Magento\Framework\Module\Dir::MODULE_VIEW_DIR, 'Crealoz_EasyAudit') . '/adminhtml/web/images/crealoz-logo-dark.png'; - try { - if (!isset($this->logo)) { - $this->logo = \Zend_Pdf_Image::imageWithPath($imagePath); - } - } catch (\Zend_Pdf_Exception $e) { - $this->logger->error('Error while loading the logo: ' . $e->getMessage()); - $this->logo = null; - } + $this->addPage(); if (!empty($results['introduction']) && is_array($results['introduction'])) { $this->logger->debug('Starting to write the introduction'); @@ -122,6 +120,24 @@ public function createdPDF($results, $filename): string return $filePath; } + private function getLogo() + { + if (!$this->logoInitialized) { + try { + if (!isset($this->logo)) { + $imagePath = $this->moduleReader->getModuleDir(\Magento\Framework\Module\Dir::MODULE_VIEW_DIR, 'Crealoz_EasyAudit') . '/adminhtml/web/images/crealoz-logo-dark.png'; + $this->logo = \Zend_Pdf_Image::imageWithPath($imagePath); + $this->logoInitialized = true; + } + } catch (\Zend_Pdf_Exception $e) { + $this->logger->error('Error while loading the logo: ' . $e->getMessage()); + $this->logo = null; + $this->logoInitialized = true; + } + } + return $this->logo; + } + /** * Display the header of the PDF with the logo of the company. * @return void @@ -131,8 +147,8 @@ private function makeHeaderAndFooter(): void { $this->currentPage->drawText(__('EasyAudit Report by Crealoz'), 20, 20); // Get the image path - if ($this->logo !== null) { - $this->currentPage->drawImage($this->logo, 500, 800, 550, 820); + if ($this->getLogo() !== null) { + $this->currentPage->drawImage($this->getLogo(), 500, 800, 550, 820); } $this->currentPage->drawText('Created on : ' . date('Y-m-d H:i:s'), 420, 20); } @@ -190,7 +206,7 @@ private function manageSubResult(array $subResults): void if ($this->y < $this->sizeCalculation->calculateTitlePlusFirstSubsectionSize($subResults['errors'])) { $this->addPage(); } - $this->setErrorStyle(14); + $this->styleManager->setErrorStyle($this->currentPage, 14); $this->displaySection('Errors', $subResults['errors']); } if (!empty($subResults['warnings'])) { @@ -199,7 +215,7 @@ private function manageSubResult(array $subResults): void } else { $this->y -= 15; } - $this->setWarningStyle(14); + $this->styleManager->setWarningStyle($this->currentPage, 14); $this->displaySection('Warnings', $subResults['warnings']); } if (!empty($subResults['suggestions'])) { @@ -208,7 +224,7 @@ private function manageSubResult(array $subResults): void } else { $this->y -= 15; } - $this->setGeneralStyle(14); + $this->styleManager->setGeneralStyle($this->currentPage, 14); $this->displaySection('Suggestions', $subResults['suggestions']); } } @@ -377,32 +393,53 @@ private function writeAnnexes(): void public function writeLine(string $text, bool $isFile = false, int $size = 9, int $depth = 0, float $r = 0, float $g = 0, float $b = 0): void { $text = trim($text); - $this->setGeneralStyle($size, $r, $g, $b); + $this->styleManager->setGeneralStyle($this->currentPage, $size, $r, $g, $b); // If line is too long, we split it $availableWidth = 130 / $this->columnCount; if ($depth == 0 && strlen($text) > $availableWidth) { + foreach ($this->getLines($text, $availableWidth, $isFile) as $line) { + $this->writeText($line, $size); + } + } else { + $this->writeText($text, $size); + } + } + + private function writeText(string $text, int $size): void + { + $this->currentPage->drawText($text, $this->columnX, $this->y); + $this->y -= floor($size * 1.3); + if ($this->y < 50) { + $this->switchColumnOrAddPage(); + } + } + + private function getLines(string $text, int $availableWidth, bool $isFile): array + { + $lines = []; + while (strlen($text) > $availableWidth) { if ($isFile) { $lastSlashPos = strrpos(substr($text, 0, $availableWidth), '/'); if ($lastSlashPos !== false) { - $lines = [substr($text, 0, $lastSlashPos + 1), substr($text, $lastSlashPos + 1)]; + $lines[] = substr($text, 0, $lastSlashPos + 1); + $text = substr($text, $lastSlashPos + 1); } else { - $lines = str_split($text, $availableWidth - 30); + $lines[] = substr($text, 0, $availableWidth - 30); // Ajustement pour $availableWidth + $text = substr($text, $availableWidth - 30); } } else { - $wrappedText = wordwrap($text, $availableWidth, "--SPLIT--"); - $lines = explode("--SPLIT--", $wrappedText); - } - $depth++; - foreach ($lines as $line) { - $this->writeLine($line, $isFile, $size, $depth, $r, $g, $b); + $wrappedText = wordwrap($text, $availableWidth, "--SPLIT--", true); + $splitLines = explode("--SPLIT--", $wrappedText); + $lines = array_merge($lines, $splitLines); + $text = ""; } - return; } - $this->currentPage->drawText($text, $this->columnX, $this->y); - $this->y -= floor($size * 1.3); - if ($this->y < 50) { - $this->switchColumnOrAddPage(); + + if (!empty($text)) { + $lines[] = $text; } + + return $lines; } /** @@ -462,11 +499,11 @@ private function writeTitle($text, $x = null): void $this->addPage(); } $x = $x ?? $this->x; - $this->setTitleStyle(); + $this->styleManager->setTitleStyle($this->currentPage); $this->y -= 15; $this->currentPage->drawText(strtoupper($text), $x, $this->y); $this->y -= 30; - $this->setGeneralStyle(); + $this->styleManager->setGeneralStyle($this->currentPage); } /** @@ -483,7 +520,7 @@ public function writeSubSectionIntro(array $subsection): void } if (isset($subsection['title'])) { $this->y -= 20; - $this->setSubTitleStyle(); + $this->styleManager->setSubTitleStyle($this->currentPage); $this->currentPage->drawText($subsection['title'], 48, $this->y); } if (isset($subsection['explanation'])) { @@ -507,14 +544,14 @@ public function writeSubSectionIntro(array $subsection): void */ private function writeSectionTitle(string $text): void { - $this->setTitleStyle(15); + $this->styleManager->setTitleStyle($this->currentPage, 15); $this->y -= 15; $this->currentPage->drawText($text, 43, $this->y); $this->y -= 20; if ($this->y < 50) { $this->addPage(); } - $this->setGeneralStyle(); + $this->styleManager->setGeneralStyle($this->currentPage); } /** @@ -528,143 +565,9 @@ public function addPage(): void if ($this->y !== 800) { $this->currentPage = $this->pdf->newPage(\Zend_Pdf_Page::SIZE_A4); $this->pdf->pages[] = $this->currentPage; - $this->setGeneralStyle(); + $this->styleManager->setGeneralStyle($this->currentPage); $this->y = 800; $this->makeHeaderAndFooter(); } } - - /** - * Sets the general style of the text. It will be black by default. - * - * @param int $size font size - * @param float $r red color balance between 0 and 1 - * @param float $g green color balance between 0 and 1 - * @param float $b blue color balance between 0 and 1 - * @return void - * @throws \Zend_Pdf_Exception - */ - public function setGeneralStyle(int $size = 9, float $r = 0, float $g = 0, float $b = 0): void - { - $style = new \Zend_Pdf_Style(); - $style->setLineColor(new \Zend_Pdf_Color_Rgb($r, $g, $b)); - $style->setFillColor(new \Zend_Pdf_Color_Rgb($r, $g, $b)); - $font = \Zend_Pdf_Font::fontWithName(\Zend_Pdf_Font::FONT_TIMES); - $style->setFont($font, $size); - $this->currentPage->setStyle($style); - } - - /** - * Sets the style of the title. It will be blue. - * - * @param int $size - * @return void - * @throws \Zend_Pdf_Exception - */ - private function setTitleStyle(int $size = 20): void - { - $style = new \Zend_Pdf_Style(); - // Blue color - $style->setLineColor(new \Zend_Pdf_Color_Rgb(0, 0, 0.85)); - $style->setFillColor(new \Zend_Pdf_Color_Rgb(0, 0, 0.85)); - $font = \Zend_Pdf_Font::fontWithName(\Zend_Pdf_Font::FONT_TIMES); - $style->setFont($font, $size); - $this->currentPage->setStyle($style); - } - - /** - * Sets the style of the subtitle. It will be greenish blue. - * - * @param int $size - * @return void - * @throws \Zend_Pdf_Exception - */ - private function setSubTitleStyle(int $size = 12): void - { - $style = new \Zend_Pdf_Style(); - // Blue color - $style->setLineColor(new \Zend_Pdf_Color_Rgb(0, 0.45, 0.85)); - $style->setFillColor(new \Zend_Pdf_Color_Rgb(0, 0.45, 0.85)); - $font = \Zend_Pdf_Font::fontWithName(\Zend_Pdf_Font::FONT_TIMES); - $style->setFont($font, $size); - $this->currentPage->setStyle($style); - } - - /** - * Sets the style of the error. It will be red. - * - * @param int $size - * @return void - * @throws \Zend_Pdf_Exception - */ - private function setErrorStyle(int $size = 11): void - { - $style = new \Zend_Pdf_Style(); - // Red color - $style->setLineColor(new \Zend_Pdf_Color_Rgb(0.85, 0, 0)); - $style->setFillColor(new \Zend_Pdf_Color_Rgb(0.85, 0, 0)); - $font = \Zend_Pdf_Font::fontWithName(\Zend_Pdf_Font::FONT_TIMES); - $style->setFont($font, $size); - $this->currentPage->setStyle($style); - } - - /** - * Sets the style of the warning. It will be orange. - * - * @param int $size - * @return void - * @throws \Zend_Pdf_Exception - */ - private function setWarningStyle($size = 11): void - { - $style = new \Zend_Pdf_Style(); - // Orange color - $style->setLineColor(new \Zend_Pdf_Color_Rgb(0.85, 0.45, 0)); - $style->setFillColor(new \Zend_Pdf_Color_Rgb(0.85, 0.45, 0)); - $font = \Zend_Pdf_Font::fontWithName(\Zend_Pdf_Font::FONT_TIMES); - $style->setFont($font, $size); - $this->currentPage->setStyle($style); - } - - /** - * Public getter for the columnY property - * - * @return int - */ - public function getColumnY(): int - { - return $this->columnY; - } - - /** - * Public setter for the columnY property - * - * @param int $columnY - * @return void - */ - public function setColumnY(int $columnY): void - { - $this->columnY = $columnY; - } - - /** - * Public getter for the columnX property - * - * @return int - */ - public function getColumnX(): int - { - return $this->columnX; - } - - /** - * Public setter for the columnX property - * - * @param int $columnX - * @return void - */ - public function setColumnX(int $columnX): void - { - $this->columnX = $columnX; - } } \ No newline at end of file diff --git a/Service/PDFWriter/StyleManager.php b/Service/PDFWriter/StyleManager.php new file mode 100644 index 0000000..6ad71e5 --- /dev/null +++ b/Service/PDFWriter/StyleManager.php @@ -0,0 +1,76 @@ +setLineColor(new Zend_Pdf_Color_Rgb($r, $g, $b)); + $style->setFillColor(new Zend_Pdf_Color_Rgb($r, $g, $b)); + $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES); + $style->setFont($font, $size); + $page->setStyle($style); + } + + /** + * Sets the style of the title. It will be blue. + * + * @param Zend_Pdf_Page $page + * @param int $size + * @return void + */ + public function setTitleStyle(Zend_Pdf_Page $page, int $size = 20): void + { + $this->setColorStyle($page, $size, 0, 0, 0.85); // Blue + } + + /** + * Sets the style of the subtitle. It will be greenish blue. + * + * @param Zend_Pdf_Page $page + * @param int $size + * @return void + */ + public function setSubTitleStyle(Zend_Pdf_Page $page, int $size = 12): void + { + $this->setColorStyle($page, $size, 0, 0.45, 0.85); // Greenish Blue + } + + public function setErrorStyle(Zend_Pdf_Page $page, int $size = 11): void + { + $this->setColorStyle($page, $size, 0.85, 0, 0); // Red + } + + public function setWarningStyle(Zend_Pdf_Page $page, int $size = 11): void + { + $this->setColorStyle($page, $size, 0.85, 0.45, 0); // Orange + } + + private function setColorStyle(Zend_Pdf_Page $page, int $size, float $r, float $g, float $b): void + { + $style = new Zend_Pdf_Style(); + $style->setLineColor(new Zend_Pdf_Color_Rgb($r, $g, $b)); + $style->setFillColor(new Zend_Pdf_Color_Rgb($r, $g, $b)); + $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES); + $style->setFont($font, $size); + $page->setStyle($style); + } +} diff --git a/Test/Integration/ProcessorTest.php b/Test/Integration/ProcessorTest.php new file mode 100644 index 0000000..8a72726 --- /dev/null +++ b/Test/Integration/ProcessorTest.php @@ -0,0 +1,192 @@ +isFile() && $file->getExtension() === 'php') { + $files[] = $file->getPathname(); + } + } + + foreach ($files as $file) { + require_once $file; + $className = $this->getClassNameFromFile($file); + if (class_exists($className)) { + /** Check that class in not abstract or an interface */ + $reflection = new \ReflectionClass($className); + if (!$reflection->isAbstract() && !$reflection->isInterface() && $reflection->implementsInterface(AuditProcessorInterface::class)) { + $constructor = $reflection->getConstructor(); + $parameters = $constructor ? $constructor->getParameters() : []; + + $dependencies = []; + foreach ($parameters as $parameter) { + if ($parameter->getName() === 'auditStorage') { + $dependencies[] = $this->getMockAuditStorage(); + } else { + $dependencies[] = $this->getMockDependency($parameter->getType()); + } + } + + $processor = $reflection->newInstanceArgs($dependencies); + + // Appeler setFile sur la classe parent + if (method_exists($processor, 'setFile')) { + $processor->setFile('fake_file_path'); // Faux fichier + } + + if (method_exists($processor, 'setArray')) { + $processor->setArray([]); + } + + if (method_exists($processor, 'getContent')) { + $file = $this->createTempXmlFile(); + $processor->setFile($file); + } + + $this->processors[$className] = [ + 'object' =>$processor, + 'reflection' => $reflection + ]; + } + } + } + } + + protected function tearDown(): void + { + unset($this->processors); + } + + private function getClassNameFromFile($file) + { + $contents = file_get_contents($file); + if (preg_match('/namespace\s+([^;]+);/i', $contents, $namespaceMatch) && + preg_match('/class\s+([^\s{]+)/i', $contents, $classMatch)) { + return trim($namespaceMatch[1]) . '\\' . trim($classMatch[1]); + } + return null; + } + + private function getMockAuditStorage() + { + $mock = $this->createMock(\Crealoz\EasyAudit\Model\AuditStorage::class); + $mock->method('getIgnoredModules')->willReturn(['Module1', 'Module2']); + return $mock; + } + + private function getMockDependency($class) + { + if ($class === null) { + return null; + } + + if (ltrim(DriverInterface::class, '\\') === ltrim((string) $class, '\\')) { + $mock = $this->createMock(DriverInterface::class); + $mock->method('fileGetContents')->willReturn('mocked file content'); + return $mock; + } + + return $this->createMock($class->getName()); + } + + private function createTempXmlFile(): string + { + $tempFile = tempnam(sys_get_temp_dir(), 'test_xml_'); + file_put_contents($tempFile, 'value'); + return $tempFile; + } + + public function testProcessors() + { + foreach ($this->processors as $class => $processor) { + $this->testProcessor($class, $processor['object']); + $this->testReflection($processor['reflection'], $processor['object']); + } + } + + private function testProcessor($class, $processor) + { + $this->assertInstanceOf(AuditProcessorInterface::class, $processor); + + $processor->prepopulateResults(); + $this->assertFalse($processor->hasErrors(), $class . ' should not have errors after prepopulateResults'); + + $processor->run(); + $results = $processor->getResults(); + $this->assertNotEmpty($results, $class . ' should produce non-empty results after run'); + + $processorName = $processor->getProcessorName(); + $this->assertIsString($processorName, $class . ' should return a string as processor name'); + + $processorTag = $processor->getProcessorTag(); + $this->assertIsString($processorTag, $class . ' should return a string as processor tag'); + $this->assertMatchesRegularExpression('/^[a-zA-Z0-9]+$/', $processorTag, $class . ' should return a tag without spaces or special characters'); + + $auditSection = $processor->getAuditSection(); + $this->assertIsString($auditSection, $class . ' should return a string as audit section'); + + $erroneousFiles = $processor->getErroneousFiles(); + $this->assertIsArray($erroneousFiles, $class . ' should return an array as erroneous files'); + + $hasErrors = $processor->hasErrors(); + $this->assertIsBool($hasErrors, $class . ' should return a boolean from hasErrors'); + } + + private function testReflection(\ReflectionClass $class, AuditProcessorInterface $processor) + { + $resultsProperty = $class->getProperty('results'); + $resultsProperty->setAccessible(true); + + $resultsProperty->setValue($processor, [ + 'hasErrors' => true, + 'errors' => ['error'], + 'warnings' => [], + 'suggestions' => [], + ]); + + $hasErrors = $processor->hasErrors(); + $this->assertTrue($hasErrors, $class . ' should return true from hasErrors when errors are present'); + + $resultsProperty->setValue($processor, []); + $this->assertFalse($processor->hasErrors(), $class . ' should return false when no errors are present'); + + $erroneousFilesProperty = $class->getProperty('erroneousFiles'); + $erroneousFilesProperty->setAccessible(true); + + $addErroneousFileMethod = $class->getMethod('addErroneousFile'); + $addErroneousFileMethod->setAccessible(true); + + $addErroneousFileMethod->invoke($processor, 'file1', 5); + $erroneousFiles = $erroneousFilesProperty->getValue($processor); + $this->assertEquals(['file1' => 5], $erroneousFiles, $class . ' should add a new erroneous file with the correct score'); + + $addErroneousFileMethod->invoke($processor, 'file1', 3); + $erroneousFiles = $erroneousFilesProperty->getValue($processor); + $this->assertEquals(['file1' => 8], $erroneousFiles, $class . ' should increment the score of an existing erroneous file'); + + $getIgnoredModulesMethod = $class->getMethod('getIgnoredModules'); + $getIgnoredModulesMethod->setAccessible(true); + $ignoredModules = $getIgnoredModulesMethod->invoke($processor); + $this->assertEquals(['Module1', 'Module2'], $ignoredModules, $class . ' should return the ignored modules'); + + + } + +} \ No newline at end of file diff --git a/Test/Integration/Service/AuditTest.php b/Test/Integration/Service/AuditTest.php index 4b576d9..0829623 100644 --- a/Test/Integration/Service/AuditTest.php +++ b/Test/Integration/Service/AuditTest.php @@ -4,6 +4,7 @@ use Crealoz\EasyAudit\Api\AuditRequestRepositoryInterface; use Crealoz\EasyAudit\Model\AuditRequest; +use Crealoz\EasyAudit\Model\Request\File; use Crealoz\EasyAudit\Model\Request\FileFactory; use Crealoz\EasyAudit\Model\AuditRequestFactory; use Crealoz\EasyAudit\Processor\Type\Logic; @@ -127,6 +128,28 @@ protected function setUp(): void ['xml', $this->xmlMock] ]); + /** @codingStandardsIgnoreStart */ + eval(' + namespace Crealoz\EasyAudit\Model; + + class AuditRequestFactory { + public function create(array $data = []) { + return new AuditRequest($data); + } + } + '); + + eval(' + namespace Crealoz\EasyAudit\Model\Request; + + class FileFactory { + public function create(array $data = []) { + return new \File($data); + } + } + '); + /** @codingStandardsIgnoreEnd */ + $this->arrayTools = $this->createMock(ArrayTools::class); $this->logger = $this->createMock(LoggerInterface::class); $this->auditRequestFactory = $this->createPartialMock(AuditRequestFactory::class, ['create']); @@ -141,7 +164,7 @@ protected function setUp(): void $this->serializer->method('serialize')->willReturn('{language: en_US}'); - $this->fileFactory->method('create')->willReturn($this->createMock(\Crealoz\EasyAudit\Model\Request\File::class)); + $this->fileFactory->method('create')->willReturn($this->createMock(File::class)); $this->audit = new Audit( $this->pdfWriter, @@ -163,6 +186,17 @@ protected function tearDown(): void unset($this->pdfWriter); unset($this->typeFactory); unset($this->arrayTools); + unset($this->logicMock); + unset($this->phpMock); + unset($this->xmlMock); + unset($this->typeMockBuilder); + unset($this->fileFactory); + unset($this->logger); + unset($this->auditRequestFactory); + unset($this->auditRequestRepository); + unset($this->serializer); + unset($this->localization); + } public function testRun() diff --git a/Test/Unit/Block/BackTest.php b/Test/Unit/Block/BackTest.php new file mode 100644 index 0000000..363f6db --- /dev/null +++ b/Test/Unit/Block/BackTest.php @@ -0,0 +1,44 @@ +getMockBuilder(Back::class) + ->disableOriginalConstructor() + ->onlyMethods(['getUrl']) + ->getMock(); + + $button->expects($this->once()) + ->method('getUrl') + ->with('*/*/') + ->willReturn('expected-url'); + + $this->assertEquals('expected-url', $button->getBackUrl()); + } + + public function testGetButtonData() + { + $button = $this->getMockBuilder(Back::class) + ->disableOriginalConstructor() + ->onlyMethods(['getBackUrl']) + ->getMock(); + + $button->expects($this->once()) + ->method('getBackUrl') + ->willReturn('expected-url'); + + $expected = [ + 'label' => __('Back'), + 'on_click' => "location.href = 'expected-url';", + 'sort_order' => 10, + ]; + + $this->assertEquals($expected, $button->getButtonData()); + } +} diff --git a/Test/Unit/Block/SaveTest.php b/Test/Unit/Block/SaveTest.php new file mode 100644 index 0000000..e9c7639 --- /dev/null +++ b/Test/Unit/Block/SaveTest.php @@ -0,0 +1,30 @@ +getMockBuilder(Save::class) + ->disableOriginalConstructor() + ->onlyMethods([]) + ->getMock(); + $expected = [ + 'label' => __('Request Audit'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => ['button' => ['event' => 'save']], + 'form-role' => 'save', + ], + 'sort_order' => 90, + ]; + + $actual = $saveButton->getButtonData(); + + $this->assertEquals($expected, $actual); + } +} diff --git a/Test/Unit/Command/RunAuditCommandTest.php b/Test/Unit/Command/RunAuditCommandTest.php new file mode 100644 index 0000000..183ebc9 --- /dev/null +++ b/Test/Unit/Command/RunAuditCommandTest.php @@ -0,0 +1,73 @@ +auditServiceMock = $this->createMock(Audit::class); + $this->auditStorageMock = $this->createMock(AuditStorage::class); + + $this->command = new RunAuditCommand( + $this->auditServiceMock, + $this->auditStorageMock + ); + } + + public function testExecuteWithDefaultOptions() + { + $inputMock = $this->createMock(InputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); + + $inputMock->method('getOption')->willReturnMap([ + ['language', 'en_US'], + ['ignored-modules', ''], + ]); + + $this->auditServiceMock + ->expects($this->once()) + ->method('run') + ->with($outputMock, 'en_US'); + + $result = $this->command->run($inputMock, $outputMock); + + $this->assertEquals(RunAuditCommand::SUCCESS, $result); + } + + public function testExecuteWithIgnoredModules() + { + $inputMock = $this->createMock(InputInterface::class); + $outputMock = $this->createMock(OutputInterface::class); + + $inputMock->method('getOption')->willReturnMap([ + ['language', 'en_US'], + ['ignored-modules', 'Module1,Module2'], // Ignored modules + ]); + + $this->auditStorageMock + ->expects($this->once()) + ->method('setIgnoredModules') + ->with(['Module1', 'Module2']); + + $this->auditServiceMock + ->expects($this->once()) + ->method('run') + ->with($outputMock, 'en_US'); + + $result = $this->command->run($inputMock, $outputMock); + + $this->assertEquals(RunAuditCommand::SUCCESS, $result); + } +} diff --git a/Test/Unit/Processors/Files/BlockViewModelRatioTest.php b/Test/Unit/Processors/Files/BlockViewModelRatioTest.php new file mode 100644 index 0000000..5439938 --- /dev/null +++ b/Test/Unit/Processors/Files/BlockViewModelRatioTest.php @@ -0,0 +1,69 @@ +auditStorage = $this->createMock(AuditStorage::class); + $this->blockViewModelRatio = new BlockViewModelRatio($this->auditStorage); + } + + protected function tearDown(): void + { + unset($this->blockViewModelRatio); + unset($this->auditStorage); + } + + public function testGetProcessorName() + { + $this->assertEquals('Block vs ViewModel Ratio', $this->blockViewModelRatio->getProcessorName()); + } + + public function testGetAuditSection() + { + $this->assertEquals('PHP', $this->blockViewModelRatio->getAuditSection()); + } + + public function testPrepopulateResults() + { + $this->blockViewModelRatio->prepopulateResults(); + $results = $this->blockViewModelRatio->getResults(); + $this->assertArrayHasKey('warnings', $results); + $this->assertArrayHasKey('blockViewModelRatio', $results['warnings']); + } + + public function testRun() + { + $files = [ + '/path/Vendor/Module/Block/File1.php', + '/path/Vendor/Module/ViewModel/File2.php', + '/path/Vendor/Module/Block/File3.php', + '/path/Vendor/Module/Block/File4.php', + ]; + + $this->blockViewModelRatio->setArray($files); + $this->auditStorage->method('isModuleIgnored')->willReturn(false); + + $this->blockViewModelRatio->run(); + $results = $this->blockViewModelRatio->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('blockViewModelRatio', $results['warnings']); + $this->assertArrayHasKey('Vendor_Module', $results['warnings']['blockViewModelRatio']['files']); + $this->assertEquals(0.75, $results['warnings']['blockViewModelRatio']['files']['Vendor_Module']); + } + + public function testGetProcessorTag() + { + $this->assertEquals('BlockVsViewModelRatio', $this->blockViewModelRatio->getProcessorTag()); + } +} \ No newline at end of file diff --git a/Test/Unit/Processors/CacheableTest.php b/Test/Unit/Processors/Files/CacheableTest.php similarity index 97% rename from Test/Unit/Processors/CacheableTest.php rename to Test/Unit/Processors/Files/CacheableTest.php index 3dcc301..3dffcf6 100644 --- a/Test/Unit/Processors/CacheableTest.php +++ b/Test/Unit/Processors/Files/CacheableTest.php @@ -1,6 +1,6 @@ tempFile); unset($this->processor); + unset($this->xmlString); } public function testRun(): void diff --git a/Test/Unit/Processors/Files/HardWrittenSQLTest.php b/Test/Unit/Processors/Files/HardWrittenSQLTest.php new file mode 100644 index 0000000..a2ddd41 --- /dev/null +++ b/Test/Unit/Processors/Files/HardWrittenSQLTest.php @@ -0,0 +1,111 @@ +auditStorage = $this->createMock(AuditStorage::class); + $this->driver = $this->createMock(DriverInterface::class); + $this->driver->method('fileGetContents')->willReturnMap( + [ + ['/path/to/empty.php', null, null, 'toto'], + ['/path/to/error.php', null, null, ' + $select = "SELECT * FROM table"; + $delete = "DELETE FROM table"; + $update = "UPDATE table SET column = value"; + $insert = "INSERT INTO table (column) VALUES (value)"; + $join = "SELECT * FROM table1 JOIN table2 ON table1.id = table2.id"; + '] + ] + ); + $this->hardWrittenSQL = new HardWrittenSQL($this->auditStorage, $this->driver); + } + + protected function tearDown(): void + { + unset($this->hardWrittenSQL); + unset($this->auditStorage); + unset($this->driver); + } + + public function testGetProcessorName() + { + $this->assertEquals('Hard Written SQL', $this->hardWrittenSQL->getProcessorName()); + } + + public function testGetAuditSection() + { + $this->assertEquals('PHP', $this->hardWrittenSQL->getAuditSection()); + } + + public function testPrepopulateResults() + { + $this->hardWrittenSQL->prepopulateResults(); + $results = $this->hardWrittenSQL->getResults(); + $this->assertArrayHasKey('errors', $results); + $this->assertArrayHasKey('warnings', $results); + $this->assertArrayHasKey('suggestions', $results); + } + + /** + * @throws FileSystemException + */ + public function testRun() + { + + $results = $this->getResultsFromFileContent('/path/to/empty.php'); + + $this->assertFalse($results['hasErrors']); + $this->testArrayKeysExist($results); + + $results = $this->getResultsFromFileContent('/path/to/error.php'); + + $this->assertNotEmpty($results, 'Results should not be empty'); + $this->assertTrue($results['hasErrors'], 'Results should indicate errors'); + $this->testArrayKeysExist($results); + + $this->assertNotEmpty($results['errors']['hardWrittenSQLSelect']['files']); + $this->assertNotEmpty($results['errors']['hardWrittenSQLDelete']['files']); + $this->assertNotEmpty($results['warnings']['hardWrittenSQLUpdate']['files']); + $this->assertNotEmpty($results['warnings']['hardWrittenSQLInsert']['files']); + $this->assertNotEmpty($results['suggestions']['hardWrittenSQLJoin']['files']); + + } + + private function getResultsFromFileContent($path) + { + $content = $this->driver->fileGetContents($path); + $this->assertNotNull($content, 'fileGetContents should not return null for ' . $path); + + $this->hardWrittenSQL->prepopulateResults(); + $this->hardWrittenSQL->setFile($path); + $this->hardWrittenSQL->run(); + return $this->hardWrittenSQL->getResults(); + } + + private function testArrayKeysExist($results) + { + $this->assertArrayHasKey('hardWrittenSQLSelect', $results['errors']); + $this->assertArrayHasKey('hardWrittenSQLDelete', $results['errors']); + $this->assertArrayHasKey('hardWrittenSQLUpdate', $results['warnings']); + $this->assertArrayHasKey('hardWrittenSQLInsert', $results['warnings']); + $this->assertArrayHasKey('hardWrittenSQLJoin', $results['suggestions']); + } + + public function testGetProcessorTag() + { + $this->assertEquals('hardWrittenSQL', $this->hardWrittenSQL->getProcessorTag()); + } +} \ No newline at end of file diff --git a/Test/Unit/Processors/Files/PluginsTest.php b/Test/Unit/Processors/Files/PluginsTest.php new file mode 100644 index 0000000..28d75fd --- /dev/null +++ b/Test/Unit/Processors/Files/PluginsTest.php @@ -0,0 +1,188 @@ +auditStorage = $this->createMock(AuditStorage::class); + $this->aroundToAfter = $this->createMock(AroundToAfter::class); + $this->aroundToBefore = $this->createMock(AroundToBefore::class); + $this->checkConfigProvider = $this->createMock(CheckConfigProvider::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->filesUtility = $this->createMock(Files::class); + + $this->plugins = new Plugins( + $this->auditStorage, + $this->aroundToAfter, + $this->aroundToBefore, + $this->checkConfigProvider, + $this->logger, + $this->filesUtility + ); + } + + protected function tearDown(): void + { + unset($this->plugins); + unset($this->auditStorage); + unset($this->aroundToAfter); + unset($this->aroundToBefore); + unset($this->checkConfigProvider); + unset($this->logger); + unset($this->filesUtility); + } + + private function createDummyFile($content) + { + $filePath = tempnam(sys_get_temp_dir(), 'test'); + file_put_contents($filePath, $content); + return $filePath; + } + + public function testSameModulePlugin() + { + $dummyContent = << + + + + +XML; + $dummyFile = $this->createDummyFile($dummyContent); + $this->plugins->setFile($dummyFile); + + $this->plugins->run(); + $results = $this->plugins->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('sameModulePlugin', $results['errors']); + $this->assertContains('Vendor\Module\PluginClass', $results['errors']['sameModulePlugin']['files']); + } + + public function testMagentoFrameworkPlugin() + { + $dummyContent = << + + + + +XML; + $dummyFile = $this->createDummyFile($dummyContent); + $this->plugins->setFile($dummyFile); + + $this->plugins->run(); + $results = $this->plugins->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('magentoFrameworkPlugin', $results['errors']); + $this->assertArrayHasKey('Magento\Framework\Class', $results['errors']['magentoFrameworkPlugin']['files']); + $this->assertContains('Vendor\Module\PluginClass', $results['errors']['magentoFrameworkPlugin']['files']['Magento\Framework\Class']); + } + + public function testNonExistentPluginFile() + { + $dummyContent = << + + + + +XML; + $dummyFile = $this->createDummyFile($dummyContent); + $this->plugins->setFile($dummyFile); + + $this->filesUtility->method('classFileExists')->willReturn(false); + $this->plugins->run(); + $results = $this->plugins->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('nonExistentPluginFile', $results['warnings']); + $this->assertContains('Vendor\Module\NonExistentPluginClass', $results['warnings']['nonExistentPluginFile']['files']); + } + + public function testAroundToBeforePlugin() + { + $dummyContent = << + + + + +XML; + $dummyFile = $this->createDummyFile($dummyContent); + $this->plugins->setFile($dummyFile); + + $this->aroundToBefore->method('execute')->willThrowException(new AroundToBeforePluginException(__('Error'), 'Vendor\Module\PluginClass')); + $this->plugins->run(); + $results = $this->plugins->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('aroundToBeforePlugin', $results['warnings']); + $this->assertContains('Vendor\Module\PluginClass', $results['warnings']['aroundToBeforePlugin']['files']); + } + + public function testAroundToAfterPlugin() + { + $dummyContent = << + + + + +XML; + $dummyFile = $this->createDummyFile($dummyContent); + $this->plugins->setFile($dummyFile); + + $this->aroundToAfter->method('execute')->willThrowException(new AroundToAfterPluginException(__('Error'), 'Vendor\Module\PluginClass')); + $this->plugins->run(); + $results = $this->plugins->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('aroundToAfterPlugin', $results['warnings']); + $this->assertContains('Vendor\Module\PluginClass', $results['warnings']['aroundToAfterPlugin']['files']); + } + + public function testConfigProviderPlugin() + { + $dummyContent = << + + + + +XML; + $dummyFile = $this->createDummyFile($dummyContent); + $this->plugins->setFile($dummyFile); + + $this->checkConfigProvider->method('execute')->willThrowException(new ConfigProviderPluginException(__('Error'), 'Vendor\Module\PluginClass')); + $this->plugins->run(); + $results = $this->plugins->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('configProviderPlugin', $results['errors']); + $this->assertContains('Vendor\Module\PluginClass', $results['errors']['configProviderPlugin']['files']); + } +} \ No newline at end of file diff --git a/Test/Unit/Processors/Files/SpecificClassInjectionTest.php b/Test/Unit/Processors/Files/SpecificClassInjectionTest.php new file mode 100644 index 0000000..fd5dbf4 --- /dev/null +++ b/Test/Unit/Processors/Files/SpecificClassInjectionTest.php @@ -0,0 +1,184 @@ +auditStorage = $this->createMock(AuditStorage::class); + $this->classNameGetter = $this->createMock(ClassNameGetter::class); + $this->definitions = $this->createMock(DefinitionInterface::class); + $this->hasModelAnInterface = $this->createMock(HasModelAnInterface::class); + $this->argumentTypeChecker = $this->createMock(ArgumentTypeChecker::class); + $this->constructorService = $this->createMock(ConstructorService::class); + + $this->specificClassInjection = new SpecificClassInjection( + $this->auditStorage, + $this->classNameGetter, + $this->definitions, + $this->hasModelAnInterface, + $this->argumentTypeChecker, + $this->constructorService + ); + } + + protected function tearDown(): void + { + unset($this->specificClassInjection); + unset($this->auditStorage); + unset($this->classNameGetter); + unset($this->definitions); + unset($this->hasModelAnInterface); + unset($this->argumentTypeChecker); + unset($this->constructorService); + } + + public function testGetProcessorName() + { + $this->assertEquals('Specific Class Injection', $this->specificClassInjection->getProcessorName()); + } + + public function testGetAuditSection() + { + $this->assertEquals('PHP', $this->specificClassInjection->getAuditSection()); + } + + public function testPrepopulateResults() + { + $this->specificClassInjection->prepopulateResults(); + $results = $this->specificClassInjection->getResults(); + + $this->assertArrayHasKey('errors', $results); + $this->assertArrayHasKey('warnings', $results); + $this->assertArrayHasKey('suggestions', $results); + } + + public function testRun() + { + $this->classNameGetter->method('getClassFullNameFromFile')->willReturn('SomeClass'); + $this->definitions->method('getParameters')->willReturn([ + ['param1', 'SomeClass'], + ['param2', 'AnotherClass'] + ]); + $this->constructorService->method('isConstructorOverridden')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentModel')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentCollection')->willReturn(false); + $this->argumentTypeChecker->method('isArgumentRepository')->willReturn(false); + $this->hasModelAnInterface->method('execute')->willReturn(false); + $this->argumentTypeChecker->method('isArgumentResourceModel')->willReturn(false); + + $this->specificClassInjection->setFile('/path/to/file.php'); + $this->specificClassInjection->run(); + $results = $this->specificClassInjection->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('specificClassInjection', $results['warnings']); + } + + public function testIsArgumentRepository() + { + $className = 'SomeClass'; + $argumentName = 'SomeRepository'; + $this->classNameGetter->method('getClassFullNameFromFile')->willReturn($className); + $this->definitions->method('getParameters')->willReturn([ + ['param1', $argumentName] + ]); + $this->constructorService->method('isConstructorOverridden')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentModel')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentRepository')->willReturn(true); + + $this->specificClassInjection->setFile('/path/to/file.php'); + $this->specificClassInjection->run(); + $results = $this->specificClassInjection->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('repositoryMustUseInterface', $results['errors']); + $this->assertArrayHasKey($className, $results['errors']['repositoryMustUseInterface']['files']); + $this->assertContains($argumentName, $results['errors']['repositoryMustUseInterface']['files'][$className]); + } + + public function testIsArgumentCollection() + { + $className = 'SomeClass'; + $argumentName = 'SomeCollection'; + $this->classNameGetter->method('getClassFullNameFromFile')->willReturn($className); + $this->definitions->method('getParameters')->willReturn([ + ['param1', $argumentName] + ]); + $this->constructorService->method('isConstructorOverridden')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentModel')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentCollection')->willReturn(true); + + $this->specificClassInjection->setFile('/path/to/file.php'); + $this->specificClassInjection->run(); + $results = $this->specificClassInjection->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('collectionMustUseFactory', $results['errors']); + $this->assertArrayHasKey($className, $results['errors']['collectionMustUseFactory']['files']); + $this->assertContains($argumentName, $results['errors']['collectionMustUseFactory']['files'][$className]); + } + + public function testSpecificModelInjection() + { + $className = 'SomeClass'; + $argumentName = 'SomeModel'; + $this->classNameGetter->method('getClassFullNameFromFile')->willReturn($className); + $this->definitions->method('getParameters')->willReturn([ + ['param1', $argumentName] + ]); + $this->constructorService->method('isConstructorOverridden')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentModel')->willReturn(true); + $this->hasModelAnInterface->method('execute')->willReturn(true); + + $this->specificClassInjection->setFile('/path/to/file.php'); + $this->specificClassInjection->run(); + $results = $this->specificClassInjection->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('specificModelInjection', $results['errors']); + $this->assertArrayHasKey($className, $results['errors']['specificModelInjection']['files']); + $this->assertContains($argumentName, $results['errors']['specificModelInjection']['files'][$className]); + } + + public function testResourceModelInjection() + { + $className = 'SomeClass'; + $argumentName = 'SomeResourceModel'; + $this->classNameGetter->method('getClassFullNameFromFile')->willReturn($className); + $this->definitions->method('getParameters')->willReturn([ + ['param1', $argumentName] + ]); + $this->constructorService->method('isConstructorOverridden')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentModel')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentResourceModel')->willReturn(true); + $this->argumentTypeChecker->method('isArgumentRepository')->willReturn(false); + + $this->specificClassInjection->setFile('/path/to/file.php'); + $this->specificClassInjection->run(); + $results = $this->specificClassInjection->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('resourceModelInjection', $results['errors']); + $this->assertArrayHasKey($className, $results['errors']['resourceModelInjection']['files']); + $this->assertContains($argumentName, $results['errors']['resourceModelInjection']['files'][$className]); + } +} \ No newline at end of file diff --git a/Test/Unit/Processors/UnusedModulesTest.php b/Test/Unit/Processors/Files/UnusedModulesTest.php similarity index 96% rename from Test/Unit/Processors/UnusedModulesTest.php rename to Test/Unit/Processors/Files/UnusedModulesTest.php index 1d84b42..2e5f0e2 100644 --- a/Test/Unit/Processors/UnusedModulesTest.php +++ b/Test/Unit/Processors/Files/UnusedModulesTest.php @@ -1,6 +1,6 @@ processor); + unset($this->getModuleConfigMock); } public function testRun(): void diff --git a/Test/Unit/Processors/Files/UseOfRegistryTest.php b/Test/Unit/Processors/Files/UseOfRegistryTest.php new file mode 100644 index 0000000..4220751 --- /dev/null +++ b/Test/Unit/Processors/Files/UseOfRegistryTest.php @@ -0,0 +1,102 @@ +auditStorage = $this->createMock(AuditStorage::class); + $this->classNameGetter = $this->createMock(ClassNameGetter::class); + $this->definitions = $this->createMock(DefinitionInterface::class); + $this->constructorService = $this->createMock(ConstructorService::class); + + $this->useOfRegistry = new UseOfRegistry( + $this->auditStorage, + $this->classNameGetter, + $this->definitions, + $this->constructorService + ); + } + + public function testGetProcessorName() + { + $this->assertEquals('Use of Registry', $this->useOfRegistry->getProcessorName()); + } + + public function testGetAuditSection() + { + $this->assertEquals('PHP', $this->useOfRegistry->getAuditSection()); + } + + public function testPrepopulateResults() + { + $this->useOfRegistry->prepopulateResults(); + $results = $this->useOfRegistry->getResults(); + + $this->assertArrayHasKey('errors', $results); + $this->assertArrayHasKey('warnings', $results); + $this->assertArrayHasKey('suggestions', $results); + $this->assertArrayHasKey('useOfRegistry', $results['errors']); + } + + public function testRunWithRegistry() + { + $className = 'SomeClass'; + $this->classNameGetter->method('getClassFullNameFromFile')->willReturn($className); + $this->definitions->method('getParameters')->willReturn([ + ['param1', 'Magento\Framework\Registry'] + ]); + $this->constructorService->method('isConstructorOverridden')->willReturn(true); + + $this->useOfRegistry->prepopulateResults(); + $this->useOfRegistry->setFile('/path/to/file.php'); + $this->useOfRegistry->run(); + $results = $this->useOfRegistry->getResults(); + + $this->assertTrue($results['hasErrors']); + $this->assertArrayHasKey('useOfRegistry', $results['errors']); + $this->assertContains($className, $results['errors']['useOfRegistry']['files']); + } + + public function testRunWithoutRegistry() + { + $className = 'SomeClass'; + $this->classNameGetter->method('getClassFullNameFromFile')->willReturn($className); + $this->definitions->method('getParameters')->willReturn([ + ['param1', 'SomeOtherClass'] + ]); + $this->constructorService->method('isConstructorOverridden')->willReturn(true); + + $this->useOfRegistry->prepopulateResults(); + $this->useOfRegistry->setFile('/path/to/file.php'); + $this->useOfRegistry->run(); + $results = $this->useOfRegistry->getResults(); + + $this->assertFalse($results['hasErrors']); + $this->assertArrayHasKey('useOfRegistry', $results['errors']); + $this->assertNotContains($className, $results['errors']['useOfRegistry']['files']); + } + + protected function tearDown(): void + { + unset($this->useOfRegistry); + unset($this->auditStorage); + unset($this->classNameGetter); + unset($this->definitions); + unset($this->constructorService); + } +} \ No newline at end of file diff --git a/Test/Unit/Service/LocalizationTest.php b/Test/Unit/Service/LocalizationTest.php new file mode 100644 index 0000000..7d1d49c --- /dev/null +++ b/Test/Unit/Service/LocalizationTest.php @@ -0,0 +1,98 @@ +filesystem = $this->createMock(Filesystem::class); + $this->moduleReader = $this->createMock(Reader::class); + $this->translator = $this->createMock(TranslateInterface::class); + $this->translateRenderer = $this->createMock(\Magento\Framework\Phrase\Renderer\Translate::class); + $this->appState = $this->createMock(State::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->directoryRead = $this->createMock(\Magento\Framework\Filesystem\Directory\Read::class); + $this->directoryRead->method('read') + ->willReturn(['en_US.csv', 'fr_FR.csv']); + + $this->moduleReader->method('getModuleDir') + ->with(\Magento\Framework\Module\Dir::MODULE_I18N_DIR, 'Crealoz_EasyAudit') + ->willReturn($this->moduleDir); + + $this->filesystem->method('getDirectoryReadByPath') + ->with($this->moduleDir) + ->willReturn($this->directoryRead); + + $this->localization = new Localization( + $this->filesystem, + $this->moduleReader, + $this->translator, + $this->translateRenderer, + $this->appState, + $this->logger + ); + } + + public function testGetAvailableLanguages() + { + + $resultDirectoryRead = $this->filesystem->getDirectoryReadByPath($this->moduleDir); + $this->assertNotNull($resultDirectoryRead); + + $result = $this->localization->getAvailableLanguages(); + + $this->assertEquals(['en_US', 'fr_FR'], $result); + } + + public function testInitializeLanguage() + { + $locale = 'fr_FR'; + $this->appState->method('setAreaCode') + ->will($this->throwException(new \Exception())); + + $this->translator->expects($this->once()) + ->method('setLocale') + ->with($locale); + + $this->translator->expects($this->once()) + ->method('loadData') + ->with(Area::AREA_GLOBAL, true); + + $result = $this->localization->initializeLanguage($locale); + $this->assertEquals($locale, $result); + } + + protected function tearDown(): void + { + unset($this->filesystem); + unset($this->moduleReader); + unset($this->translator); + unset($this->translateRenderer); + unset($this->appState); + unset($this->logger); + unset($this->localization); + Phrase::setRenderer(new \Magento\Framework\Phrase\Renderer\Placeholder()); + } +} \ No newline at end of file diff --git a/Test/Unit/Service/PDFWriterTest.php b/Test/Unit/Service/PDFWriterTest.php new file mode 100644 index 0000000..b8f2773 --- /dev/null +++ b/Test/Unit/Service/PDFWriterTest.php @@ -0,0 +1,218 @@ +filesystem = $this->createMock(Filesystem::class); + $this->sizeCalculation = $this->createMock(SizeCalculation::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->moduleReader = $this->createMock(Reader::class); + $this->modulePaths = $this->createMock(ModulePaths::class); + $this->styleManager = new PDFWriter\StyleManager(); + $this->BlockVsVMRatio = $this->createMock(PDFWriter\SpecificSection\BlockVMRatio::class); + + $this->mockedPage = $this->createMock(\Zend_Pdf_Page::class); + $this->mockedStyleManager = $this->createMock(\Crealoz\EasyAudit\Service\PDFWriter\StyleManager::class); + + + $this->pdfWriter = new PDFWriter( + $this->filesystem, + $this->sizeCalculation, + $this->logger, + $this->moduleReader, + $this->modulePaths, + $this->styleManager, + [ + 'manageBlockVMRatio' => $this->BlockVsVMRatio + ], + 50, + 5 + ); + + $this->reflection = new \ReflectionClass($this->pdfWriter); + + $pdfProperty = $this->reflection->getProperty('pdf'); + $pdfProperty->setAccessible(true); + $pdfProperty->setValue($this->pdfWriter, new \Zend_Pdf()); + + $this->pdfWriter->currentPage = new \Zend_Pdf_Page(100, 100); + } + + public function testCreatedPDF(): void + { + $results = [ + 'introduction' => [ + 'overall' => [ + 'summary' => ['text' => 'This is a test summary.'], + 'files' => ['scope' => ['file1' => 10, 'file2' => 5]] + ] + ], + 'PHP' => [ + 'BlockVsViewModelRatio' => [ + 'hasErrors' => true, + 'title' => 'Subsection 1', + 'errors' => [ + 'testError' => [ + 'title' => 'Error 1', + 'explanation' => 'Explanation 1', + 'files' => ['file1' => 10, 'file2' => 5], + 'specificSections' => 'manageBlockVMRatio' + ] + ], + 'warnings' => [ + 'testWarning' => [ + 'title' => 'Warning 1', + 'explanation' => 'Explanation 1', + 'files' => ['file1' => 10, 'file2' => 5] + ] + ], + 'suggestions' => [ + 'testSuggestion' => [ + 'title' => 'Suggestion 1', + 'explanation' => 'Explanation 1', + 'files' => ['file1' => 10, 'file2' => 5] + ] + ] + ] + ] + ]; + + $filename = 'test'; + + $tempDir = sys_get_temp_dir() . '/pdf_test'; + if (!is_dir($tempDir)) { + mkdir($tempDir, 0777, true); + } + + $this->moduleReader->method('getModuleDir')->willReturn($tempDir); + $directoryRead = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadInterface::class); + $directoryWrite = $this->createMock(\Magento\Framework\Filesystem\Directory\WriteInterface::class); + + $directoryRead->method('isExist')->willReturn(true); + $directoryWrite->method('getAbsolutePath')->willReturn($tempDir . '/' . $filename . '.pdf'); + + $this->filesystem->method('getDirectoryRead')->willReturn($directoryRead); + $this->filesystem->method('getDirectoryWrite')->willReturn($directoryWrite); + + $filePath = $this->pdfWriter->createdPDF($results, $filename); + + $this->assertStringContainsString($filename, $filePath, 'Filename should be part of the resulting file path'); + $this->assertFileExists($tempDir . '/' . $filename . '.pdf', 'File should not actually exist since save does nothing'); + + unlink($filePath); + rmdir($tempDir); + } + + public function testDelegateToAnnex() + { + $files = array_fill(0, 200, 'file'); + $description = 'Test Annex'; + + $this->pdfWriter->delegateToAnnex(2, $files, $description); + + $annexesProperty = $this->reflection->getProperty('annexes'); + $annexesProperty->setAccessible(true); + $annexes = $annexesProperty->getValue($this->pdfWriter); + $this->assertArrayHasKey(1, $annexes, 'The annex should be added.'); + $this->assertSame($files, $annexes[1]['files'], 'Files should match the provided input.'); + $this->assertSame($description, $annexes[1]['description'], 'Description should match the provided input.'); + } + + + + /** + * Next tests are for the writeLine method. This method is used to write text on the current page and it calculates + * the amount of line depending on available width. + * For an A4 page, the width is 595 and the height is 842. If columns are used, the width is divided by the number of columns. + * We also subtract left and right margins from the width (50 points). + * The available width is then 495/5 = 99. A letter is in average 10 points wide. This means that we can fit 9 letters in a line per column. + */ + + public function testWriteLineWithShortText(): void + { + $this->mockedPage + ->expects($this->once()) + ->method('drawText') + ->with($this->equalTo("Short text"), $this->anything(), $this->anything()); + + $this->pdfWriter->currentPage = $this->mockedPage; + + $this->pdfWriter->writeLine("Short text"); + } + + public function testWriteLineWithLongText(): void + { + $longText = str_repeat("This is a long line. ", 10); + + $this->mockedPage + ->expects($this->atLeast(2)) // Expecting split into multiple lines + ->method('drawText') + ->with($this->anything(), $this->anything(), $this->anything()); + + $this->pdfWriter->currentPage = $this->mockedPage; + + + $this->pdfWriter->writeLine($longText); + } + + public function testWriteLineWithFilePath(): void + { + $filePath = "/var/www/html/app/code/Crealoz/EasyAudit/Service/PDFWriter.php"; + + $this->mockedPage + ->expects($this->atLeastOnce()) + ->method('drawText') + ->with($this->anything(), $this->anything(), $this->anything()); + + $this->pdfWriter->currentPage = $this->mockedPage; + + + $this->pdfWriter->writeLine($filePath, true); + } + + protected function tearDown(): void + { + unset($this->pdfWriter); + unset($this->filesystem); + unset($this->sizeCalculation); + unset($this->logger); + unset($this->moduleReader); + unset($this->modulePaths); + unset($this->styleManager); + unset($this->BlockVsVMRatio); + unset($this->mockedPage); + unset($this->mockedStyleManager); + + } + +} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 20224e9..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - ./Test/Unit - - - - - - ./ - - - ./Test - - ./Api - - - \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..db8cfb9 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,49 @@ + + + + ../../../vendor/%COMPOSER_NAME%/Test/Unit + + + ../../../vendor/%COMPOSER_NAME%/Test/Integration + + + + + + ../../../vendor/%COMPOSER_NAME%/ + + + ../../../vendor/%COMPOSER_NAME%/Test + ../../../vendor/%COMPOSER_NAME%/Api + + + + + + + + + + + + + . + testsuite + + + + + + + + + + + + + + + From c018a70100ffc14137edff62f7323d63851ddf9c Mon Sep 17 00:00:00 2001 From: Christophe Ferreboeuf Date: Fri, 10 Jan 2025 16:31:28 +0100 Subject: [PATCH 2/2] Update composer.json Update version number --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b3d180d..6e65cc5 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ "ext-zip": "*" }, "type": "magento2-module", - "version": "0.1.4", + "version": "0.1.5", "license": [ "MIT" ],