Skip to content

Commit bbe1ba7

Browse files
authored
Merge pull request #191 from ByteInternet/add_unit_tests
Add unit tests for basic classes
2 parents 9eef87b + f5e84f8 commit bbe1ba7

File tree

13 files changed

+1275
-4
lines changed

13 files changed

+1275
-4
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
*.iml
88
*.local.*
99
*.swp
10+
11+
# Tests
12+
.phpunit.result.cache

src/Report/ReportLoader.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,18 @@
88

99
class ReportLoader
1010
{
11+
private string $reportPath;
12+
13+
public function __construct(string $reportPath = Report::REPORT_FILENAME)
14+
{
15+
$this->reportPath = $reportPath;
16+
}
17+
1118
public function loadReport(): ?Report
1219
{
13-
if (file_exists(Report::REPORT_FILENAME)) {
14-
$contents = file_get_contents(Report::REPORT_FILENAME);
15-
$data = json_decode($contents, true, JSON_THROW_ON_ERROR);
20+
if (file_exists($this->reportPath)) {
21+
$contents = file_get_contents($this->reportPath);
22+
$data = json_decode($contents, true, flags: JSON_THROW_ON_ERROR);
1623
Assert::isArray($data);
1724
return Report::fromArray($data);
1825
}

src/Report/ReportWriter.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@
66

77
class ReportWriter
88
{
9+
private string $reportPath;
10+
11+
public function __construct(string $reportPath = Report::REPORT_FILENAME)
12+
{
13+
$this->reportPath = $reportPath;
14+
}
15+
916
public function write(Report $report): void
1017
{
11-
file_put_contents(Report::REPORT_FILENAME, json_encode($report->toArray()));
18+
file_put_contents($this->reportPath, json_encode($report->toArray()));
1219
}
1320
}
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hypernode\Deploy\Tests\Unit\Console\Output;
6+
7+
use DateTime;
8+
use Hypernode\Deploy\Console\Output\ConsoleLogger;
9+
use PHPUnit\Framework\MockObject\MockObject;
10+
use PHPUnit\Framework\TestCase;
11+
use Psr\Log\InvalidArgumentException;
12+
use Psr\Log\LogLevel;
13+
use Symfony\Component\Console\Output\ConsoleOutputInterface;
14+
use Symfony\Component\Console\Output\OutputInterface;
15+
16+
class ConsoleLoggerTest extends TestCase
17+
{
18+
private OutputInterface&MockObject $output;
19+
private ConsoleLogger $logger;
20+
21+
protected function setUp(): void
22+
{
23+
$this->output = $this->createMock(OutputInterface::class);
24+
$this->output->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL);
25+
$this->logger = new ConsoleLogger($this->output);
26+
}
27+
28+
public function testLogInterpolatesStringPlaceholder(): void
29+
{
30+
$this->output->expects($this->once())
31+
->method('writeln')
32+
->with(
33+
$this->stringContains('Hello World'),
34+
$this->anything()
35+
);
36+
37+
$this->logger->info('Hello {name}', ['name' => 'World']);
38+
}
39+
40+
public function testLogInterpolatesIntegerPlaceholder(): void
41+
{
42+
$this->output->expects($this->once())
43+
->method('writeln')
44+
->with(
45+
$this->stringContains('Count: 42'),
46+
$this->anything()
47+
);
48+
49+
$this->logger->info('Count: {count}', ['count' => 42]);
50+
}
51+
52+
public function testLogInterpolatesFloatPlaceholder(): void
53+
{
54+
$this->output->expects($this->once())
55+
->method('writeln')
56+
->with(
57+
$this->stringContains('Value: 3.14'),
58+
$this->anything()
59+
);
60+
61+
$this->logger->info('Value: {value}', ['value' => 3.14]);
62+
}
63+
64+
public function testLogInterpolatesBooleanPlaceholder(): void
65+
{
66+
$this->output->expects($this->once())
67+
->method('writeln')
68+
->with(
69+
$this->stringContains('Active: 1'),
70+
$this->anything()
71+
);
72+
73+
$this->logger->info('Active: {active}', ['active' => true]);
74+
}
75+
76+
public function testLogInterpolatesObjectWithToString(): void
77+
{
78+
$object = new class {
79+
public function __toString(): string
80+
{
81+
return 'StringableObject';
82+
}
83+
};
84+
85+
$this->output->expects($this->once())
86+
->method('writeln')
87+
->with(
88+
$this->stringContains('Object: StringableObject'),
89+
$this->anything()
90+
);
91+
92+
$this->logger->info('Object: {obj}', ['obj' => $object]);
93+
}
94+
95+
public function testLogInterpolatesDateTimeInterface(): void
96+
{
97+
$date = new DateTime('2024-01-15T10:30:00+00:00');
98+
99+
$this->output->expects($this->once())
100+
->method('writeln')
101+
->with(
102+
$this->stringContains('Date: 2024-01-15T10:30:00+00:00'),
103+
$this->anything()
104+
);
105+
106+
$this->logger->info('Date: {date}', ['date' => $date]);
107+
}
108+
109+
public function testLogInterpolatesObjectWithoutToStringAsClassName(): void
110+
{
111+
$object = new \stdClass();
112+
113+
$this->output->expects($this->once())
114+
->method('writeln')
115+
->with(
116+
$this->stringContains('[object stdClass]'),
117+
$this->anything()
118+
);
119+
120+
$this->logger->info('Object: {obj}', ['obj' => $object]);
121+
}
122+
123+
public function testLogInterpolatesArrayAsArrayType(): void
124+
{
125+
$this->output->expects($this->once())
126+
->method('writeln')
127+
->with(
128+
$this->stringContains('[array]'),
129+
$this->anything()
130+
);
131+
132+
$this->logger->info('Data: {data}', ['data' => ['foo', 'bar']]);
133+
}
134+
135+
public function testLogInterpolatesNullAsEmpty(): void
136+
{
137+
$this->output->expects($this->once())
138+
->method('writeln')
139+
->with(
140+
$this->stringContains('Value: </info>'),
141+
$this->anything()
142+
);
143+
144+
$this->logger->info('Value: {value}', ['value' => null]);
145+
}
146+
147+
public function testLogWithoutPlaceholdersReturnsMessageUnchanged(): void
148+
{
149+
$this->output->expects($this->once())
150+
->method('writeln')
151+
->with(
152+
$this->stringContains('Simple message'),
153+
$this->anything()
154+
);
155+
156+
$this->logger->info('Simple message');
157+
}
158+
159+
public function testLogThrowsExceptionForInvalidLevel(): void
160+
{
161+
$this->expectException(InvalidArgumentException::class);
162+
$this->expectExceptionMessage('The log level "invalid" does not exist.');
163+
164+
$this->logger->log('invalid', 'test message');
165+
}
166+
167+
public function testHasErroredReturnsFalseInitially(): void
168+
{
169+
$this->assertSame(false, $this->logger->hasErrored());
170+
}
171+
172+
public function testHasErroredReturnsTrueAfterErrorLog(): void
173+
{
174+
$this->logger->error('An error occurred');
175+
176+
$this->assertSame(true, $this->logger->hasErrored());
177+
}
178+
179+
public function testHasErroredReturnsTrueAfterCriticalLog(): void
180+
{
181+
$this->logger->critical('Critical error');
182+
183+
$this->assertSame(true, $this->logger->hasErrored());
184+
}
185+
186+
public function testHasErroredReturnsFalseAfterInfoLog(): void
187+
{
188+
$this->logger->info('Info message');
189+
190+
$this->assertSame(false, $this->logger->hasErrored());
191+
}
192+
193+
public function testErrorLevelWritesToErrorOutput(): void
194+
{
195+
$errorOutput = $this->createMock(OutputInterface::class);
196+
$errorOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL);
197+
$errorOutput->expects($this->once())
198+
->method('writeln')
199+
->with(
200+
$this->stringContains('error message'),
201+
$this->anything()
202+
);
203+
204+
$consoleOutput = $this->createMock(ConsoleOutputInterface::class);
205+
$consoleOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL);
206+
$consoleOutput->method('getErrorOutput')->willReturn($errorOutput);
207+
208+
$logger = new ConsoleLogger($consoleOutput);
209+
$logger->error('error message');
210+
}
211+
212+
public function testDebugLevelNotWrittenAtNormalVerbosity(): void
213+
{
214+
$this->output->expects($this->never())->method('writeln');
215+
216+
$this->logger->debug('debug message');
217+
}
218+
219+
public function testDebugLevelWrittenAtVerboseLevel(): void
220+
{
221+
$verboseOutput = $this->createMock(OutputInterface::class);
222+
$verboseOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_VERBOSE);
223+
$verboseOutput->expects($this->once())
224+
->method('writeln')
225+
->with(
226+
$this->stringContains('debug message'),
227+
$this->anything()
228+
);
229+
230+
$logger = new ConsoleLogger($verboseOutput);
231+
$logger->debug('debug message');
232+
}
233+
234+
// Additional log level tests
235+
236+
public function testHasErroredReturnsTrueAfterEmergencyLog(): void
237+
{
238+
$this->logger->emergency('Emergency error');
239+
240+
$this->assertSame(true, $this->logger->hasErrored());
241+
}
242+
243+
public function testHasErroredReturnsTrueAfterAlertLog(): void
244+
{
245+
$this->logger->alert('Alert error');
246+
247+
$this->assertSame(true, $this->logger->hasErrored());
248+
}
249+
250+
public function testHasErroredReturnsFalseAfterWarningLog(): void
251+
{
252+
$this->logger->warning('Warning message');
253+
254+
$this->assertSame(false, $this->logger->hasErrored());
255+
}
256+
257+
public function testHasErroredReturnsFalseAfterNoticeLog(): void
258+
{
259+
$this->logger->notice('Notice message');
260+
261+
$this->assertSame(false, $this->logger->hasErrored());
262+
}
263+
264+
public function testCustomVerbosityLevelMapIsUsed(): void
265+
{
266+
// Create custom map where info requires verbose
267+
$customVerbosityMap = [
268+
LogLevel::INFO => OutputInterface::VERBOSITY_VERBOSE,
269+
];
270+
271+
$normalOutput = $this->createMock(OutputInterface::class);
272+
$normalOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL);
273+
$normalOutput->expects($this->never())->method('writeln');
274+
275+
$logger = new ConsoleLogger($normalOutput, $customVerbosityMap);
276+
$logger->info('This should not be written at normal verbosity');
277+
}
278+
279+
public function testCustomFormatLevelMapIsUsed(): void
280+
{
281+
// Create custom map where warning is treated as error (writes to error output)
282+
$customFormatMap = [
283+
LogLevel::WARNING => ConsoleLogger::ERROR,
284+
];
285+
286+
$errorOutput = $this->createMock(OutputInterface::class);
287+
$errorOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL);
288+
$errorOutput->expects($this->once())
289+
->method('writeln')
290+
->with(
291+
$this->stringContains('warning treated as error'),
292+
$this->anything()
293+
);
294+
295+
$consoleOutput = $this->createMock(ConsoleOutputInterface::class);
296+
$consoleOutput->method('getVerbosity')->willReturn(OutputInterface::VERBOSITY_NORMAL);
297+
$consoleOutput->method('getErrorOutput')->willReturn($errorOutput);
298+
299+
$logger = new ConsoleLogger($consoleOutput, [], $customFormatMap);
300+
$logger->warning('warning treated as error');
301+
302+
// Warning with custom format map should set errored flag
303+
$this->assertSame(true, $logger->hasErrored());
304+
}
305+
}

0 commit comments

Comments
 (0)