2026-04-06 14:42:54 +02:00
|
|
|
|
<?php
|
|
|
|
|
|
/*
|
|
|
|
|
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
|
|
|
|
|
*
|
|
|
|
|
|
* Copyright (C) 2019 - 2023 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/>.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Tests\Services\InfoProviderSystem\Providers;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Entity\Parts\ManufacturingStatus;
|
|
|
|
|
|
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
2026-07-26 13:55:27 +02:00
|
|
|
|
use App\Services\InfoProviderSystem\DTOs\ProviderInfoDTO;
|
2026-04-06 14:42:54 +02:00
|
|
|
|
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;
|
2026-08-02 18:13:44 +02:00
|
|
|
|
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
2026-04-06 14:42:54 +02:00
|
|
|
|
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);
|
2026-06-27 19:01:04 +02:00
|
|
|
|
$this->settings->apiToken = 'test_token_000000000000000000000000000000000000000000000000';
|
|
|
|
|
|
$this->settings->apiSecret = 'test_secret_00000000';
|
2026-04-06 14:42:54 +02:00
|
|
|
|
$this->settings->currency = 'EUR';
|
|
|
|
|
|
$this->settings->language = 'en';
|
|
|
|
|
|
$this->settings->country = 'DE';
|
2026-08-02 18:13:44 +02:00
|
|
|
|
$this->provider = new TMEProvider(new TMEClient($this->httpClient, $this->settings, new ArrayAdapter()), $this->settings, $this->httpClient);
|
2026-04-06 14:42:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Mock response helpers ---
|
|
|
|
|
|
|
2026-06-27 19:01:04 +02:00
|
|
|
|
/** OAuth2 token response – always the first reply in a sequence that triggers an API call */
|
|
|
|
|
|
private function mockTokenResponse(): MockResponse
|
2026-04-06 14:42:54 +02:00
|
|
|
|
{
|
|
|
|
|
|
return new MockResponse(json_encode([
|
2026-06-27 19:01:04 +02:00
|
|
|
|
'access_token' => 'mock_access_token',
|
|
|
|
|
|
'token_type' => 'Bearer',
|
|
|
|
|
|
'expires_in' => 300,
|
|
|
|
|
|
'refresh_token' => 'mock_refresh_token',
|
|
|
|
|
|
]));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function mockSearchResults(array $products): MockResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
return new MockResponse(json_encode([
|
|
|
|
|
|
'status' => 'OK',
|
|
|
|
|
|
'data' => [
|
|
|
|
|
|
'products' => ['elements' => $products],
|
|
|
|
|
|
'parameters' => [],
|
|
|
|
|
|
'counters' => [],
|
|
|
|
|
|
],
|
|
|
|
|
|
]));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function mockProductsList(array $products): MockResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
return new MockResponse(json_encode([
|
|
|
|
|
|
'status' => 'OK',
|
|
|
|
|
|
'data' => ['elements' => $products],
|
2026-04-06 14:42:54 +02:00
|
|
|
|
]));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function mockFilesList(array $products): MockResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
return new MockResponse(json_encode([
|
2026-06-27 19:01:04 +02:00
|
|
|
|
'status' => 'OK',
|
|
|
|
|
|
'data' => ['elements' => $products],
|
2026-04-06 14:42:54 +02:00
|
|
|
|
]));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function mockParametersList(array $products): MockResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
return new MockResponse(json_encode([
|
2026-06-27 19:01:04 +02:00
|
|
|
|
'status' => 'OK',
|
|
|
|
|
|
'data' => ['elements' => $products],
|
2026-04-06 14:42:54 +02:00
|
|
|
|
]));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-27 19:01:04 +02:00
|
|
|
|
private function mockPricesData(string $currency, string $priceType, array $products): MockResponse
|
2026-04-06 14:42:54 +02:00
|
|
|
|
{
|
|
|
|
|
|
return new MockResponse(json_encode([
|
2026-06-27 19:01:04 +02:00
|
|
|
|
'status' => 'OK',
|
|
|
|
|
|
'data' => [
|
|
|
|
|
|
'currency' => $currency,
|
|
|
|
|
|
'price_type' => $priceType,
|
|
|
|
|
|
'elements' => $products,
|
2026-04-06 14:42:54 +02:00
|
|
|
|
],
|
|
|
|
|
|
]));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-27 19:01:04 +02:00
|
|
|
|
// --- Mock data (v2 field names) ---
|
|
|
|
|
|
|
|
|
|
|
|
private function smd0603Product(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return [
|
|
|
|
|
|
'symbol' => 'SMD0603-5K1-1%',
|
|
|
|
|
|
'original_symbol' => '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',
|
|
|
|
|
|
'statuses' => [],
|
|
|
|
|
|
'product_information_page' => '//www.tme.eu/en/details/smd0603-5k1-1%/smd-resistors/royalohm/0603saf5101t5e/',
|
|
|
|
|
|
'weight' => 0.021,
|
|
|
|
|
|
'weight_unit' => 'g',
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function smd0603SearchResults(): MockResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->mockSearchResults([$this->smd0603Product()]);
|
|
|
|
|
|
}
|
2026-04-06 14:42:54 +02:00
|
|
|
|
|
|
|
|
|
|
private function smd0603Products(): MockResponse
|
|
|
|
|
|
{
|
2026-06-27 19:01:04 +02:00
|
|
|
|
return $this->mockProductsList([$this->smd0603Product()]);
|
2026-04-06 14:42:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function smd0603Files(): MockResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->mockFilesList([[
|
2026-06-27 19:01:04 +02:00
|
|
|
|
'symbol' => 'SMD0603-5K1-1%',
|
|
|
|
|
|
'photos' => [],
|
|
|
|
|
|
'documents' => [
|
|
|
|
|
|
['url' => '//www.tme.eu/Document/b315665a56acbc42df513c99b390ad98/ROYALOHM-THICKFILM.pdf'],
|
|
|
|
|
|
['url' => '//www.tme.eu/Document/c283990e907c122bb808207d1578ac7f/POWER_RATING-DTE.pdf'],
|
2026-04-06 14:42:54 +02:00
|
|
|
|
],
|
|
|
|
|
|
]]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function smd0603Parameters(): MockResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->mockParametersList([[
|
2026-06-27 19:01:04 +02:00
|
|
|
|
'symbol' => 'SMD0603-5K1-1%',
|
|
|
|
|
|
'parameters' => [
|
|
|
|
|
|
['id' => 34, 'name' => 'Type of resistor', 'value' => 'thick film'],
|
|
|
|
|
|
['id' => 35, 'name' => 'Case - mm', 'value' => '1608'],
|
|
|
|
|
|
['id' => 38, 'name' => 'Resistance', 'value' => '5.1kΩ'],
|
|
|
|
|
|
['id' => 39, 'name' => 'Tolerance', 'value' => '±1%'],
|
|
|
|
|
|
['id' => 120, 'name' => 'Operating voltage', 'value' => '50V'],
|
2026-04-06 14:42:54 +02:00
|
|
|
|
],
|
|
|
|
|
|
]]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function smd0603Prices(): MockResponse
|
|
|
|
|
|
{
|
2026-06-27 19:01:04 +02:00
|
|
|
|
return $this->mockPricesData('EUR', 'NET', [[
|
|
|
|
|
|
'symbol' => 'SMD0603-5K1-1%',
|
|
|
|
|
|
'price_list' => [
|
|
|
|
|
|
['amount' => 100, 'price_value' => 0.01077],
|
|
|
|
|
|
['amount' => 1000, 'price_value' => 0.00291],
|
|
|
|
|
|
['amount' => 5000, 'price_value' => 0.00150],
|
2026-04-06 14:42:54 +02:00
|
|
|
|
],
|
|
|
|
|
|
]]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-27 19:01:04 +02:00
|
|
|
|
private function etqp3mProduct(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return [
|
|
|
|
|
|
'symbol' => 'ETQP3M6R8KVP',
|
|
|
|
|
|
'original_symbol' => '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',
|
|
|
|
|
|
'statuses' => [],
|
|
|
|
|
|
'product_information_page' => '//www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/',
|
|
|
|
|
|
'weight' => 0.44,
|
|
|
|
|
|
'weight_unit' => 'g',
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 14:42:54 +02:00
|
|
|
|
private function etqp3mProducts(): MockResponse
|
|
|
|
|
|
{
|
2026-06-27 19:01:04 +02:00
|
|
|
|
return $this->mockProductsList([$this->etqp3mProduct()]);
|
2026-04-06 14:42:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function etqp3mFiles(): MockResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->mockFilesList([[
|
2026-06-27 19:01:04 +02:00
|
|
|
|
'symbol' => 'ETQP3M6R8KVP',
|
|
|
|
|
|
'photos' => [],
|
|
|
|
|
|
'documents' => [
|
|
|
|
|
|
['url' => '//www.tme.eu/Document/50a845881f09d8a2248350946e11df38/AGL0000C63.pdf'],
|
|
|
|
|
|
['url' => '//www.tme.eu/Document/8480690a42fa577214e35e33d3fc8d77/ETQP3M100KVN-LNK.txt'],
|
2026-04-06 14:42:54 +02:00
|
|
|
|
],
|
|
|
|
|
|
]]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function etqp3mParameters(): MockResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
return $this->mockParametersList([[
|
2026-06-27 19:01:04 +02:00
|
|
|
|
'symbol' => 'ETQP3M6R8KVP',
|
|
|
|
|
|
'parameters' => [
|
|
|
|
|
|
['id' => 566, 'name' => 'Inductance', 'value' => '6.8µH'],
|
|
|
|
|
|
['id' => 370, 'name' => 'Operating current', 'value' => '2.9A'],
|
|
|
|
|
|
['id' => 39, 'name' => 'Tolerance', 'value' => '±20%'],
|
2026-04-06 14:42:54 +02:00
|
|
|
|
],
|
|
|
|
|
|
]]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function etqp3mPrices(): MockResponse
|
|
|
|
|
|
{
|
2026-06-27 19:01:04 +02:00
|
|
|
|
return $this->mockPricesData('EUR', 'NET', [[
|
|
|
|
|
|
'symbol' => 'ETQP3M6R8KVP',
|
|
|
|
|
|
'price_list' => [
|
|
|
|
|
|
['amount' => 1, 'price_value' => 0.589],
|
|
|
|
|
|
['amount' => 5, 'price_value' => 0.429],
|
|
|
|
|
|
['amount' => 10, 'price_value' => 0.399],
|
2026-04-06 14:42:54 +02:00
|
|
|
|
],
|
|
|
|
|
|
]]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// --- Tests ---
|
|
|
|
|
|
|
|
|
|
|
|
public function testGetProviderInfo(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$info = $this->provider->getProviderInfo();
|
|
|
|
|
|
|
2026-07-26 13:55:27 +02:00
|
|
|
|
$this->assertInstanceOf(ProviderInfoDTO::class, $info);
|
|
|
|
|
|
$this->assertSame('tme', $info->key);
|
|
|
|
|
|
$this->assertEquals('TME', $info->name);
|
|
|
|
|
|
$this->assertEquals('https://tme.eu/', $info->url);
|
2026-04-06 14:42:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testIsActiveWithCredentials(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$this->assertTrue($this->provider->isActive());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testIsActiveWithoutCredentials(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$this->settings->apiToken = null;
|
2026-08-02 18:13:44 +02:00
|
|
|
|
$provider = new TMEProvider(new TMEClient($this->httpClient, $this->settings, new ArrayAdapter()), $this->settings, $this->httpClient);
|
2026-04-06 14:42:54 +02:00
|
|
|
|
$this->assertFalse($provider->isActive());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testGetCapabilities(): void
|
|
|
|
|
|
{
|
2026-07-26 13:55:27 +02:00
|
|
|
|
$capabilities = $this->provider->getProviderInfo()->capabilities;
|
2026-04-06 14:42:54 +02:00
|
|
|
|
|
|
|
|
|
|
$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/'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-02 18:13:44 +02:00
|
|
|
|
public function testAccessTokenIsCachedAcrossClientInstances(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
// A single ArrayAdapter shared between two independent TMEClient instances simulates
|
|
|
|
|
|
// the token cache surviving across separate requests (each request builds a fresh client).
|
|
|
|
|
|
$cache = new ArrayAdapter();
|
|
|
|
|
|
|
|
|
|
|
|
// First client has to fetch a token before making its API call
|
|
|
|
|
|
$client1 = new TMEClient($this->httpClient, $this->settings, $cache);
|
|
|
|
|
|
$this->httpClient->setResponseFactory([
|
|
|
|
|
|
$this->mockTokenResponse(),
|
|
|
|
|
|
$this->smd0603SearchResults(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
$client1->makeRequest('products/search', ['phrase' => 'SMD0603-5K1-1%']);
|
|
|
|
|
|
|
|
|
|
|
|
// Second client is a fresh instance, but shares the cache pool, so no token request should be made
|
|
|
|
|
|
$client2 = new TMEClient($this->httpClient, $this->settings, $cache);
|
|
|
|
|
|
$this->httpClient->setResponseFactory([
|
|
|
|
|
|
$this->smd0603SearchResults(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
$response = $client2->makeRequest('products/search', ['phrase' => 'SMD0603-5K1-1%']);
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertSame('OK', $response->toArray()['status']);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testAccessTokenIsRefetchedAfterExpiryEvenWithSharedCache(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$cache = new ArrayAdapter();
|
|
|
|
|
|
|
|
|
|
|
|
$client1 = new TMEClient($this->httpClient, $this->settings, $cache);
|
|
|
|
|
|
$this->httpClient->setResponseFactory([
|
|
|
|
|
|
new MockResponse(json_encode([
|
|
|
|
|
|
'access_token' => 'mock_access_token',
|
|
|
|
|
|
'token_type' => 'Bearer',
|
|
|
|
|
|
'expires_in' => -1, // already expired
|
|
|
|
|
|
'refresh_token' => 'mock_refresh_token',
|
|
|
|
|
|
])),
|
|
|
|
|
|
$this->smd0603SearchResults(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
$client1->makeRequest('products/search', ['phrase' => 'SMD0603-5K1-1%']);
|
|
|
|
|
|
|
|
|
|
|
|
// The cached token is expired, and the refresh_token grant fails (no mock queued for it beyond
|
|
|
|
|
|
// the token response below), so a fresh client_credentials token must be fetched.
|
|
|
|
|
|
$client2 = new TMEClient($this->httpClient, $this->settings, $cache);
|
|
|
|
|
|
$this->httpClient->setResponseFactory([
|
|
|
|
|
|
new MockResponse('', ['http_code' => 400]), // refresh_token grant fails
|
|
|
|
|
|
$this->mockTokenResponse(), // fallback client_credentials grant
|
|
|
|
|
|
$this->smd0603SearchResults(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
$response = $client2->makeRequest('products/search', ['phrase' => 'SMD0603-5K1-1%']);
|
|
|
|
|
|
|
|
|
|
|
|
$this->assertSame('OK', $response->toArray()['status']);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-06 14:42:54 +02:00
|
|
|
|
public function testSearchByKeyword(): void
|
|
|
|
|
|
{
|
2026-06-27 19:01:04 +02:00
|
|
|
|
// Request order: POST /auth/token, GET /products/search
|
|
|
|
|
|
$this->httpClient->setResponseFactory([
|
|
|
|
|
|
$this->mockTokenResponse(),
|
|
|
|
|
|
$this->smd0603SearchResults(),
|
|
|
|
|
|
]);
|
2026-04-06 14:42:54 +02:00
|
|
|
|
|
|
|
|
|
|
$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
|
|
|
|
|
|
{
|
2026-06-27 19:01:04 +02:00
|
|
|
|
// Request order: POST /auth/token, GET /products, GET /products/files,
|
|
|
|
|
|
// GET /products/parameters, GET /products/data
|
2026-04-06 14:42:54 +02:00
|
|
|
|
$this->httpClient->setResponseFactory([
|
2026-06-27 19:01:04 +02:00
|
|
|
|
$this->mockTokenResponse(),
|
2026-04-06 14:42:54 +02:00
|
|
|
|
$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
|
|
|
|
|
|
{
|
2026-06-27 19:01:04 +02:00
|
|
|
|
// Request order: POST /auth/token, GET /products, GET /products/files,
|
|
|
|
|
|
// GET /products/parameters, GET /products/data
|
2026-04-06 14:42:54 +02:00
|
|
|
|
$this->httpClient->setResponseFactory([
|
2026-06-27 19:01:04 +02:00
|
|
|
|
$this->mockTokenResponse(),
|
2026-04-06 14:42:54 +02:00
|
|
|
|
$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'));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|