Skip to content

Commit b3af89e

Browse files
authored
Improve 3.x test and CI workflows (#146)
* Improve 3.x test and CI workflows * Fix Craft 4 compatibility workflow resolution * Document local Craft 4 compatibility workflow * Use Uri query cleanup in UrlSigner * Avoid phpstan version-specific generateUrl calls * Ignore version-specific phpstan errors * Use inline phpstan compatibility ignores
1 parent 25c08bf commit b3af89e

File tree

9 files changed

+148
-9
lines changed

9 files changed

+148
-9
lines changed

.github/workflows/ci.yml

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ on:
33
workflow_dispatch:
44
push:
55
branches:
6-
- 1.x
7-
- 2.x
6+
- 3.x
87
pull_request:
98
permissions:
109
contents: read
@@ -18,4 +17,50 @@ jobs:
1817
with:
1918
node_version: '18'
2019
php_version: '8.3'
21-
jobs: '["ecs", "phpstan", "prettier"]'
20+
jobs: '["ecs", "prettier"]'
21+
22+
compatibility:
23+
name: Compatibility (Craft ${{ matrix.craft }})
24+
runs-on: ubuntu-latest
25+
strategy:
26+
fail-fast: false
27+
matrix:
28+
include:
29+
- craft: '^4.6'
30+
flysystem: '^1.0'
31+
- craft: '^5'
32+
flysystem: '^2.0'
33+
services:
34+
mysql:
35+
image: mysql:8.0
36+
ports:
37+
- 33066:3306
38+
env:
39+
MYSQL_DATABASE: db
40+
MYSQL_USER: db
41+
MYSQL_PASSWORD: db
42+
MYSQL_ROOT_PASSWORD: root
43+
options: >-
44+
--health-cmd="mysqladmin ping -h 127.0.0.1 -udb -pdb"
45+
--health-interval=10s
46+
--health-timeout=5s
47+
--health-retries=10
48+
steps:
49+
- uses: actions/checkout@v4
50+
51+
- uses: shivammathur/setup-php@v2
52+
with:
53+
php-version: '8.3'
54+
coverage: none
55+
56+
- name: Resolve dependencies
57+
run: composer update "craftcms/cms:${{ matrix.craft }}" "craftcms/flysystem:${{ matrix.flysystem }}" --with-all-dependencies --no-interaction --no-progress --prefer-dist --no-audit
58+
59+
- name: Prepare test environment
60+
run: composer test:init
61+
62+
- name: Run PHPStan
63+
run: composer phpstan
64+
65+
- name: Run unit tests
66+
run: composer test

.lintstagedrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"**/*.php": ["composer run fix-cs"],
2+
"**/*.php": ["composer run cs:fix"],
33
"*": "prettier --ignore-unknown --write"
44
}

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,31 @@ When you [deploy](https://craftcms.com/knowledge-base/cloud-deployment) a projec
2626

2727
When setting up your project’s assets, use the provided **Craft Cloud** filesystem type. Read more about [managing assets in Cloud projects](https://craftcms.com/knowledge-base/cloud-assets).
2828

29+
## Testing
30+
31+
The Codeception `unit` suite on `3.x` boots Craft and expects a local test database.
32+
33+
```bash
34+
composer test:init
35+
composer test:up
36+
composer test
37+
composer test:down
38+
```
39+
40+
`composer test:init` will create `tests/.env` from `tests/.env.example` if it does not already exist. `composer test:up` uses that file when starting the MySQL service defined in `tests/docker-compose.yaml`.
41+
42+
For local compatibility work on `3.x`, it can be helpful to keep your main checkout on the default/latest Craft 5 dependency set and use a separate Git worktree for Craft 4 so each checkout can keep its own `vendor/`, `composer.lock`, and `tests/.env` state.
43+
44+
```bash
45+
git worktree add ../cloud-3x-craft4 3.x
46+
47+
# In the Craft 4 worktree:
48+
composer update "craftcms/cms:^4.6" "craftcms/flysystem:^1.0" --with-all-dependencies --no-audit
49+
50+
# In your main checkout:
51+
composer update "craftcms/cms:^5" "craftcms/flysystem:^2.0" --with-all-dependencies
52+
```
53+
2954
## Developer Features
3055

3156
### Template Helpers

composer.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,15 @@
2828
"codeception/module-yii2": "^1.0"
2929
},
3030
"scripts": {
31-
"check-cs": "ecs check --ansi",
32-
"fix-cs": "ecs check --ansi --fix",
31+
"cs:check": "ecs check --ansi",
32+
"cs:fix": "ecs check --ansi --fix",
3333
"phpstan": "phpstan --memory-limit=1G",
34+
"test:init": "@php -r \"file_exists('tests/.env') || copy('tests/.env.example', 'tests/.env');\"",
35+
"test:up": [
36+
"@test:init",
37+
"docker compose --env-file tests/.env -f tests/docker-compose.yaml up -d --wait"
38+
],
39+
"test:down": "docker compose --env-file tests/.env -f tests/docker-compose.yaml down",
3440
"test": "codecept run unit",
3541
"test:coverage": "codecept run unit --coverage"
3642
},

phpstan.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ includes:
33

44
parameters:
55
level: 5
6+
reportUnmatchedIgnoredErrors: false
67
paths:
78
- src

src/UrlSigner.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ public function sign(string $url): string
2626

2727
private function getSigningData(string $url): string
2828
{
29-
return Modifier::wrap($url)
29+
return (string) Modifier::wrap($url)
3030
->removeQueryParameters($this->signatureParameter)
31-
->sortQuery();
31+
->sortQuery()
32+
->removeEmptyQueryPairs();
3233
}
3334

3435
public function verify(string $url): bool

src/imagetransforms/ImageTransformer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ class ImageTransformer extends Component implements ImageTransformerInterface
2626
public function getTransformUrl(Asset $asset, \craft\models\ImageTransform $imageTransform, bool $immediately): string
2727
{
2828
if (version_compare(Craft::$app->version, '5.0', '>=')) {
29+
// @phpstan-ignore argument.type, arguments.count (Craft 5 compatibility)
2930
$assetUrl = Html::encodeSpaces(Assets::generateUrl($asset));
3031
} else {
3132
$fs = $asset->getVolume()->getTransformFs();
32-
/** @phpstan-ignore argument.type (Craft 4 compatibility) */
33+
// @phpstan-ignore argument.type, arguments.count (Craft 4 compatibility)
3334
$assetUrl = Html::encodeSpaces(Assets::generateUrl($fs, $asset));
3435
}
3536

tests/docker-compose.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ services:
88
MYSQL_DATABASE: ${CRAFT_DB_DATABASE}
99
MYSQL_USER: ${CRAFT_DB_USER}
1010
MYSQL_PASSWORD: ${CRAFT_DB_PASSWORD}
11+
healthcheck:
12+
test:
13+
[
14+
'CMD-SHELL',
15+
'mysqladmin ping -h 127.0.0.1 -u$$MYSQL_USER -p$$MYSQL_PASSWORD --silent',
16+
]
17+
interval: 5s
18+
timeout: 5s
19+
retries: 20
20+
start_period: 10s
1121
# postgres:
1222
# image: postgres:15
1323
# ports:
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace craft\cloud\tests\unit;
4+
5+
use Codeception\Test\Unit;
6+
use Craft;
7+
use craft\cloud\controllers\AssetsController;
8+
use craft\models\Volume;
9+
use ReflectionMethod;
10+
11+
class AssetsControllerTest extends Unit
12+
{
13+
/**
14+
* @var \UnitTester
15+
*/
16+
protected $tester;
17+
18+
public function testVolumeSubpathReturnsEmptyStringOnCraft4(): void
19+
{
20+
$volume = new Volume();
21+
22+
if (method_exists($volume, 'getSubpath')) {
23+
$this->markTestSkipped('Craft 5 volume subpath behavior is covered by a separate test.');
24+
}
25+
26+
$this->assertSame('', $this->invokeVolumeSubpath($volume));
27+
}
28+
29+
public function testVolumeSubpathReturnsVolumeSubpathOnCraft5(): void
30+
{
31+
$volume = new Volume();
32+
33+
if (!method_exists($volume, 'getSubpath')) {
34+
$this->markTestSkipped('Craft 4 volumes do not implement getSubpath().');
35+
}
36+
37+
$volume->setSubpath('volume-prefix');
38+
39+
$this->assertSame('volume-prefix/', $this->invokeVolumeSubpath($volume));
40+
}
41+
42+
private function invokeVolumeSubpath(Volume $volume): string
43+
{
44+
$controller = new AssetsController('cloud-assets', Craft::$app);
45+
$method = new ReflectionMethod($controller, 'volumeSubpath');
46+
$method->setAccessible(true);
47+
48+
return $method->invoke($controller, $volume);
49+
}
50+
}

0 commit comments

Comments
 (0)