Skip to content

Commit 2a1df0e

Browse files
committed
tests(repository): cover ReleasesCollection class
1 parent 7c4d75a commit 2a1df0e

File tree

6 files changed

+424
-71
lines changed

6 files changed

+424
-71
lines changed

docs/guidelines/how-to-write-tests.md

Lines changed: 11 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This document outlines the standards and best practices for writing unit tests f
66

77
- Unit tests should be in `tests/Unit`, integration tests in `tests/Integration`, acceptance tests in `tests/Acceptance`, architecture tests in `tests/Arch`, etc.
88
- Tests should be organized to mirror the project structure.
9-
- Each PHP class should have a corresponding test class
9+
- Each concrete PHP class should have a corresponding test class
1010
- Place tests in the appropriate namespace matching the source code structure
1111
- Use `final` keyword for test classes
1212

@@ -15,6 +15,13 @@ Source: src/ExternalContext/LocalExternalContextSource.php
1515
Test: tests/Unit/ExternalContext/LocalExternalContextSourceTest.php
1616
```
1717

18+
## What to Test
19+
20+
- **Test Concrete Implementations, Not Interfaces**: Interfaces define contracts but don't contain actual logic to test. Only test concrete implementations of interfaces.
21+
- **Focus on Behavior**: Test the behavior of classes rather than their internal implementation details.
22+
- **Test Public API**: Focus on testing the public methods and functionality that clients of the class will use.
23+
- **Test Edge Cases**: Include tests for boundary conditions, invalid inputs, and error scenarios.
24+
1825
## Module Testing
1926

2027
Modules located in `src/Module` are treated as independent units with their own test structure:
@@ -23,12 +30,11 @@ Modules located in `src/Module` are treated as independent units with their own
2330
```
2431
tests/Unit/Module/{ModuleName}/
2532
├── Stub/ (Contains stubs for the module's dependencies)
26-
├── API/ (Tests for the module's public API)
2733
└── Internal/ (Tests for module's internal implementations)
2834
```
2935

3036
- Internal implementations of a module are located in the `Internal` folder of each module
31-
- Everything outside the `Internal` folder is considered part of the module's API
37+
- Tests for public classes of the module should be placed directly in the module's test directory corresponding to the source code structure
3238
- Each module's tests should be structured as independent areas with their own stubs
3339

3440
## Arrange-Act-Assert (AAA) Pattern
@@ -133,13 +139,13 @@ protected function setUp(): void
133139
When testing modules from `src/Module`:
134140

135141
- Module tests should use stubs from their dedicated `Stub` directory
136-
- API tests should only rely on the public interfaces of the module, not internal implementations
142+
- Tests should only rely on the public interfaces of the module, not internal implementations
137143
- Internal tests can have additional stubs specific to internal components
138144
- Cross-module dependencies should be stubbed if possible (for interfaces), treating each module as an independent unit
139145

140146
```php
141147
// Example of module test setup with stubs
142-
namespace Tests\Unit\Module\Payment\API;
148+
namespace Tests\Unit\Module\Payment;
143149

144150
use Tests\Unit\Module\Payment\Stub\PaymentGatewayStub;
145151
use Tests\Unit\Module\Payment\Stub\LoggerStub;
@@ -328,71 +334,6 @@ final class MyTest extends TestCase
328334
}
329335
```
330336

331-
## Running Tests
332-
333-
Run tests using Composer scripts from the project root:
334-
335-
```bash
336-
# Run all tests with code coverage
337-
composer test
338-
339-
# Run architecture tests
340-
composer test:arch
341-
342-
# Run tests with clover coverage report
343-
composer test:cc
344-
```
345-
346-
To run specific tests with PHPUnit directly:
347-
348-
```bash
349-
# Run specific test class
350-
vendor/bin/phpunit tests/Unit/ExternalContext/LocalExternalContextSourceTest.php
351-
352-
# Run specific test method
353-
vendor/bin/phpunit --filter testFetchContextReturnsValidData tests/Unit/ExternalContext/LocalExternalContextSourceTest.php
354-
355-
# Run tests by group
356-
vendor/bin/phpunit --group slow
357-
```
358-
359-
## Test Coverage
360-
361-
Code coverage is automatically enabled in the test commands through the `XDEBUG_MODE=coverage` environment variable.
362-
363-
```bash
364-
# Generate coverage report with clover XML format
365-
composer test:cc
366-
# The report will be available at .build/phpunit/logs/clover.xml
367-
368-
# Run tests with coverage
369-
composer test
370-
```
371-
372-
For local development, you can generate HTML coverage reports:
373-
374-
```bash
375-
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html .build/coverage
376-
```
377-
378-
## Static Analysis & Code Quality
379-
380-
The project includes additional tools to maintain code quality:
381-
382-
```bash
383-
# Run PHP CS Fixer to check code style
384-
composer cs:diff
385-
386-
# Fix code style issues
387-
composer cs:fix
388-
389-
# Run Psalm static analysis
390-
composer psalm
391-
392-
# Run Infection for mutation testing
393-
composer infect
394-
```
395-
396337
## Example Test Class
397338

398339
```php

src/Module/Repository/Collection/ReleasesCollection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function withAssets(): self
6868
}
6969

7070
/**
71-
* Sorts releases by version in descending order (newest first).
71+
* Sorts releases by version in descending order (newest first) and maintain index association.
7272
*
7373
* @return $this New sorted collection
7474
*/
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Internal\DLoad\Tests\Unit\Module\Repository;
6+
7+
use Internal\DLoad\Module\Common\Architecture;
8+
use Internal\DLoad\Module\Common\OperatingSystem;
9+
use Internal\DLoad\Module\Common\Stability;
10+
use Internal\DLoad\Module\Repository\Collection\ReleasesCollection;
11+
use Internal\DLoad\Module\Repository\ReleaseInterface;
12+
use Internal\DLoad\Tests\Unit\Module\Repository\Stub\AssetStub;
13+
use Internal\DLoad\Tests\Unit\Module\Repository\Stub\ReleaseStub;
14+
use Internal\DLoad\Tests\Unit\Module\Repository\Stub\RepositoryStub;
15+
use PHPUnit\Framework\Attributes\CoversClass;
16+
use PHPUnit\Framework\TestCase;
17+
18+
#[CoversClass(ReleasesCollection::class)]
19+
final class ReleasesCollectionTest extends TestCase
20+
{
21+
private RepositoryStub $repository;
22+
private array $releases;
23+
private ReleasesCollection $collection;
24+
25+
public function testSatisfiesFiltersReleasesByVersionConstraint(): void
26+
{
27+
// Act
28+
$result = $this->collection->satisfies('^1.0.0');
29+
30+
// Assert
31+
self::assertCount(2, $result, 'Should only include 1.0.0 and 1.5.0 versions');
32+
33+
$versions = \array_map(
34+
static fn(ReleaseInterface $release): string => $release->getName(),
35+
\iterator_to_array($result),
36+
);
37+
38+
self::assertContains('1.0.0', $versions);
39+
self::assertContains('1.5.0', $versions);
40+
self::assertNotContains('2.0.0', $versions);
41+
}
42+
43+
public function testNotSatisfiesFiltersOutReleasesByVersionConstraint(): void
44+
{
45+
// Act
46+
$result = $this->collection->notSatisfies('^1.0.0');
47+
48+
// Assert
49+
self::assertGreaterThan(0, $result->count());
50+
self::assertNotContains('1.0.0', $this->getVersionsFromCollection($result));
51+
self::assertNotContains('1.5.0', $this->getVersionsFromCollection($result));
52+
self::assertContains('2.0.0', $this->getVersionsFromCollection($result));
53+
}
54+
55+
public function testStabilityFiltersReleasesByExactStability(): void
56+
{
57+
// Act
58+
$result = $this->collection->stability(Stability::Beta);
59+
60+
// Assert
61+
self::assertCount(1, $result);
62+
self::assertSame('2.1.0-beta', $result->first()->getName());
63+
}
64+
65+
public function testStableFiltersToOnlyStableReleases(): void
66+
{
67+
// Act
68+
$result = $this->collection->stable();
69+
70+
// Assert
71+
self::assertCount(3, $result, 'Should only include stable versions');
72+
73+
foreach ($result as $release) {
74+
self::assertSame(Stability::Stable, $release->getStability());
75+
}
76+
}
77+
78+
public function testMinimumStabilityFiltersReleasesByMinimumStabilityLevel(): void
79+
{
80+
// Act
81+
$result = $this->collection->minimumStability(Stability::RC);
82+
83+
// Assert
84+
$stabilities = [];
85+
foreach ($result as $release) {
86+
$stabilities[] = $release->getStability();
87+
}
88+
89+
self::assertContains(Stability::Stable, $stabilities);
90+
self::assertContains(Stability::RC, $stabilities);
91+
self::assertNotContains(Stability::Beta, $stabilities);
92+
self::assertNotContains(Stability::Alpha, $stabilities);
93+
}
94+
95+
public function testSortByVersionSortsReleasesByVersionDescending(): void
96+
{
97+
// Act
98+
$result = $this->collection->sortByVersion();
99+
$versions = $this->getVersionsFromCollection($result);
100+
101+
// Assert
102+
self::assertSame([
103+
// Expected order by semantic version, newest first
104+
'2.1.0-beta',
105+
'2.1.0-alpha',
106+
'2.0.1-rc1',
107+
'2.0.0',
108+
'1.5.0',
109+
'1.0.0',
110+
], \array_values($versions));
111+
}
112+
113+
public function testChainedFiltersWorkCorrectly(): void
114+
{
115+
// Act - Get stable releases that satisfy version constraint and sort them
116+
$result = $this->collection
117+
->stable()
118+
->satisfies('^1.0.0')
119+
->sortByVersion();
120+
121+
// Assert
122+
$versions = $this->getVersionsFromCollection($result);
123+
self::assertSame(['1.5.0', '1.0.0'], \array_values($versions));
124+
}
125+
126+
public function testWithAssetsFiltersReleasesWithAssets(): void
127+
{
128+
// Arrange - Set up assets for the second release (1.5.0)
129+
$release = $this->releases[1]; // 1.5.0 release
130+
131+
$assets = [
132+
new AssetStub(
133+
$release,
134+
'package-1.5.0-linux-x64.tar.gz',
135+
'https://example.com/downloads/package-1.5.0-linux-x64.tar.gz',
136+
OperatingSystem::Linux,
137+
Architecture::X86_64,
138+
)
139+
];
140+
141+
$this->repository->setAssets($assets, $release);
142+
143+
// Act
144+
$result = $this->collection->withAssets();
145+
146+
// Assert
147+
self::assertCount(1, $result);
148+
self::assertSame('1.5.0', $result->first()->getName());
149+
self::assertCount(1, $result->first()->getAssets());
150+
}
151+
152+
protected function setUp(): void
153+
{
154+
// Arrange
155+
$this->repository = new RepositoryStub('vendor/package', []);
156+
157+
// Create a series of releases with different versions and stabilities
158+
$this->releases = [
159+
new ReleaseStub(
160+
$this->repository,
161+
'2.0.0',
162+
'v2.0.0',
163+
Stability::Stable,
164+
[],
165+
),
166+
new ReleaseStub(
167+
$this->repository,
168+
'1.5.0',
169+
'v1.5.0',
170+
Stability::Stable,
171+
[],
172+
),
173+
new ReleaseStub(
174+
$this->repository,
175+
'1.0.0',
176+
'v1.0.0',
177+
Stability::Stable,
178+
[],
179+
),
180+
new ReleaseStub(
181+
$this->repository,
182+
'2.1.0-beta',
183+
'v2.1.0-beta',
184+
Stability::Beta,
185+
[],
186+
),
187+
new ReleaseStub(
188+
$this->repository,
189+
'2.1.0-alpha',
190+
'v2.1.0-alpha',
191+
Stability::Alpha,
192+
[],
193+
),
194+
new ReleaseStub(
195+
$this->repository,
196+
'2.0.1-rc1',
197+
'v2.0.1-rc1',
198+
Stability::RC,
199+
[],
200+
),
201+
];
202+
203+
// Create the collection with all releases
204+
$this->collection = new ReleasesCollection($this->releases);
205+
}
206+
207+
/**
208+
* Helper method to extract version names from a collection
209+
*/
210+
private function getVersionsFromCollection(ReleasesCollection $collection): array
211+
{
212+
return \array_map(
213+
static fn(ReleaseInterface $release): string => $release->getName(),
214+
\iterator_to_array($collection),
215+
);
216+
}
217+
}

0 commit comments

Comments
 (0)