Skip to content

Commit a088fbc

Browse files
committed
Add TME provider unit tests.
1 parent 11d642d commit a088fbc

1 file changed

Lines changed: 391 additions & 0 deletions

File tree

Lines changed: 391 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
<?php
2+
/*
3+
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+
*
5+
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as published
9+
* by the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace App\Tests\Services\InfoProviderSystem\Providers;
24+
25+
use App\Entity\Parts\ManufacturingStatus;
26+
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
27+
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
28+
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
29+
use App\Services\InfoProviderSystem\Providers\ProviderCapabilities;
30+
use App\Services\InfoProviderSystem\Providers\TMEClient;
31+
use App\Services\InfoProviderSystem\Providers\TMEProvider;
32+
use App\Settings\InfoProviderSystem\TMESettings;
33+
use App\Tests\SettingsTestHelper;
34+
use PHPUnit\Framework\TestCase;
35+
use Symfony\Component\HttpClient\MockHttpClient;
36+
use Symfony\Component\HttpClient\Response\MockResponse;
37+
38+
final class TMEProviderTest extends TestCase
39+
{
40+
private TMESettings $settings;
41+
private TMEProvider $provider;
42+
private MockHttpClient $httpClient;
43+
44+
protected function setUp(): void
45+
{
46+
$this->httpClient = new MockHttpClient();
47+
$this->settings = SettingsTestHelper::createSettingsDummy(TMESettings::class);
48+
// Use a short (anonymous-style) token so grossPrices is read from settings
49+
$this->settings->apiToken = 'test_token_000000000000000000000000000000000000000';
50+
$this->settings->apiSecret = 'test_secret';
51+
$this->settings->currency = 'EUR';
52+
$this->settings->language = 'en';
53+
$this->settings->country = 'DE';
54+
$this->settings->grossPrices = false;
55+
$this->provider = new TMEProvider(new TMEClient($this->httpClient, $this->settings), $this->settings);
56+
}
57+
58+
// --- Mock response helpers ---
59+
// Only fields actually read by TMEProvider are included.
60+
61+
private function mockProductList(array $products): MockResponse
62+
{
63+
return new MockResponse(json_encode([
64+
'Status' => 'OK',
65+
'Data' => ['ProductList' => $products],
66+
]));
67+
}
68+
69+
private function mockFilesList(array $products): MockResponse
70+
{
71+
return new MockResponse(json_encode([
72+
'Status' => 'OK',
73+
'Data' => ['ProductList' => $products],
74+
]));
75+
}
76+
77+
private function mockParametersList(array $products): MockResponse
78+
{
79+
return new MockResponse(json_encode([
80+
'Status' => 'OK',
81+
'Data' => ['ProductList' => $products],
82+
]));
83+
}
84+
85+
private function mockPrices(string $currency, string $priceType, array $products): MockResponse
86+
{
87+
return new MockResponse(json_encode([
88+
'Status' => 'OK',
89+
'Data' => [
90+
'Currency' => $currency,
91+
'PriceType' => $priceType,
92+
'ProductList' => $products,
93+
],
94+
]));
95+
}
96+
97+
// --- Mock data ---
98+
99+
private function smd0603Products(): MockResponse
100+
{
101+
return $this->mockProductList([[
102+
'Symbol' => 'SMD0603-5K1-1%',
103+
'OriginalSymbol' => '0603SAF5101T5E',
104+
'Producer' => 'ROYALOHM',
105+
'Description' => 'Resistor: thick film; SMD; 0603; 5.1kΩ; 0.1W; ±1%; 50V; -55÷155°C',
106+
'Category' => 'SMD resistors',
107+
'Photo' => '//ce8dc832c.cloudimg.io/v7/_cdn_/E9/C2/B0/00/0/732318_1.jpg',
108+
'ProductStatusList' => [],
109+
'ProductInformationPage' => '//www.tme.eu/en/details/smd0603-5k1-1%/smd-resistors/royalohm/0603saf5101t5e/',
110+
'Weight' => 0.021,
111+
'WeightUnit' => 'g',
112+
]]);
113+
}
114+
115+
private function smd0603Files(): MockResponse
116+
{
117+
return $this->mockFilesList([[
118+
'Symbol' => 'SMD0603-5K1-1%',
119+
'Files' => [
120+
'AdditionalPhotoList' => [],
121+
'DocumentList' => [
122+
['DocumentUrl' => '//www.tme.eu/Document/b315665a56acbc42df513c99b390ad98/ROYALOHM-THICKFILM.pdf'],
123+
['DocumentUrl' => '//www.tme.eu/Document/c283990e907c122bb808207d1578ac7f/POWER_RATING-DTE.pdf'],
124+
],
125+
],
126+
]]);
127+
}
128+
129+
private function smd0603Parameters(): MockResponse
130+
{
131+
return $this->mockParametersList([[
132+
'Symbol' => 'SMD0603-5K1-1%',
133+
'ParameterList' => [
134+
['ParameterId' => 34, 'ParameterName' => 'Type of resistor', 'ParameterValue' => 'thick film'],
135+
['ParameterId' => 35, 'ParameterName' => 'Case - mm', 'ParameterValue' => '1608'],
136+
['ParameterId' => 38, 'ParameterName' => 'Resistance', 'ParameterValue' => '5.1kΩ'],
137+
['ParameterId' => 39, 'ParameterName' => 'Tolerance', 'ParameterValue' => '±1%'],
138+
['ParameterId' => 120, 'ParameterName' => 'Operating voltage', 'ParameterValue' => '50V'],
139+
],
140+
]]);
141+
}
142+
143+
private function smd0603Prices(): MockResponse
144+
{
145+
return $this->mockPrices('EUR', 'NET', [[
146+
'Symbol' => 'SMD0603-5K1-1%',
147+
'PriceList' => [
148+
['Amount' => 100, 'PriceValue' => 0.01077],
149+
['Amount' => 1000, 'PriceValue' => 0.00291],
150+
['Amount' => 5000, 'PriceValue' => 0.00150],
151+
],
152+
]]);
153+
}
154+
155+
private function etqp3mProducts(): MockResponse
156+
{
157+
return $this->mockProductList([[
158+
'Symbol' => 'ETQP3M6R8KVP',
159+
'OriginalSymbol' => 'ETQP3M6R8KVP',
160+
'Producer' => 'PANASONIC',
161+
'Description' => 'Inductor: wire; SMD; 6.8uH; 2.9A; R: 65.7mΩ; ±20%; ETQP3M; 5.5x5x3mm',
162+
'Category' => 'Inductors',
163+
'Photo' => '//ce8dc832c.cloudimg.io/v7/_cdn_/9E/27/A0/00/0/684777_1.jpg',
164+
'ProductStatusList' => [],
165+
'ProductInformationPage' => '//www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/',
166+
'Weight' => 0.44,
167+
'WeightUnit' => 'g',
168+
]]);
169+
}
170+
171+
private function etqp3mFiles(): MockResponse
172+
{
173+
return $this->mockFilesList([[
174+
'Symbol' => 'ETQP3M6R8KVP',
175+
'Files' => [
176+
'AdditionalPhotoList' => [],
177+
'DocumentList' => [
178+
['DocumentUrl' => '//www.tme.eu/Document/50a845881f09d8a2248350946e11df38/AGL0000C63.pdf'],
179+
['DocumentUrl' => '//www.tme.eu/Document/8480690a42fa577214e35e33d3fc8d77/ETQP3M100KVN-LNK.txt'],
180+
],
181+
],
182+
]]);
183+
}
184+
185+
private function etqp3mParameters(): MockResponse
186+
{
187+
return $this->mockParametersList([[
188+
'Symbol' => 'ETQP3M6R8KVP',
189+
'ParameterList' => [
190+
['ParameterId' => 566, 'ParameterName' => 'Inductance', 'ParameterValue' => '6.8µH'],
191+
['ParameterId' => 370, 'ParameterName' => 'Operating current', 'ParameterValue' => '2.9A'],
192+
['ParameterId' => 39, 'ParameterName' => 'Tolerance', 'ParameterValue' => '±20%'],
193+
],
194+
]]);
195+
}
196+
197+
private function etqp3mPrices(): MockResponse
198+
{
199+
return $this->mockPrices('EUR', 'NET', [[
200+
'Symbol' => 'ETQP3M6R8KVP',
201+
'PriceList' => [
202+
['Amount' => 1, 'PriceValue' => 0.589],
203+
['Amount' => 5, 'PriceValue' => 0.429],
204+
['Amount' => 10, 'PriceValue' => 0.399],
205+
],
206+
]]);
207+
}
208+
209+
// --- Tests ---
210+
211+
public function testGetProviderInfo(): void
212+
{
213+
$info = $this->provider->getProviderInfo();
214+
215+
$this->assertIsArray($info);
216+
$this->assertArrayHasKey('name', $info);
217+
$this->assertArrayHasKey('description', $info);
218+
$this->assertArrayHasKey('url', $info);
219+
$this->assertEquals('TME', $info['name']);
220+
$this->assertEquals('https://tme.eu/', $info['url']);
221+
}
222+
223+
public function testGetProviderKey(): void
224+
{
225+
$this->assertSame('tme', $this->provider->getProviderKey());
226+
}
227+
228+
public function testIsActiveWithCredentials(): void
229+
{
230+
$this->assertTrue($this->provider->isActive());
231+
}
232+
233+
public function testIsActiveWithoutCredentials(): void
234+
{
235+
$this->settings->apiToken = null;
236+
$provider = new TMEProvider(new TMEClient($this->httpClient, $this->settings), $this->settings);
237+
$this->assertFalse($provider->isActive());
238+
}
239+
240+
public function testGetCapabilities(): void
241+
{
242+
$capabilities = $this->provider->getCapabilities();
243+
244+
$this->assertIsArray($capabilities);
245+
$this->assertContains(ProviderCapabilities::BASIC, $capabilities);
246+
$this->assertContains(ProviderCapabilities::PICTURE, $capabilities);
247+
$this->assertContains(ProviderCapabilities::DATASHEET, $capabilities);
248+
$this->assertContains(ProviderCapabilities::PRICE, $capabilities);
249+
$this->assertContains(ProviderCapabilities::FOOTPRINT, $capabilities);
250+
}
251+
252+
public function testGetHandledDomains(): void
253+
{
254+
$this->assertContains('tme.eu', $this->provider->getHandledDomains());
255+
}
256+
257+
public function testGetIDFromURL(): void
258+
{
259+
$this->assertSame('fi321_se', $this->provider->getIDFromURL('https://www.tme.eu/de/details/fi321_se/kuhler/alutronic/'));
260+
$this->assertSame('smd0603-5k1-1%25', $this->provider->getIDFromURL('https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/'));
261+
$this->assertNull($this->provider->getIDFromURL('https://www.tme.eu/en/'));
262+
}
263+
264+
public function testSearchByKeyword(): void
265+
{
266+
$this->httpClient->setResponseFactory([$this->smd0603Products()]);
267+
268+
$results = $this->provider->searchByKeyword('SMD0603-5K1-1%');
269+
270+
$this->assertIsArray($results);
271+
$this->assertCount(1, $results);
272+
$this->assertInstanceOf(SearchResultDTO::class, $results[0]);
273+
$this->assertSame('SMD0603-5K1-1%', $results[0]->provider_id);
274+
$this->assertSame('0603SAF5101T5E', $results[0]->name);
275+
$this->assertSame('ROYALOHM', $results[0]->manufacturer);
276+
$this->assertSame('SMD resistors', $results[0]->category);
277+
$this->assertSame(ManufacturingStatus::ACTIVE, $results[0]->manufacturing_status);
278+
$this->assertSame(
279+
'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/',
280+
$results[0]->provider_url
281+
);
282+
}
283+
284+
public function testGetDetailsWithPercentInPartNumber(): void
285+
{
286+
$this->httpClient->setResponseFactory([
287+
$this->smd0603Products(),
288+
$this->smd0603Files(),
289+
$this->smd0603Parameters(),
290+
$this->smd0603Prices(),
291+
]);
292+
293+
$result = $this->provider->getDetails('SMD0603-5K1-1%');
294+
295+
$this->assertInstanceOf(PartDetailDTO::class, $result);
296+
$this->assertSame('SMD0603-5K1-1%', $result->provider_id);
297+
$this->assertSame('0603SAF5101T5E', $result->name);
298+
$this->assertSame('Resistor: thick film; SMD; 0603; 5.1kΩ; 0.1W; ±1%; 50V; -55÷155°C', $result->description);
299+
$this->assertSame('ROYALOHM', $result->manufacturer);
300+
$this->assertSame('0603SAF5101T5E', $result->mpn);
301+
$this->assertSame('SMD resistors', $result->category);
302+
$this->assertSame(ManufacturingStatus::ACTIVE, $result->manufacturing_status);
303+
$this->assertSame(0.021, $result->mass);
304+
$this->assertSame('1608', $result->footprint);
305+
$this->assertSame(
306+
'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/',
307+
$result->provider_url
308+
);
309+
310+
$this->assertCount(2, $result->datasheets);
311+
$this->assertSame('https://www.tme.eu/Document/b315665a56acbc42df513c99b390ad98/ROYALOHM-THICKFILM.pdf', $result->datasheets[0]->url);
312+
$this->assertCount(0, $result->images);
313+
314+
$this->assertCount(1, $result->vendor_infos);
315+
$vendorInfo = $result->vendor_infos[0];
316+
$this->assertInstanceOf(PurchaseInfoDTO::class, $vendorInfo);
317+
$this->assertSame('TME', $vendorInfo->distributor_name);
318+
$this->assertSame('SMD0603-5K1-1%', $vendorInfo->order_number);
319+
$this->assertSame(
320+
'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/',
321+
$vendorInfo->product_url
322+
);
323+
$this->assertCount(3, $vendorInfo->prices);
324+
$this->assertSame(100.0, $vendorInfo->prices[0]->minimum_discount_amount);
325+
$this->assertSame('0.01077', $vendorInfo->prices[0]->price);
326+
$this->assertSame('EUR', $vendorInfo->prices[0]->currency_iso_code);
327+
$this->assertFalse($vendorInfo->prices[0]->includes_tax);
328+
329+
$this->assertCount(5, $result->parameters);
330+
}
331+
332+
public function testGetDetailsForEtqp3m6r8kvp(): void
333+
{
334+
$this->httpClient->setResponseFactory([
335+
$this->etqp3mProducts(),
336+
$this->etqp3mFiles(),
337+
$this->etqp3mParameters(),
338+
$this->etqp3mPrices(),
339+
]);
340+
341+
$result = $this->provider->getDetails('ETQP3M6R8KVP');
342+
343+
$this->assertInstanceOf(PartDetailDTO::class, $result);
344+
$this->assertSame('ETQP3M6R8KVP', $result->provider_id);
345+
$this->assertSame('ETQP3M6R8KVP', $result->name);
346+
$this->assertSame('Inductor: wire; SMD; 6.8uH; 2.9A; R: 65.7mΩ; ±20%; ETQP3M; 5.5x5x3mm', $result->description);
347+
$this->assertSame('PANASONIC', $result->manufacturer);
348+
$this->assertSame('ETQP3M6R8KVP', $result->mpn);
349+
$this->assertSame('Inductors', $result->category);
350+
$this->assertSame(ManufacturingStatus::ACTIVE, $result->manufacturing_status);
351+
$this->assertSame(0.44, $result->mass);
352+
$this->assertNull($result->footprint);
353+
$this->assertSame('https://www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/', $result->provider_url);
354+
355+
$this->assertCount(2, $result->datasheets);
356+
$this->assertSame('https://www.tme.eu/Document/50a845881f09d8a2248350946e11df38/AGL0000C63.pdf', $result->datasheets[0]->url);
357+
$this->assertCount(0, $result->images);
358+
359+
$this->assertCount(1, $result->vendor_infos);
360+
$vendorInfo = $result->vendor_infos[0];
361+
$this->assertSame('TME', $vendorInfo->distributor_name);
362+
$this->assertSame('ETQP3M6R8KVP', $vendorInfo->order_number);
363+
$this->assertSame('https://www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/', $vendorInfo->product_url);
364+
$this->assertCount(3, $vendorInfo->prices);
365+
$this->assertSame(1.0, $vendorInfo->prices[0]->minimum_discount_amount);
366+
$this->assertSame('0.589', $vendorInfo->prices[0]->price);
367+
$this->assertSame('EUR', $vendorInfo->prices[0]->currency_iso_code);
368+
$this->assertFalse($vendorInfo->prices[0]->includes_tax);
369+
370+
$this->assertCount(3, $result->parameters);
371+
}
372+
373+
public function testNormalizeURLEncodesBarePctSign(): void
374+
{
375+
$method = (new \ReflectionClass($this->provider))->getMethod('normalizeURL');
376+
377+
$this->assertSame(
378+
'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/',
379+
$method->invoke($this->provider, '//www.tme.eu/en/details/smd0603-5k1-1%/smd-resistors/royalohm/0603saf5101t5e/')
380+
);
381+
$this->assertSame(
382+
'https://www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/',
383+
$method->invoke($this->provider, '//www.tme.eu/en/details/smd0603-5k1-1%25/smd-resistors/royalohm/0603saf5101t5e/')
384+
);
385+
$this->assertSame(
386+
'https://www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/',
387+
$method->invoke($this->provider, '//www.tme.eu/en/details/etqp3m6r8kvp/inductors/panasonic/')
388+
);
389+
$this->assertSame('https://example.com/path', $method->invoke($this->provider, 'https://example.com/path'));
390+
}
391+
}

0 commit comments

Comments
 (0)