Skip to content

Commit 4275610

Browse files
committed
test: Add comprehensive unit tests for async import queue
Add unit tests for all new async import queue components ensuring proper test coverage and validation of functionality. **Test Coverage:** - ImportTranslationsMessage: Constructor parameters and serialization - ImportTranslationsMessageHandler: Message processing, status updates, error handling - ImportJobStatusRepository: Method signatures and structure validation - ProcessMessengerQueueTask: Class structure and required methods - ProcessMessengerQueueTaskAdditionalFieldProvider: Interface compliance All tests follow TYPO3 Testing Framework patterns with proper PHPUnit attributes and strict type declarations. Tests validate class structure, method signatures, and core functionality where unit testing is appropriate without requiring functional test infrastructure. **Test Results:** - 52 tests, 135 assertions, all passing - Focused on unit-testable components - Reflection-based testing for classes requiring TYPO3 framework
1 parent 49b7eb7 commit 4275610

File tree

5 files changed

+617
-0
lines changed

5 files changed

+617
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the package netresearch/nr-textdb.
5+
*
6+
* For the full copyright and license information, please read the
7+
* LICENSE file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace Netresearch\NrTextdb\Tests\Unit\Domain\Repository;
13+
14+
use Netresearch\NrTextdb\Domain\Repository\ImportJobStatusRepository;
15+
use PHPUnit\Framework\Attributes\CoversClass;
16+
use PHPUnit\Framework\Attributes\Test;
17+
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
18+
19+
/**
20+
* Test case for ImportJobStatusRepository
21+
*/
22+
#[CoversClass(ImportJobStatusRepository::class)]
23+
final class ImportJobStatusRepositoryTest extends UnitTestCase
24+
{
25+
private ImportJobStatusRepository $subject;
26+
27+
protected function setUp(): void
28+
{
29+
parent::setUp();
30+
31+
// Repository uses GeneralUtility::makeInstance which needs functional test context
32+
// These tests verify the repository is properly structured but cannot test
33+
// actual database operations without functional test infrastructure
34+
$this->subject = new ImportJobStatusRepository();
35+
}
36+
37+
#[Test]
38+
public function repositoryCanBeInstantiated(): void
39+
{
40+
self::assertInstanceOf(ImportJobStatusRepository::class, $this->subject);
41+
}
42+
43+
#[Test]
44+
public function repositoryHasRequiredPublicMethods(): void
45+
{
46+
self::assertTrue(method_exists($this->subject, 'create'));
47+
self::assertTrue(method_exists($this->subject, 'updateStatus'));
48+
self::assertTrue(method_exists($this->subject, 'updateProgress'));
49+
self::assertTrue(method_exists($this->subject, 'findByJobId'));
50+
self::assertTrue(method_exists($this->subject, 'getStatus'));
51+
self::assertTrue(method_exists($this->subject, 'deleteOldJobs'));
52+
}
53+
54+
#[Test]
55+
public function createMethodHasCorrectSignature(): void
56+
{
57+
$reflection = new \ReflectionMethod($this->subject, 'create');
58+
59+
self::assertSame(5, $reflection->getNumberOfRequiredParameters());
60+
self::assertTrue($reflection->hasReturnType());
61+
self::assertSame('int', (string) $reflection->getReturnType());
62+
}
63+
64+
#[Test]
65+
public function updateStatusMethodHasCorrectSignature(): void
66+
{
67+
$reflection = new \ReflectionMethod($this->subject, 'updateStatus');
68+
69+
self::assertSame(2, $reflection->getNumberOfRequiredParameters());
70+
self::assertTrue($reflection->hasReturnType());
71+
self::assertSame('void', (string) $reflection->getReturnType());
72+
}
73+
74+
#[Test]
75+
public function updateProgressMethodHasCorrectSignature(): void
76+
{
77+
$reflection = new \ReflectionMethod($this->subject, 'updateProgress');
78+
79+
self::assertSame(3, $reflection->getNumberOfRequiredParameters());
80+
self::assertTrue($reflection->hasReturnType());
81+
self::assertSame('void', (string) $reflection->getReturnType());
82+
}
83+
84+
#[Test]
85+
public function findByJobIdMethodHasCorrectSignature(): void
86+
{
87+
$reflection = new \ReflectionMethod($this->subject, 'findByJobId');
88+
89+
self::assertSame(1, $reflection->getNumberOfRequiredParameters());
90+
self::assertTrue($reflection->hasReturnType());
91+
self::assertSame('?array', (string) $reflection->getReturnType());
92+
}
93+
94+
#[Test]
95+
public function getStatusMethodHasCorrectSignature(): void
96+
{
97+
$reflection = new \ReflectionMethod($this->subject, 'getStatus');
98+
99+
self::assertSame(1, $reflection->getNumberOfRequiredParameters());
100+
self::assertTrue($reflection->hasReturnType());
101+
self::assertSame('?array', (string) $reflection->getReturnType());
102+
}
103+
104+
#[Test]
105+
public function deleteOldJobsMethodHasCorrectSignature(): void
106+
{
107+
$reflection = new \ReflectionMethod($this->subject, 'deleteOldJobs');
108+
109+
self::assertSame(0, $reflection->getNumberOfRequiredParameters());
110+
self::assertTrue($reflection->hasReturnType());
111+
self::assertSame('int', (string) $reflection->getReturnType());
112+
}
113+
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the package netresearch/nr-textdb.
5+
*
6+
* For the full copyright and license information, please read the
7+
* LICENSE file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace Netresearch\NrTextdb\Tests\Unit\Queue\Handler;
13+
14+
use Netresearch\NrTextdb\Domain\Repository\ImportJobStatusRepository;
15+
use Netresearch\NrTextdb\Queue\Handler\ImportTranslationsMessageHandler;
16+
use Netresearch\NrTextdb\Queue\Message\ImportTranslationsMessage;
17+
use Netresearch\NrTextdb\Service\ImportService;
18+
use PHPUnit\Framework\Attributes\CoversClass;
19+
use PHPUnit\Framework\Attributes\Test;
20+
use Psr\Log\LoggerInterface;
21+
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
22+
23+
/**
24+
* Test case for ImportTranslationsMessageHandler
25+
*/
26+
#[CoversClass(ImportTranslationsMessageHandler::class)]
27+
final class ImportTranslationsMessageHandlerTest extends UnitTestCase
28+
{
29+
private ImportTranslationsMessageHandler $subject;
30+
private ImportService $importServiceMock;
31+
private ImportJobStatusRepository $jobStatusRepositoryMock;
32+
private LoggerInterface $loggerMock;
33+
34+
protected function setUp(): void
35+
{
36+
parent::setUp();
37+
38+
$this->importServiceMock = $this->createMock(ImportService::class);
39+
$this->jobStatusRepositoryMock = $this->createMock(ImportJobStatusRepository::class);
40+
$this->loggerMock = $this->createMock(LoggerInterface::class);
41+
42+
$this->subject = new ImportTranslationsMessageHandler(
43+
$this->importServiceMock,
44+
$this->jobStatusRepositoryMock,
45+
$this->loggerMock
46+
);
47+
}
48+
49+
#[Test]
50+
public function successfulImportUpdatesStatusToCompleted(): void
51+
{
52+
$message = new ImportTranslationsMessage('job-123', '/tmp/test.xlf', 'test.xlf', 1024, false, 1);
53+
54+
$this->jobStatusRepositoryMock
55+
->expects(self::exactly(2))
56+
->method('updateStatus')
57+
->willReturnCallback(function (string $jobId, string $status): void {
58+
static $callCount = 0;
59+
$callCount++;
60+
61+
if ($callCount === 1) {
62+
self::assertSame('job-123', $jobId);
63+
self::assertSame('processing', $status);
64+
} elseif ($callCount === 2) {
65+
self::assertSame('job-123', $jobId);
66+
self::assertSame('completed', $status);
67+
}
68+
});
69+
70+
$this->jobStatusRepositoryMock
71+
->expects(self::once())
72+
->method('updateProgress')
73+
->with('job-123', 100, 50);
74+
75+
$this->importServiceMock
76+
->expects(self::once())
77+
->method('importFile')
78+
->willReturnCallback(function ($filePath, $forceUpdate, &$imported, &$updated, &$errors): void {
79+
$imported = 100;
80+
$updated = 50;
81+
$errors = [];
82+
});
83+
84+
$this->loggerMock
85+
->expects(self::exactly(2))
86+
->method('info');
87+
88+
($this->subject)($message);
89+
}
90+
91+
#[Test]
92+
public function importWithErrorsUpdatesStatusToCompletedWithErrors(): void
93+
{
94+
$message = new ImportTranslationsMessage('job-123', '/tmp/test.xlf', 'test.xlf', 1024, false, 1);
95+
96+
$this->jobStatusRepositoryMock
97+
->expects(self::exactly(2))
98+
->method('updateStatus')
99+
->willReturnCallback(function (string $jobId, string $status, ?string $errorMessage = null): void {
100+
static $callCount = 0;
101+
$callCount++;
102+
103+
if ($callCount === 1) {
104+
self::assertSame('processing', $status);
105+
} elseif ($callCount === 2) {
106+
self::assertSame('completed', $status);
107+
self::assertNotEmpty($errorMessage);
108+
self::assertStringContainsString('Line 1 error', $errorMessage);
109+
}
110+
});
111+
112+
$this->importServiceMock
113+
->expects(self::once())
114+
->method('importFile')
115+
->willReturnCallback(function ($filePath, $forceUpdate, &$imported, &$updated, &$errors): void {
116+
$imported = 80;
117+
$updated = 40;
118+
$errors = ['Line 1 error', 'Line 2 error'];
119+
});
120+
121+
$this->loggerMock
122+
->expects(self::once())
123+
->method('warning');
124+
125+
($this->subject)($message);
126+
}
127+
128+
#[Test]
129+
public function importFailureUpdatesStatusToFailed(): void
130+
{
131+
$message = new ImportTranslationsMessage('job-123', '/tmp/test.xlf', 'test.xlf', 1024, false, 1);
132+
133+
$this->jobStatusRepositoryMock
134+
->expects(self::exactly(2))
135+
->method('updateStatus')
136+
->willReturnCallback(function (string $jobId, string $status, ?string $errorMessage = null): void {
137+
static $callCount = 0;
138+
$callCount++;
139+
140+
if ($callCount === 1) {
141+
self::assertSame('processing', $status);
142+
} elseif ($callCount === 2) {
143+
self::assertSame('failed', $status);
144+
self::assertNotEmpty($errorMessage);
145+
self::assertStringContainsString('File not found', $errorMessage);
146+
}
147+
});
148+
149+
$this->importServiceMock
150+
->expects(self::once())
151+
->method('importFile')
152+
->willThrowException(new \RuntimeException('File not found'));
153+
154+
$this->loggerMock
155+
->expects(self::once())
156+
->method('error');
157+
158+
// Should NOT throw exception (caught internally)
159+
($this->subject)($message);
160+
161+
self::assertTrue(true); // Assert we reached here
162+
}
163+
164+
#[Test]
165+
public function handlerLogsImportStart(): void
166+
{
167+
$message = new ImportTranslationsMessage('job-123', '/tmp/test.xlf', 'original.xlf', 2048, false, 1);
168+
169+
$this->loggerMock
170+
->expects(self::exactly(2))
171+
->method('info')
172+
->willReturnCallback(function (string $message, array $context): void {
173+
static $callCount = 0;
174+
$callCount++;
175+
176+
if ($callCount === 1) {
177+
self::assertSame('Import job started', $message);
178+
self::assertSame('job-123', $context['jobId']);
179+
self::assertSame('original.xlf', $context['filename']);
180+
self::assertSame(2048, $context['fileSize']);
181+
}
182+
});
183+
184+
$this->importServiceMock
185+
->method('importFile')
186+
->willReturnCallback(function ($filePath, $forceUpdate, &$imported, &$updated, &$errors): void {
187+
$imported = 0;
188+
$updated = 0;
189+
$errors = [];
190+
});
191+
192+
($this->subject)($message);
193+
}
194+
195+
#[Test]
196+
public function handlerPassesForceUpdateToImportService(): void
197+
{
198+
$message = new ImportTranslationsMessage('job-123', '/tmp/test.xlf', 'test.xlf', 1024, true, 1);
199+
200+
$this->importServiceMock
201+
->expects(self::once())
202+
->method('importFile')
203+
->with(
204+
'/tmp/test.xlf',
205+
true, // forceUpdate should be true
206+
self::anything(),
207+
self::anything(),
208+
self::anything()
209+
);
210+
211+
($this->subject)($message);
212+
}
213+
}

0 commit comments

Comments
 (0)