FOSSology  4.7.1
Open Source License Compliance by Open Source Software
DeciderAgentKotobaTest.php
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2025 Fossology contributors
4 
5  SPDX-License-Identifier: GPL-2.0-only
6 */
7 
8 namespace Fossology\Decider;
9 
31 use Mockery as M;
32 
33 global $container;
34 require_once(__DIR__ . '/../../../lib/php/Test/Agent/AgentTestMockHelper.php');
35 require_once(__DIR__ . '/../../agent/DeciderAgent.php');
36 
41 class DeciderAgentKotobaTest extends \PHPUnit\Framework\TestCase
42 {
44  private $deciderAgent;
46  private $clearingDecisionProcessor;
48  private $licenseMap;
49  private $assertCountBefore;
50 
51  protected function setUp(): void
52  {
53  global $container;
54  $container = M::mock('ContainerBuilder');
55 
56  $dbManager = M::mock(DbManager::class);
57  $agentDao = M::mock(AgentDao::class);
58  $agentDao->shouldReceive('getCurrentAgentId')->andReturn(1);
59 
60  $this->clearingDecisionProcessor = M::mock(ClearingDecisionProcessor::class);
61  $agentLicenseEventProcessor = M::mock(AgentLicenseEventProcessor::class);
62  $clearingDao = M::mock(ClearingDao::class);
63  $uploadDao = M::mock(UploadDao::class);
64  $highlightDao = M::mock(HighlightDao::class);
65  $showJobsDao = new ShowJobsDao($dbManager, $uploadDao);
66  $copyrightDao = M::mock(CopyrightDao::class);
67  $compatibilityDao = M::mock(CompatibilityDao::class);
68  $licenseDao = M::mock(LicenseDao::class);
69 
70  $container->shouldReceive('get')->with('db.manager')->andReturn($dbManager);
71  $container->shouldReceive('get')->with('dao.agent')->andReturn($agentDao);
72  $container->shouldReceive('get')->with('dao.highlight')->andReturn($highlightDao);
73  $container->shouldReceive('get')->with('dao.show_jobs')->andReturn($showJobsDao);
74  $container->shouldReceive('get')->with('dao.copyright')->andReturn($copyrightDao);
75  $container->shouldReceive('get')->with('dao.upload')->andReturn($uploadDao);
76  $container->shouldReceive('get')->with('dao.clearing')->andReturn($clearingDao);
77  $container->shouldReceive('get')->with('dao.compatibility')->andReturn($compatibilityDao);
78  $container->shouldReceive('get')->with('dao.license')->andReturn($licenseDao);
79  $container->shouldReceive('get')->with('decision.types')->andReturn(M::mock(DecisionTypes::class));
80  $container->shouldReceive('get')->with('businessrules.clearing_decision_processor')
81  ->andReturn($this->clearingDecisionProcessor);
82  $container->shouldReceive('get')->with('businessrules.agent_license_event_processor')
83  ->andReturn($agentLicenseEventProcessor);
84 
85  $this->deciderAgent = new DeciderAgent();
86 
87  // Inject a LicenseMap mock that acts as identity (no projections)
88  $this->licenseMap = M::mock(LicenseMap::class);
89  $this->licenseMap->shouldReceive('getProjectedId')->andReturnUsing(function ($id) {
90  return $id;
91  });
92  Reflectory::setObjectsProperty($this->deciderAgent, 'licenseMap', $this->licenseMap);
93 
94  $this->assertCountBefore = \Hamcrest\MatcherAssert::getCount();
95  }
96 
97  protected function tearDown(): void
98  {
99  $this->addToAssertionCount(\Hamcrest\MatcherAssert::getCount() - $this->assertCountBefore);
100  M::close();
101  }
102 
103  // ------------------------------------------------------------------ helpers
104 
108  private function makeClearingEvent(int $licenseId, int $eventType, bool $removed = false): ClearingEvent
109  {
110  $licenseRef = new LicenseRef($licenseId, "Lic$licenseId", "License $licenseId", "Lic$licenseId");
111  $clearingLicense = new ClearingLicense($licenseRef, $removed, $eventType);
112  return new ClearingEvent(
113  $licenseId, // eventId (reuse licenseId for simplicity)
114  1, // uploadTreeId
115  time(),
116  1, // userId
117  1, // groupId
118  $eventType,
119  $clearingLicense
120  );
121  }
122 
126  private function makeScannerMatch(int $licenseId): LicenseMatch
127  {
128  return new LicenseMatch(
129  1,
130  new LicenseRef($licenseId, "Lic$licenseId", "License $licenseId", "Lic$licenseId"),
131  M::mock(AgentRef::class),
132  1
133  );
134  }
135 
136  private function makeItemTreeBounds(): ItemTreeBounds
137  {
138  return new ItemTreeBounds(10, 'uploadtree', '2', 1, 4);
139  }
140 
141  // ------------------------------------------------------------------ tests
142 
147  public function testReturnsFalseWhenNoCurrentEvents(): void
148  {
149  $result = Reflectory::invokeObjectsMethodnameWith(
150  $this->deciderAgent,
151  'autodecideIfKotobaMatchesNoContradiction',
152  [$this->makeItemTreeBounds(), [], []]
153  );
154  $this->assertFalse($result);
155  }
156 
162  {
163  $events = [100 => $this->makeClearingEvent(100, ClearingEventTypes::USER)];
164 
165  $result = Reflectory::invokeObjectsMethodnameWith(
166  $this->deciderAgent,
167  'autodecideIfKotobaMatchesNoContradiction',
168  [$this->makeItemTreeBounds(), [], $events]
169  );
170  $this->assertFalse($result);
171  }
172 
177  public function testReturnsFalseWhenBulkEventPresent(): void
178  {
179  $events = [100 => $this->makeClearingEvent(100, ClearingEventTypes::BULK)];
180 
181  $result = Reflectory::invokeObjectsMethodnameWith(
182  $this->deciderAgent,
183  'autodecideIfKotobaMatchesNoContradiction',
184  [$this->makeItemTreeBounds(), [], $events]
185  );
186  $this->assertFalse($result);
187  }
188 
195  {
196  $mitId = 1;
197  $gplId = 2;
198 
199  $events = [
200  $mitId => $this->makeClearingEvent($mitId, ClearingEventTypes::KOTOBA, false), // add MIT
201  $gplId => $this->makeClearingEvent($gplId, ClearingEventTypes::KOTOBA, true), // remove GPL
202  ];
203 
204  $scannerMatches = [
205  $mitId => ['nomos' => [$this->makeScannerMatch($mitId)]],
206  $gplId => ['nomos' => [$this->makeScannerMatch($gplId)]],
207  ];
208 
209  $itemTreeBounds = $this->makeItemTreeBounds(); // single instance reused below
210 
211  $this->clearingDecisionProcessor->shouldReceive('makeDecisionFromLastEvents')
212  ->once()
213  ->with($itemTreeBounds, M::any(), M::any(), DecisionTypes::IDENTIFIED, false);
214 
215  $result = Reflectory::invokeObjectsMethodnameWith(
216  $this->deciderAgent,
217  'autodecideIfKotobaMatchesNoContradiction',
218  [$itemTreeBounds, $scannerMatches, $events]
219  );
220  $this->assertTrue($result);
221  }
222 
229  {
230  $mitId = 1;
231  $gplId = 2;
232  $mplId = 3;
233 
234  $events = [
235  $mitId => $this->makeClearingEvent($mitId, ClearingEventTypes::KOTOBA, false),
236  $gplId => $this->makeClearingEvent($gplId, ClearingEventTypes::KOTOBA, true),
237  ];
238 
239  $scannerMatches = [
240  $mitId => ['nomos' => [$this->makeScannerMatch($mitId)]],
241  $gplId => ['nomos' => [$this->makeScannerMatch($gplId)]],
242  $mplId => ['nomos' => [$this->makeScannerMatch($mplId)]], // unaccounted
243  ];
244 
245  // makeDecisionFromLastEvents must NOT be called
246  $this->clearingDecisionProcessor->shouldNotReceive('makeDecisionFromLastEvents');
247 
248  $result = Reflectory::invokeObjectsMethodnameWith(
249  $this->deciderAgent,
250  'autodecideIfKotobaMatchesNoContradiction',
251  [$this->makeItemTreeBounds(), $scannerMatches, $events]
252  );
253  $this->assertFalse($result);
254  }
255 
260  public function testConcludesWhenNoScannerFindings(): void
261  {
262  $mitId = 1;
263  $events = [
264  $mitId => $this->makeClearingEvent($mitId, ClearingEventTypes::KOTOBA, false),
265  ];
266 
267  $this->clearingDecisionProcessor->shouldReceive('makeDecisionFromLastEvents')->once();
268 
269  $result = Reflectory::invokeObjectsMethodnameWith(
270  $this->deciderAgent,
271  'autodecideIfKotobaMatchesNoContradiction',
272  [$this->makeItemTreeBounds(), [], $events]
273  );
274  $this->assertTrue($result);
275  }
276 
282  {
283  $events = [
284  1 => $this->makeClearingEvent(1, ClearingEventTypes::KOTOBA),
285  2 => $this->makeClearingEvent(2, ClearingEventTypes::USER),
286  ];
287 
288  $this->clearingDecisionProcessor->shouldNotReceive('makeDecisionFromLastEvents');
289 
290  $result = Reflectory::invokeObjectsMethodnameWith(
291  $this->deciderAgent,
292  'autodecideIfKotobaMatchesNoContradiction',
293  [$this->makeItemTreeBounds(), [], $events]
294  );
295  $this->assertFalse($result);
296  }
297 
303  {
304  $mitId = 1;
305  $events = [
306  $mitId => $this->makeClearingEvent($mitId, ClearingEventTypes::KOTOBA, false),
307  ];
308 
309  $this->clearingDecisionProcessor->shouldReceive('makeDecisionFromLastEvents')
310  ->once()
311  ->andThrow(new \Exception("candidate license"));
312 
313  $result = Reflectory::invokeObjectsMethodnameWith(
314  $this->deciderAgent,
315  'autodecideIfKotobaMatchesNoContradiction',
316  [$this->makeItemTreeBounds(), [], $events]
317  );
318  $this->assertFalse($result);
319  }
320 }
Unit tests for DeciderAgent::autodecideIfKotobaMatchesNoContradiction()
makeClearingEvent(int $licenseId, int $eventType, bool $removed=false)
Agent to decide license findings in an upload.
Utility functions to process ClearingDecision.
Wrapper class for license map.
Definition: LicenseMap.php:19
Namespace for decider agent.
Definition: BulkReuser.php:8