diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index 04d8206b..0d05c248 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -1,5 +1,8 @@ . */ - -declare(strict_types=1); - namespace App\DataTables; -use App\DataTables\Adapters\TwoStepORMAdapter; use App\DataTables\Column\EntityColumn; use App\DataTables\Column\EnumColumn; use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\MarkdownColumn; use App\DataTables\Helpers\PartDataTableHelper; -use App\Doctrine\Helpers\FieldHelper; +use App\Entity\Attachments\Attachment; use App\Entity\Parts\Part; use App\Entity\Parts\ManufacturingStatus; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Services\ElementTypeNameGenerator; use App\Services\EntityURLGenerator; use App\Services\Formatters\AmountFormatter; -use Doctrine\ORM\AbstractQuery; -use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider; +use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter; use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTableTypeInterface; @@ -46,12 +44,9 @@ use Symfony\Contracts\Translation\TranslatorInterface; class ProjectBomEntriesDataTable implements DataTableTypeInterface { - public function __construct( - protected EntityURLGenerator $entityURLGenerator, - protected TranslatorInterface $translator, - protected AmountFormatter $amountFormatter, - protected PartDataTableHelper $partDataTableHelper - ) { + public function __construct(protected TranslatorInterface $translator, protected PartDataTableHelper $partDataTableHelper, + protected EntityURLGenerator $entityURLGenerator, protected AmountFormatter $amountFormatter) + { } @@ -67,7 +62,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface return ''; } return $this->partDataTableHelper->renderPicture($context->getPart()); - } + }, ]) ->add('id', TextColumn::class, [ @@ -138,24 +133,23 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('category', EntityColumn::class, [ 'label' => $this->translator->trans('part.table.category'), 'property' => 'part.category', - 'orderField' => 'NATSORT(category.name)' + 'orderField' => 'NATSORT(category.name)', ]) ->add('footprint', EntityColumn::class, [ 'property' => 'part.footprint', 'label' => $this->translator->trans('part.table.footprint'), - 'orderField' => 'NATSORT(footprint.name)' + 'orderField' => 'NATSORT(footprint.name)', ]) ->add('manufacturer', EntityColumn::class, [ 'property' => 'part.manufacturer', 'label' => $this->translator->trans('part.table.manufacturer'), - 'orderField' => 'NATSORT(manufacturer.name)' + 'orderField' => 'NATSORT(manufacturer.name)', ]) ->add('manufacturing_status', EnumColumn::class, [ 'label' => $this->translator->trans('part.table.manufacturingStatus'), - 'data' => static fn(ProjectBOMEntry $context): ?ManufacturingStatus => $context->getPart()?->getManufacturingStatus(), - 'orderField' => 'part.manufacturing_status', + 'data' => static fn(ProjectBOMEntry $context): ?ManufacturingStatus => $context->getPart()?->getManufacturingStatus(), 'class' => ManufacturingStatus::class, 'render' => function (?ManufacturingStatus $status, ProjectBOMEntry $context): string { if ($status === null) { @@ -189,10 +183,8 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface return ''; } ]) - ->add('storelocation', TextColumn::class, [ - 'label' => $this->translator->trans('part.table.storeLocations'), - //We need to use a aggregate function to get the first store location, as we have a one-to-many relation - 'orderField' => 'NATSORT(MIN(_storelocations.name))', + ->add('storageLocations', TextColumn::class, [ + 'label' => 'part.table.storeLocations', 'visible' => false, 'render' => function ($value, ProjectBOMEntry $context) { if ($context->getPart() !== null) { @@ -215,13 +207,11 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface $dataTable->addOrderBy('name', DataTable::SORT_ASCENDING); - $dataTable->createAdapter(TwoStepORMAdapter::class, [ - 'entity' => ProjectBOMEntry::class, - 'hydrate' => AbstractQuery::HYDRATE_OBJECT, - 'filter_query' => function (QueryBuilder $builder) use ($options): void { - $this->getFilterQuery($builder, $options); + $dataTable->createAdapter(ORMAdapter::class, [ + 'entity' => Attachment::class, + 'query' => function (QueryBuilder $builder) use ($options): void { + $this->getQuery($builder, $options); }, - 'detail_query' => $this->getDetailQuery(...), 'criteria' => [ function (QueryBuilder $builder) use ($options): void { $this->buildCriteria($builder, $options); @@ -231,71 +221,20 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ]); } - private function getFilterQuery(QueryBuilder $builder, array $options): void + private function getQuery(QueryBuilder $builder, array $options): void { - $builder - ->select('bom_entry.id') + $builder->select('bom_entry') + ->addSelect('part') ->from(ProjectBOMEntry::class, 'bom_entry') ->leftJoin('bom_entry.part', 'part') ->leftJoin('part.category', 'category') - ->leftJoin('part.partLots', '_partLots') - ->leftJoin('_partLots.storage_location', '_storelocations') ->leftJoin('part.footprint', 'footprint') ->leftJoin('part.manufacturer', 'manufacturer') - ->leftJoin('part.partCustomState', 'partCustomState') ->where('bom_entry.project = :project') ->setParameter('project', $options['project']) - ->addGroupBy('bom_entry') - ->addGroupBy('part') - ->addGroupBy('category') - ->addGroupBy('footprint') - ->addGroupBy('manufacturer') - ->addGroupBy('partCustomState') ; } - private function getDetailQuery(QueryBuilder $builder, array $filter_results): void - { - $ids = array_map(static fn (array $row) => $row['id'], $filter_results); - if ($ids === []) { - $ids = [-1]; - } - - $builder - ->select('bom_entry') - ->addSelect('part') - ->addSelect('category') - ->addSelect('partLots') - ->addSelect('storelocations') - ->addSelect('footprint') - ->addSelect('manufacturer') - ->addSelect('partCustomState') - ->from(ProjectBOMEntry::class, 'bom_entry') - ->leftJoin('bom_entry.part', 'part') - ->leftJoin('part.category', 'category') - ->leftJoin('part.partLots', 'partLots') - ->leftJoin('partLots.storage_location', 'storelocations') - ->leftJoin('part.footprint', 'footprint') - ->leftJoin('part.manufacturer', 'manufacturer') - ->leftJoin('part.partCustomState', 'partCustomState') - ->where('bom_entry.id IN (:ids)') - ->setParameter('ids', $ids) - ->addGroupBy('bom_entry') - ->addGroupBy('part') - ->addGroupBy('partLots') - ->addGroupBy('category') - ->addGroupBy('storelocations') - ->addGroupBy('footprint') - ->addGroupBy('manufacturer') - ->addGroupBy('partCustomState') - - ->setHint(Query::HINT_READ_ONLY, true) - ->setHint(Query::HINT_FORCE_PARTIAL_LOAD, false) - ; - - FieldHelper::addOrderByFieldParam($builder, 'bom_entry.id', 'ids'); - } - private function buildCriteria(QueryBuilder $builder, array $options): void { diff --git a/src/Services/InfoProviderSystem/Providers/TMEProvider.php b/src/Services/InfoProviderSystem/Providers/TMEProvider.php index 93222517..938bc7b3 100644 --- a/src/Services/InfoProviderSystem/Providers/TMEProvider.php +++ b/src/Services/InfoProviderSystem/Providers/TMEProvider.php @@ -280,13 +280,9 @@ class TMEProvider implements InfoProviderInterface, URLHandlerInfoProviderInterf { //If a URL starts with // we assume that it is a relative URL and we add the protocol if (str_starts_with($url, '//')) { - $url = 'https:' . $url; + return 'https:' . $url; } - //Encode bare % signs that are not already part of a valid percent-encoded sequence - //Fixes part numbers with % in them e.g. SMD0603-5K1-1% - $url = preg_replace('/%(?![0-9A-Fa-f]{2})/', '%25', $url); - return $url; } diff --git a/tests/Services/InfoProviderSystem/Providers/TMEProviderTest.php b/tests/Services/InfoProviderSystem/Providers/TMEProviderTest.php deleted file mode 100644 index 20fba523..00000000 --- a/tests/Services/InfoProviderSystem/Providers/TMEProviderTest.php +++ /dev/null @@ -1,391 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Tests\Services\InfoProviderSystem\Providers; - -use App\Entity\Parts\ManufacturingStatus; -use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; -use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; -use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; -use App\Services\InfoProviderSystem\Providers\ProviderCapabilities; -use App\Services\InfoProviderSystem\Providers\TMEClient; -use App\Services\InfoProviderSystem\Providers\TMEProvider; -use App\Settings\InfoProviderSystem\TMESettings; -use App\Tests\SettingsTestHelper; -use PHPUnit\Framework\TestCase; -use Symfony\Component\HttpClient\MockHttpClient; -use Symfony\Component\HttpClient\Response\MockResponse; - -final class TMEProviderTest extends TestCase -{ - private TMESettings $settings; - private TMEProvider $provider; - private MockHttpClient $httpClient; - - protected function setUp(): void - { - $this->httpClient = new MockHttpClient(); - $this->settings = SettingsTestHelper::createSettingsDummy(TMESettings::class); - // Use a short (anonymous-style) token so grossPrices is read from settings - $this->settings->apiToken = 'test_token_000000000000000000000000000000000000000'; - $this->settings->apiSecret = 'test_secret'; - $this->settings->currency = 'EUR'; - $this->settings->language = 'en'; - $this->settings->country = 'DE'; - $this->settings->grossPrices = false; - $this->provider = new TMEProvider(new TMEClient($this->httpClient, $this->settings), $this->settings); - } - - // --- Mock response helpers --- - // Only fields actually read by TMEProvider are included. - - private function mockProductList(array $products): MockResponse - { - return new MockResponse(json_encode([ - 'Status' => 'OK', - 'Data' => ['ProductList' => $products], - ])); - } - - private function mockFilesList(array $products): MockResponse - { - return new MockResponse(json_encode([ - 'Status' => 'OK', - 'Data' => ['ProductList' => $products], - ])); - } - - private function mockParametersList(array $products): MockResponse - { - return new MockResponse(json_encode([ - 'Status' => 'OK', - 'Data' => ['ProductList' => $products], - ])); - } - - private function mockPrices(string $currency, string $priceType, array $products): MockResponse - { - return new MockResponse(json_encode([ - 'Status' => 'OK', - 'Data' => [ - 'Currency' => $currency, - 'PriceType' => $priceType, - 'ProductList' => $products, - ], - ])); - } - - // --- Mock data --- - - private function smd0603Products(): MockResponse - { - return $this->mockProductList([[ - 'Symbol' => 'SMD0603-5K1-1%', - 'OriginalSymbol' => '0603SAF5101T5E', - 'Producer' => 'ROYALOHM', - 'Description' => 'Resistor: thick film; SMD; 0603; 5.1kΩ; 0.1W; ±1%; 50V; -55÷155°C', - 'Category' => 'SMD resistors', - 'Photo' => '//ce8dc832c.cloudimg.io/v7/_cdn_/E9/C2/B0/00/0/732318_1.jpg', - 'ProductStatusList' => [], - 'ProductInformationPage' => '//www.tme.eu/en/details/smd0603-5k1-1%/smd-resistors/royalohm/0603saf5101t5e/', - 'Weight' => 0.021, - 'WeightUnit' => 'g', - ]]); - } - - private function smd0603Files(): MockResponse - { - return $this->mockFilesList([[ - 'Symbol' => 'SMD0603-5K1-1%', - 'Files' => [ - 'AdditionalPhotoList' => [], - 'DocumentList' => [ - ['DocumentUrl' => '//www.tme.eu/Document/b315665a56acbc42df513c99b390ad98/ROYALOHM-THICKFILM.pdf'], - ['DocumentUrl' => '//www.tme.eu/Document/c283990e907c122bb808207d1578ac7f/POWER_RATING-DTE.pdf'], - ], - ], - ]]); - } - - private function smd0603Parameters(): MockResponse - { - return $this->mockParametersList([[ - 'Symbol' => 'SMD0603-5K1-1%', - 'ParameterList' => [ - ['ParameterId' => 34, 'ParameterName' => 'Type of resistor', 'ParameterValue' => 'thick film'], - ['ParameterId' => 35, 'ParameterName' => 'Case - mm', 'ParameterValue' => '1608'], - ['ParameterId' => 38, 'ParameterName' => 'Resistance', 'ParameterValue' => '5.1kΩ'], - ['ParameterId' => 39, 'ParameterName' => 'Tolerance', 'ParameterValue' => '±1%'], - ['ParameterId' => 120, 'ParameterName' => 'Operating voltage', 'ParameterValue' => '50V'], - ], - ]]); - } - - private function smd0603Prices(): MockResponse - { - return $this->mockPrices('EUR', 'NET', [[ - 'Symbol' => 'SMD0603-5K1-1%', - 'PriceList' => [ - ['Amount' => 100, 'PriceValue' => 0.01077], - ['Amount' => 1000, 'PriceValue' => 0.00291], - ['Amount' => 5000, 'PriceValue' => 0.00150], - ], - ]]); - } - - private function etqp3mProducts(): MockResponse - { - return $this->mockProductList([[ - 'Symbol' => 'ETQP3M6R8KVP', - 'OriginalSymbol' => 'ETQP3M6R8KVP', - 'Producer' => 'PANASONIC', - 'Description' => 'Inductor: wire; SMD; 6.8uH; 2.9A; R: 65.7mΩ; ±20%; ETQP3M; 5.5x5x3mm', - 'Category' => 'Inductors', - 'Photo' => '//ce8dc832c.cloudimg.io/v7/_cdn_/9E/27/A0/00/0/684777_1.jpg', - 'ProductStatusList' => [], - 'ProductInformationPage' => '//www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/', - 'Weight' => 0.44, - 'WeightUnit' => 'g', - ]]); - } - - private function etqp3mFiles(): MockResponse - { - return $this->mockFilesList([[ - 'Symbol' => 'ETQP3M6R8KVP', - 'Files' => [ - 'AdditionalPhotoList' => [], - 'DocumentList' => [ - ['DocumentUrl' => '//www.tme.eu/Document/50a845881f09d8a2248350946e11df38/AGL0000C63.pdf'], - ['DocumentUrl' => '//www.tme.eu/Document/8480690a42fa577214e35e33d3fc8d77/ETQP3M100KVN-LNK.txt'], - ], - ], - ]]); - } - - private function etqp3mParameters(): MockResponse - { - return $this->mockParametersList([[ - 'Symbol' => 'ETQP3M6R8KVP', - 'ParameterList' => [ - ['ParameterId' => 566, 'ParameterName' => 'Inductance', 'ParameterValue' => '6.8µH'], - ['ParameterId' => 370, 'ParameterName' => 'Operating current', 'ParameterValue' => '2.9A'], - ['ParameterId' => 39, 'ParameterName' => 'Tolerance', 'ParameterValue' => '±20%'], - ], - ]]); - } - - private function etqp3mPrices(): MockResponse - { - return $this->mockPrices('EUR', 'NET', [[ - 'Symbol' => 'ETQP3M6R8KVP', - 'PriceList' => [ - ['Amount' => 1, 'PriceValue' => 0.589], - ['Amount' => 5, 'PriceValue' => 0.429], - ['Amount' => 10, 'PriceValue' => 0.399], - ], - ]]); - } - - // --- Tests --- - - public function testGetProviderInfo(): void - { - $info = $this->provider->getProviderInfo(); - - $this->assertIsArray($info); - $this->assertArrayHasKey('name', $info); - $this->assertArrayHasKey('description', $info); - $this->assertArrayHasKey('url', $info); - $this->assertEquals('TME', $info['name']); - $this->assertEquals('https://tme.eu/', $info['url']); - } - - public function testGetProviderKey(): void - { - $this->assertSame('tme', $this->provider->getProviderKey()); - } - - public function testIsActiveWithCredentials(): void - { - $this->assertTrue($this->provider->isActive()); - } - - public function testIsActiveWithoutCredentials(): void - { - $this->settings->apiToken = null; - $provider = new TMEProvider(new TMEClient($this->httpClient, $this->settings), $this->settings); - $this->assertFalse($provider->isActive()); - } - - public function testGetCapabilities(): void - { - $capabilities = $this->provider->getCapabilities(); - - $this->assertIsArray($capabilities); - $this->assertContains(ProviderCapabilities::BASIC, $capabilities); - $this->assertContains(ProviderCapabilities::PICTURE, $capabilities); - $this->assertContains(ProviderCapabilities::DATASHEET, $capabilities); - $this->assertContains(ProviderCapabilities::PRICE, $capabilities); - $this->assertContains(ProviderCapabilities::FOOTPRINT, $capabilities); - } - - public function testGetHandledDomains(): void - { - $this->assertContains('tme.eu', $this->provider->getHandledDomains()); - } - - public function testGetIDFromURL(): void - { - $this->assertSame('fi321_se', $this->provider->getIDFromURL('https://www.tme.eu/de/details/fi321_se/kuhler/alutronic/')); - $this->assertSame('smd0603-5k1-1%25', $this->provider->getIDFromURL('https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/')); - $this->assertNull($this->provider->getIDFromURL('https://www.tme.eu/en/')); - } - - public function testSearchByKeyword(): void - { - $this->httpClient->setResponseFactory([$this->smd0603Products()]); - - $results = $this->provider->searchByKeyword('SMD0603-5K1-1%'); - - $this->assertIsArray($results); - $this->assertCount(1, $results); - $this->assertInstanceOf(SearchResultDTO::class, $results[0]); - $this->assertSame('SMD0603-5K1-1%', $results[0]->provider_id); - $this->assertSame('0603SAF5101T5E', $results[0]->name); - $this->assertSame('ROYALOHM', $results[0]->manufacturer); - $this->assertSame('SMD resistors', $results[0]->category); - $this->assertSame(ManufacturingStatus::ACTIVE, $results[0]->manufacturing_status); - $this->assertSame( - 'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/', - $results[0]->provider_url - ); - } - - public function testGetDetailsWithPercentInPartNumber(): void - { - $this->httpClient->setResponseFactory([ - $this->smd0603Products(), - $this->smd0603Files(), - $this->smd0603Parameters(), - $this->smd0603Prices(), - ]); - - $result = $this->provider->getDetails('SMD0603-5K1-1%'); - - $this->assertInstanceOf(PartDetailDTO::class, $result); - $this->assertSame('SMD0603-5K1-1%', $result->provider_id); - $this->assertSame('0603SAF5101T5E', $result->name); - $this->assertSame('Resistor: thick film; SMD; 0603; 5.1kΩ; 0.1W; ±1%; 50V; -55÷155°C', $result->description); - $this->assertSame('ROYALOHM', $result->manufacturer); - $this->assertSame('0603SAF5101T5E', $result->mpn); - $this->assertSame('SMD resistors', $result->category); - $this->assertSame(ManufacturingStatus::ACTIVE, $result->manufacturing_status); - $this->assertSame(0.021, $result->mass); - $this->assertSame('1608', $result->footprint); - $this->assertSame( - 'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/', - $result->provider_url - ); - - $this->assertCount(2, $result->datasheets); - $this->assertSame('https://www.tme.eu/Document/b315665a56acbc42df513c99b390ad98/ROYALOHM-THICKFILM.pdf', $result->datasheets[0]->url); - $this->assertCount(0, $result->images); - - $this->assertCount(1, $result->vendor_infos); - $vendorInfo = $result->vendor_infos[0]; - $this->assertInstanceOf(PurchaseInfoDTO::class, $vendorInfo); - $this->assertSame('TME', $vendorInfo->distributor_name); - $this->assertSame('SMD0603-5K1-1%', $vendorInfo->order_number); - $this->assertSame( - 'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/', - $vendorInfo->product_url - ); - $this->assertCount(3, $vendorInfo->prices); - $this->assertSame(100.0, $vendorInfo->prices[0]->minimum_discount_amount); - $this->assertSame('0.01077', $vendorInfo->prices[0]->price); - $this->assertSame('EUR', $vendorInfo->prices[0]->currency_iso_code); - $this->assertFalse($vendorInfo->prices[0]->includes_tax); - - $this->assertCount(5, $result->parameters); - } - - public function testGetDetailsForEtqp3m6r8kvp(): void - { - $this->httpClient->setResponseFactory([ - $this->etqp3mProducts(), - $this->etqp3mFiles(), - $this->etqp3mParameters(), - $this->etqp3mPrices(), - ]); - - $result = $this->provider->getDetails('ETQP3M6R8KVP'); - - $this->assertInstanceOf(PartDetailDTO::class, $result); - $this->assertSame('ETQP3M6R8KVP', $result->provider_id); - $this->assertSame('ETQP3M6R8KVP', $result->name); - $this->assertSame('Inductor: wire; SMD; 6.8uH; 2.9A; R: 65.7mΩ; ±20%; ETQP3M; 5.5x5x3mm', $result->description); - $this->assertSame('PANASONIC', $result->manufacturer); - $this->assertSame('ETQP3M6R8KVP', $result->mpn); - $this->assertSame('Inductors', $result->category); - $this->assertSame(ManufacturingStatus::ACTIVE, $result->manufacturing_status); - $this->assertSame(0.44, $result->mass); - $this->assertNull($result->footprint); - $this->assertSame('https://www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/', $result->provider_url); - - $this->assertCount(2, $result->datasheets); - $this->assertSame('https://www.tme.eu/Document/50a845881f09d8a2248350946e11df38/AGL0000C63.pdf', $result->datasheets[0]->url); - $this->assertCount(0, $result->images); - - $this->assertCount(1, $result->vendor_infos); - $vendorInfo = $result->vendor_infos[0]; - $this->assertSame('TME', $vendorInfo->distributor_name); - $this->assertSame('ETQP3M6R8KVP', $vendorInfo->order_number); - $this->assertSame('https://www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/', $vendorInfo->product_url); - $this->assertCount(3, $vendorInfo->prices); - $this->assertSame(1.0, $vendorInfo->prices[0]->minimum_discount_amount); - $this->assertSame('0.589', $vendorInfo->prices[0]->price); - $this->assertSame('EUR', $vendorInfo->prices[0]->currency_iso_code); - $this->assertFalse($vendorInfo->prices[0]->includes_tax); - - $this->assertCount(3, $result->parameters); - } - - public function testNormalizeURLEncodesBarePctSign(): void - { - $method = (new \ReflectionClass($this->provider))->getMethod('normalizeURL'); - - $this->assertSame( - 'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/', - $method->invoke($this->provider, '//www.tme.eu/en/details/smd0603-5k1-1%/smd-resistors/royalohm/0603saf5101t5e/') - ); - $this->assertSame( - 'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/', - $method->invoke($this->provider, '//www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/') - ); - $this->assertSame( - 'https://www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/', - $method->invoke($this->provider, '//www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/') - ); - $this->assertSame('https://example.com/path', $method->invoke($this->provider, 'https://example.com/path')); - } -}