Merge branch 'master' into settings-bundle

This commit is contained in:
Jan Böhmer 2025-01-17 22:06:18 +01:00
commit 8750573724
191 changed files with 27745 additions and 12133 deletions

View file

@ -115,6 +115,62 @@ class RedirectControllerTest extends WebTestCase
$this->client->followRedirects(false);
$this->client->request('GET', $input_path);
$this->assertResponseRedirects($redirect_path);
self::assertResponseRedirects($redirect_path);
}
/**
* Test if the user is redirected to the localized version of a page, based on his settings.
* We simulate the situation of a reverse proxy here, by adding a prefix to the path.
*
* @dataProvider urlAddLocaleDataProvider
* @group slow
*/
public function testAddLocaleReverseProxy(?string $user_locale, string $input_path, string $redirect_path): void
{
//Input path remains unchanged, as this is what the server receives from the proxy
//Redirect path must contain the proxy prefix
$redirect_path = 'http://localhost'. '/proxy' . $redirect_path;
/** @var User $user */
$user = $this->userRepo->findOneBy(['name' => 'user']);
//Set user locale
$user->setLanguage($user_locale);
$this->em->flush();
$this->client->followRedirects(false);
$this->client->request('GET', $input_path, [], [], ['HTTP_X_FORWARDED_PREFIX' => '/proxy']);
self::assertResponseRedirects($redirect_path);
}
/**
* Test if the user is redirected to the localized version of a page, based on his settings.
* We simulate the situation of serving Part-DB in a subfolder here.
*
* @dataProvider urlAddLocaleDataProvider
* @group slow
*/
public function testAddLocaleSubfolder(?string $user_locale, string $input_path, string $redirect_path): void
{
//Prefix our path with the proxy prefix
$input_path = '/folder'.$input_path;
//Redirect path is absolute
$redirect_path = 'http://localhost'. '/folder' . $redirect_path;
/** @var User $user */
$user = $this->userRepo->findOneBy(['name' => 'user']);
//Set user locale
$user->setLanguage($user_locale);
$this->em->flush();
$this->client->followRedirects(false);
$this->client->request('GET', $input_path, [], [], [
'SCRIPT_FILENAME' => '/var/www/html/folder/public/index.php',
'PHP_SELF' => '/folder/index.php',
]);
self::assertResponseRedirects($redirect_path);
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Tests\Exceptions;
use App\Exceptions\TwigModeException;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Twig\Error\Error;
class TwigModeExceptionTest extends KernelTestCase
{
private string $projectPath;
public function setUp(): void
{
self::bootKernel();
$this->projectPath = self::getContainer()->getParameter('kernel.project_dir');
}
public function testGetSafeMessage(): void
{
$testException = new Error("Error at : " . $this->projectPath . "/src/dir/path/file.php");
$twigModeException = new TwigModeException($testException);
$this->assertSame("Error at : " . $this->projectPath . "/src/dir/path/file.php", $testException->getMessage());
$this->assertSame("Error at : [Part-DB Root Folder]/src/dir/path/file.php", $twigModeException->getSafeMessage());
}
}

View file

@ -0,0 +1,44 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Tests\Helpers;
use App\Helpers\IPAnonymizer;
use PHPUnit\Framework\TestCase;
class IPAnonymizerTest extends TestCase
{
public function anonymizeDataProvider(): \Generator
{
yield ['127.0.0.0', '127.0.0.23'];
yield ['2001:db8:85a3::', '2001:0db8:85a3:0000:0000:8a2e:0370:7334'];
//RFC 4007 format
yield ['fe80::', 'fe80::1fc4:15d8:78db:2319%enp4s0'];
}
/**
* @dataProvider anonymizeDataProvider
*/
public function testAnonymize(string $expected, string $input): void
{
$this->assertSame($expected, IPAnonymizer::anonymize($input));
}
}

View file

@ -80,7 +80,10 @@ class PartNormalizerTest extends WebTestCase
$this->assertFalse($this->service->supportsDenormalization(new \stdClass(), Part::class));
$this->assertFalse($this->service->supportsDenormalization('string', Part::class));
$this->assertFalse($this->service->supportsDenormalization(['a' => 'b'], \stdClass::class));
$this->assertTrue($this->service->supportsDenormalization(['a' => 'b'], Part::class));
//Only support denormalization, if CSV import
$this->assertFalse($this->service->supportsDenormalization(['a' => 'b'], Part::class));
$this->assertTrue($this->service->supportsDenormalization(['a' => 'b'], Part::class, null, ['partdb_import' => true]));
}
public function testDenormalize(): void

View file

@ -148,7 +148,7 @@ class PartMergerTest extends KernelTestCase
public function testMergeOfPartLots(): void
{
$lot1 = (new PartLot())->setAmount(2)->setNeedsRefill(true);
$lot2 = (new PartLot())->setInstockUnknown(true)->setVendorBarcode('test');
$lot2 = (new PartLot())->setInstockUnknown(true)->setUserBarcode('test');
$lot3 = (new PartLot())->setDescription('lot3')->setAmount(3);
$lot4 = (new PartLot())->setDescription('lot4')->setComment('comment');

View file

@ -39,12 +39,12 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Tests\Services\LabelSystem\Barcodes;
namespace App\Tests\Services\LabelSystem\BarcodeScanner;
use App\Entity\LabelSystem\LabelSupportedElement;
use App\Services\LabelSystem\Barcodes\BarcodeRedirector;
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector;
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
use Doctrine\ORM\EntityNotFoundException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
@ -60,17 +60,17 @@ final class BarcodeRedirectorTest extends KernelTestCase
public static function urlDataProvider(): \Iterator
{
yield [new BarcodeScanResult(LabelSupportedElement::PART, 1, BarcodeSourceType::INTERNAL), '/en/part/1'];
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 1, BarcodeSourceType::INTERNAL), '/en/part/1'];
//Part lot redirects to Part info page (Part lot 1 is associated with part 3)
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL), '/en/part/3'];
yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 1, BarcodeSourceType::INTERNAL), '/en/store_location/1/parts'];
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL), '/en/part/3'];
yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 1, BarcodeSourceType::INTERNAL), '/en/store_location/1/parts'];
}
/**
* @dataProvider urlDataProvider
* @group DB
*/
public function testGetRedirectURL(BarcodeScanResult $scanResult, string $url): void
public function testGetRedirectURL(LocalBarcodeScanResult $scanResult, string $url): void
{
$this->assertSame($url, $this->service->getRedirectURL($scanResult));
}
@ -79,7 +79,7 @@ final class BarcodeRedirectorTest extends KernelTestCase
{
$this->expectException(EntityNotFoundException::class);
//If we encounter an invalid lot, we must throw an exception
$this->service->getRedirectURL(new BarcodeScanResult(LabelSupportedElement::PART_LOT,
$this->service->getRedirectURL(new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT,
12_345_678, BarcodeSourceType::INTERNAL));
}
}

View file

@ -39,13 +39,14 @@ declare(strict_types=1);
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Tests\Services\LabelSystem\Barcodes;
namespace App\Tests\Services\LabelSystem\BarcodeScanner;
use App\Entity\LabelSystem\LabelSupportedElement;
use App\Services\LabelSystem\Barcodes\BarcodeScanHelper;
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
use Com\Tecnick\Barcode\Barcode;
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper;
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultInterface;
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
use App\Services\LabelSystem\BarcodeScanner\EIGP114BarcodeScanResult;
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class BarcodeScanHelperTest extends WebTestCase
@ -61,56 +62,67 @@ class BarcodeScanHelperTest extends WebTestCase
public static function dataProvider(): \Iterator
{
//QR URL content:
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 1, BarcodeSourceType::INTERNAL),
'https://localhost:8000/scan/lot/1'];
yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
'https://localhost:8000/scan/part/123'];
yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 4, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 4, BarcodeSourceType::INTERNAL),
'http://foo.bar/part-db/scan/location/4'];
//Current Code39 format:
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
'L0010'];
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 123, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 123, BarcodeSourceType::INTERNAL),
'L0123'];
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 123456, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 123456, BarcodeSourceType::INTERNAL),
'L123456'];
yield [new BarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::INTERNAL),
'P0002'];
//Development phase Code39 barcodes:
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
'L-000010'];
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 10, BarcodeSourceType::INTERNAL),
'Lß000010'];
yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
'P-000123'];
yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 123, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 123, BarcodeSourceType::INTERNAL),
'S-000123'];
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 12_345_678, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 12_345_678, BarcodeSourceType::INTERNAL),
'L-12345678'];
//Legacy storelocation format
yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 336, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 336, BarcodeSourceType::INTERNAL),
'$L00336'];
yield [new BarcodeScanResult(LabelSupportedElement::STORELOCATION, 12_345_678, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::STORELOCATION, 12_345_678, BarcodeSourceType::INTERNAL),
'$L12345678'];
//Legacy Part format
yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
'0000123'];
yield [new BarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 123, BarcodeSourceType::INTERNAL),
'00001236'];
yield [new BarcodeScanResult(LabelSupportedElement::PART, 1_234_567, BarcodeSourceType::INTERNAL),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 1_234_567, BarcodeSourceType::INTERNAL),
'12345678'];
//Test IPN barcode
yield [new BarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::IPN),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART, 2, BarcodeSourceType::IPN),
'IPN123'];
//Test vendor barcode
yield [new BarcodeScanResult(LabelSupportedElement::PART_LOT, 2,BarcodeSourceType::VENDOR),
yield [new LocalBarcodeScanResult(LabelSupportedElement::PART_LOT, 2,BarcodeSourceType::USER_DEFINED),
'lot2_vendor_barcode'];
$eigp114Result = new EIGP114BarcodeScanResult([
'P' => '596-777A1-ND',
'1P' => 'XAF4444',
'Q' => '3',
'10D' => '1452',
'1T' => 'BF1103',
'4L' => 'US',
]);
yield [$eigp114Result, "[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04"];
}
public static function invalidDataProvider(): \Iterator
@ -131,7 +143,7 @@ class BarcodeScanHelperTest extends WebTestCase
/**
* @dataProvider dataProvider
*/
public function testNormalizeBarcodeContent(BarcodeScanResult $expected, string $input): void
public function testNormalizeBarcodeContent(BarcodeScanResultInterface $expected, string $input): void
{
$this->assertEquals($expected, $this->service->scanBarcodeContent($input));
}

View file

@ -0,0 +1,154 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Tests\Services\LabelSystem\BarcodeScanner;
use App\Services\LabelSystem\BarcodeScanner\EIGP114BarcodeScanResult;
use PHPUnit\Framework\TestCase;
class EIGP114BarcodeScanResultTest extends TestCase
{
public function testGuessBarcodeVendor(): void
{
//Generic barcode:
$barcode = new EIGP114BarcodeScanResult([
'P' => '596-777A1-ND',
'1P' => 'XAF4444',
'Q' => '3',
'10D' => '1452',
'1T' => 'BF1103',
'4L' => 'US',
]);
$this->assertNull($barcode->guessBarcodeVendor());
//Digikey barcode:
$barcode = new EIGP114BarcodeScanResult([
'P' => '596-777A1-ND',
'1P' => 'XAF4444',
'Q' => '3',
'10D' => '1452',
'1T' => 'BF1103',
'4L' => 'US',
'13Z' => 'Digi-Key',
]);
$this->assertEquals('digikey', $barcode->guessBarcodeVendor());
//Mouser barcode:
$barcode = new EIGP114BarcodeScanResult([
'P' => '596-777A1-ND',
'1P' => 'XAF4444',
'Q' => '3',
'10D' => '1452',
'1T' => 'BF1103',
'4L' => 'US',
'1V' => 'Mouser',
]);
$this->assertEquals('mouser', $barcode->guessBarcodeVendor());
//Farnell barcode:
$barcode = new EIGP114BarcodeScanResult([
'P' => '596-777A1-ND',
'1P' => 'XAF4444',
'Q' => '3',
'10D' => '1452',
'1T' => 'BF1103',
'4L' => 'US',
'3P' => 'Farnell',
]);
$this->assertEquals('element14', $barcode->guessBarcodeVendor());
}
public function testIsFormat06Code(): void
{
$this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code(''));
$this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code('test'));
$this->assertFalse(EIGP114BarcodeScanResult::isFormat06Code('12232435ew4rf'));
//Valid code (with trailer)
$this->assertTrue(EIGP114BarcodeScanResult::isFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04"));
//Valid code (digikey, without trailer)
$this->assertTrue(EIGP114BarcodeScanResult::isFormat06Code("[)>\x1e06\x1dPQ1045-ND\x1d1P364019-01\x1d30PQ1045-ND\x1dK12432 TRAVIS FOSS P\x1d1K85732873\x1d10K103332956\x1d9D231013\x1d1TQJ13P\x1d11K1\x1d4LTW\x1dQ3\x1d11ZPICK\x1d12Z7360988\x1d13Z999999\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000000000000000000000"));
}
public function testParseFormat06CodeInvalid(): void
{
$this->expectException(\InvalidArgumentException::class);
EIGP114BarcodeScanResult::parseFormat06Code('');
}
public function testParseFormat06Code(): void
{
$barcode = EIGP114BarcodeScanResult::parseFormat06Code("[)>\x1E06\x1DP596-777A1-ND\x1D1PXAF4444\x1DQ3\x1D10D1452\x1D1TBF1103\x1D4LUS\x1E\x04");
$this->assertEquals([
'P' => '596-777A1-ND',
'1P' => 'XAF4444',
'Q' => '3',
'10D' => '1452',
'1T' => 'BF1103',
'4L' => 'US',
], $barcode->data);
}
public function testDataParsing(): void
{
$barcode = new EIGP114BarcodeScanResult([
'P' => '596-777A1-ND',
'1P' => 'XAF4444',
'Q' => '3',
'10D' => '1452',
'1T' => 'BF1103',
'4L' => 'US',
]);
$this->assertEquals('596-777A1-ND', $barcode->customerPartNumber);
$this->assertEquals('XAF4444', $barcode->supplierPartNumber);
$this->assertEquals(3, $barcode->quantity);
$this->assertEquals('1452', $barcode->alternativeDateCode);
$this->assertEquals('BF1103', $barcode->lotCode);
$this->assertEquals('US', $barcode->countryOfOrigin);
}
public function testDigikeyParsing(): void
{
$barcode = EIGP114BarcodeScanResult::parseFormat06Code("[)>\x1e06\x1dPQ1045-ND\x1d1P364019-01\x1d30PQ1045-ND\x1dK12432 TRAVIS FOSS P\x1d1K85732873\x1d10K103332956\x1d9D231013\x1d1TQJ13P\x1d11K1\x1d4LTW\x1dQ3\x1d11ZPICK\x1d12Z7360988\x1d13Z999999\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
$this->assertEquals('digikey', $barcode->guessBarcodeVendor());
$this->assertEquals('Q1045-ND', $barcode->customerPartNumber);
$this->assertEquals('364019-01', $barcode->supplierPartNumber);
$this->assertEquals(3, $barcode->quantity);
$this->assertEquals('231013', $barcode->dateCode);
$this->assertEquals('QJ13P', $barcode->lotCode);
$this->assertEquals('TW', $barcode->countryOfOrigin);
$this->assertEquals('Q1045-ND', $barcode->digikeyPartNumber);
$this->assertEquals('85732873', $barcode->digikeySalesOrderNumber);
$this->assertEquals('103332956', $barcode->digikeyInvoiceNumber);
$this->assertEquals('PICK', $barcode->digikeyLabelType);
$this->assertEquals('7360988', $barcode->digikeyPartID);
$this->assertEquals('999999', $barcode->digikeyNA);
$this->assertEquals('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000', $barcode->digikeyPadding);
}
}

View file

@ -80,10 +80,10 @@ class SandboxedTwigFactoryTest extends WebTestCase
'];
yield ['
{{ location.isRoot}} {{ location.isChildOf(location) }} {{ location.comment }} {{ location.level }}
{{ location.fullPath }} {% set arr = location.pathArray %} {% set child = location.children %} {{location.childrenNotSelectable}}
{{ location.fullPath }} {% set arr = location.pathArray %} {% set child = location.children %} {{location.notSelectable}}
'];
yield ['
{{ part.reviewNeeded }} {{ part.tags }} {{ part.mass }}
{{ part.needsReview }} {{ part.tags }} {{ part.mass }}
'];
yield ['
{{ entity_type(part) is object }}