FOSSology  4.7.1
Open Source License Compliance by Open Source Software
CustomTextImportTest.php
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2025 Fossology contributors
4 
5  SPDX-License-Identifier: GPL-2.0-only
6 */
7 
9 
16 use Mockery as M;
17 use Monolog\Logger;
18 
25 class CustomTextImportTest extends \PHPUnit\Framework\TestCase
26 {
28  private $dbManager;
30  private $userDao;
32  private $licenseDao;
34  private $importer;
35 
36  protected function setUp(): void
37  {
38  // Minimal Auth globals so Auth::getUserId/getGroupId don't explode
39  $GLOBALS['SysConf']['auth']['UserId'] = 1;
40  $GLOBALS['SysConf']['auth']['GroupId'] = 1;
41 
42  $this->dbManager = M::mock(DbManager::class);
43  $this->userDao = M::mock(UserDao::class);
44  $this->licenseDao = M::mock(LicenseDao::class);
45 
46  $this->importer = new CustomTextImport($this->dbManager, $this->userDao, $this->licenseDao);
47  }
48 
49  protected function tearDown(): void
50  {
51  M::close();
52  }
53 
54  // ------------------------------------------------------------------ helpers
55 
57  private function makeLicense(int $id): object
58  {
59  $lic = M::mock(License::class);
60  $lic->shouldReceive('getId')->andReturn($id);
61  return $lic;
62  }
63 
64  // ================================================================ C2 fix: no backwards normalization
65 
73  {
74  $spdxName = 'GPL-2.0-only';
75  $cpPk = 42;
76 
77  // The lookup MUST use the exact SPDX shortname, not a mangled form.
78  $this->licenseDao->shouldReceive('getLicenseByShortName')
79  ->with($spdxName)
80  ->once()
81  ->andReturn($this->makeLicense(7));
82 
83  // No insertLicense call allowed — the license already exists.
84  $this->licenseDao->shouldNotReceive('insertLicense');
85 
86  $this->dbManager->shouldReceive('getSingleRow')
87  ->andReturn(null); // no existing association
88  $this->dbManager->shouldReceive('insertTableRow')->once();
89 
90  $result = Reflectory::invokeObjectsMethodnameWith(
91  $this->importer,
92  'associateLicenses',
93  [$cpPk, $spdxName, false]
94  );
95 
96  $this->assertSame(1, $result['associated']);
97  $this->assertEmpty($result['failed']);
98  $this->assertEmpty($result['created']);
99  }
100 
106  {
107  $spdxName = 'GPL-2.0-or-later';
108  $cpPk = 43;
109 
110  $this->licenseDao->shouldReceive('getLicenseByShortName')
111  ->with($spdxName)
112  ->once()
113  ->andReturn($this->makeLicense(8));
114 
115  $this->licenseDao->shouldNotReceive('insertLicense');
116 
117  $this->dbManager->shouldReceive('getSingleRow')->andReturn(null);
118  $this->dbManager->shouldReceive('insertTableRow')->once();
119 
120  $result = Reflectory::invokeObjectsMethodnameWith(
121  $this->importer,
122  'associateLicenses',
123  [$cpPk, $spdxName, false]
124  );
125 
126  $this->assertSame(1, $result['associated']);
127  }
128 
133  public function testAssociateLicensesTrimsWhitespace(): void
134  {
135  $cpPk = 44;
136 
137  $this->licenseDao->shouldReceive('getLicenseByShortName')
138  ->with('MIT') // trimmed
139  ->once()
140  ->andReturn($this->makeLicense(9));
141 
142  $this->dbManager->shouldReceive('getSingleRow')->andReturn(null);
143  $this->dbManager->shouldReceive('insertTableRow')->once();
144 
145  $result = Reflectory::invokeObjectsMethodnameWith(
146  $this->importer,
147  'associateLicenses',
148  [$cpPk, ' MIT ', false]
149  );
150 
151  $this->assertSame(1, $result['associated']);
152  }
153 
158  public function testAutoCreatesUnknownValidLicense(): void
159  {
160  $cpPk = 45;
161  $unknownLic = 'My-Custom-1.0';
162 
163  $this->licenseDao->shouldReceive('getLicenseByShortName')
164  ->with($unknownLic)->once()->andReturn(null);
165  $this->licenseDao->shouldReceive('insertLicense')
166  ->with($unknownLic, '', null)->once()->andReturn(99);
167  $this->licenseDao->shouldReceive('getLicenseById')
168  ->with(99)->once()->andReturn($this->makeLicense(99));
169 
170  $this->dbManager->shouldReceive('getSingleRow')->andReturn(null);
171  $this->dbManager->shouldReceive('insertTableRow')->once();
172 
173  $result = Reflectory::invokeObjectsMethodnameWith(
174  $this->importer,
175  'associateLicenses',
176  [$cpPk, $unknownLic, false, true] // $removing=false, $allowCreate=true
177  );
178 
179  $this->assertSame(1, $result['associated']);
180  $this->assertContains($unknownLic, $result['created']);
181  }
182 
188  {
189  $cpPk = 46;
190 
191  $this->licenseDao->shouldReceive('getLicenseByShortName')
192  ->with('MIT')->once()->andReturn($this->makeLicense(1));
193  $this->licenseDao->shouldReceive('getLicenseByShortName')
194  ->with('Apache-2.0')->once()->andReturn($this->makeLicense(2));
195 
196  $this->dbManager->shouldReceive('getSingleRow')->andReturn(null);
197  $this->dbManager->shouldReceive('insertTableRow')->twice();
198 
199  $result = Reflectory::invokeObjectsMethodnameWith(
200  $this->importer,
201  'associateLicenses',
202  [$cpPk, 'MIT, Apache-2.0', false]
203  );
204 
205  $this->assertSame(2, $result['associated']);
206  }
207 
208  // ================================================================ duplicate detection
209 
214  public function testImportPhrasesSkipsDuplicateText(): void
215  {
216  $text = 'This software is provided as-is.';
217 
218  // Duplicate check returns a row → existing phrase
219  $this->dbManager->shouldReceive('getSingleRow')
220  ->andReturn(['cp_pk' => 1]);
221 
222  // insertPreparedAndReturn must NOT be called
223  $this->dbManager->shouldNotReceive('insertPreparedAndReturn');
224 
225  $result = Reflectory::invokeObjectsMethodnameWith(
226  $this->importer,
227  'importSinglePhrase',
228  [['text' => $text]]
229  );
230 
231  $this->assertFalse($result['success']);
232  $this->assertStringContainsString('Duplicate', $result['message']);
233  }
234 
239  public function testImportPhraseRejectsEmptyText(): void
240  {
241  $result = Reflectory::invokeObjectsMethodnameWith(
242  $this->importer,
243  'importSinglePhrase',
244  [['text' => '']]
245  );
246 
247  $this->assertFalse($result['success']);
248  $this->assertStringContainsString('required', strtolower($result['message']));
249  }
250 
251  // ================================================================ header alias mapping
252 
257  public function testMapHeadersMapsCapitalisedNames(): void
258  {
259  $data = [
260  'Text' => 'some phrase',
261  'Acknowledgement' => 'ack text',
262  'Comments' => 'a comment',
263  'Is Active' => 'true',
264  'Licenses To Add' => 'MIT',
265  'Licenses To Remove' => 'GPL-2.0-only',
266  ];
267 
268  $mapped = Reflectory::invokeObjectsMethodnameWith(
269  $this->importer,
270  'mapHeaders',
271  [$data]
272  );
273 
274  $this->assertSame('some phrase', $mapped['text']);
275  $this->assertSame('ack text', $mapped['acknowledgement']);
276  $this->assertSame('a comment', $mapped['comments']);
277  $this->assertSame('true', $mapped['is_active']);
278  $this->assertSame('MIT', $mapped['licenses_to_add']);
279  $this->assertSame('GPL-2.0-only', $mapped['licenses_to_remove']);
280  }
281 
286  public function testMapHeadersMapsLowerCaseNames(): void
287  {
288  $data = [
289  'text' => 'phrase',
290  'acknowledgement' => '',
291  'licenses_to_add' => 'MIT',
292  'licenses_to_remove' => '',
293  ];
294 
295  $mapped = Reflectory::invokeObjectsMethodnameWith(
296  $this->importer,
297  'mapHeaders',
298  [$data]
299  );
300 
301  $this->assertSame('phrase', $mapped['text']);
302  $this->assertSame('MIT', $mapped['licenses_to_add']);
303  }
304 
305  // ================================================================ parseBoolean
306 
313  public function testParseBoolean($input, bool $expected): void
314  {
315  $result = Reflectory::invokeObjectsMethodnameWith(
316  $this->importer,
317  'parseBoolean',
318  [$input]
319  );
320  $this->assertSame($expected, $result);
321  }
322 
323  public function parseBooleanProvider(): array
324  {
325  return [
326  ['true', true],
327  ['TRUE', true],
328  ['1', true],
329  ['yes', true],
330  ['on', true],
331  ['active', true],
332  ['false', false],
333  ['0', false],
334  ['no', false],
335  ['off', false],
336  ['', false],
337  [true, true],
338  [false, false],
339  ];
340  }
341 
342  // ================================================================ normalizeBulkExportValues
343 
349  {
350  $mapped = ['acknowledgement' => 'First note | Second note | Third note'];
351 
352  $result = Reflectory::invokeObjectsMethodnameWith(
353  $this->importer,
354  'normalizeBulkExportValues',
355  [$mapped]
356  );
357 
358  $this->assertSame('First note; Second note; Third note', $result['acknowledgement']);
359  }
360 
366  {
367  $mapped = ['acknowledgement' => ['Note A', 'Note B']];
368 
369  $result = Reflectory::invokeObjectsMethodnameWith(
370  $this->importer,
371  'normalizeBulkExportValues',
372  [$mapped]
373  );
374 
375  $this->assertSame('Note A; Note B', $result['acknowledgement']);
376  }
377 
383  {
384  $mapped = ['text' => 'line one\\nline two\\nline three'];
385 
386  $result = Reflectory::invokeObjectsMethodnameWith(
387  $this->importer,
388  'normalizeBulkExportValues',
389  [$mapped]
390  );
391 
392  $this->assertSame("line one\nline two\nline three", $result['text']);
393  }
394 
395  // ================================================================ importJsonData public API
396 
402  {
403  // Duplicate check → no existing row
404  $this->dbManager->shouldReceive('getSingleRow')
405  ->with(M::pattern('/custom_phrase/'), M::any(), M::any())
406  ->andReturn(null);
407  $this->dbManager->shouldReceive('insertPreparedAndReturn')
408  ->andReturn(1);
409 
410  $msg = '';
411  $result = $this->importer->importJsonData(
412  [['text' => 'a unique phrase that is new']],
413  $msg
414  );
415 
416  $this->assertStringContainsString('1', $result);
417  }
418 
424  {
425  $msg = '';
426  $result = $this->importer->importJsonData(
427  [['text' => ''], ['acknowledgement' => 'orphan']],
428  $msg
429  );
430 
431  // 0 phrases imported
432  $this->assertStringContainsString('0', $result);
433  }
434 }
Unit tests for CustomTextImport — covering the C2 fix and import logic (alias mapping,...
Import custom text phrases from CSV/JSON.
fo_dbManager * dbManager
fo_dbManager object
Definition: process.c:16
Utility functions for specific applications.