Skip to content

Add test infrastructure and PHPUnit suite - #4

Merged
erikfrerejean merged 7 commits into
3from
feature/tests
May 7, 2026
Merged

Add test infrastructure and PHPUnit suite#4
erikfrerejean merged 7 commits into
3from
feature/tests

Conversation

@erikfrerejean

Copy link
Copy Markdown
Member

Summary

Brings the module up to the same QA-gate parity as silverstripe-menustructure: a real test runtime, automated checks, and the first PHPUnit suite. Two production bugs surfaced during test development and are fixed in dedicated commits.

Production fixes (caught by tests)

  • Svg constructor crashed on malformed input$this->logger was accessed inside a catch block, but Silverstripe DI only assigns $dependencies after the constructor returns. Any unparseable SVG would have thrown 'property accessed before initialization'. Parsing is now deferred to first access of getWidth/getHeight.
  • MigrateCurrentSvgsTask was incompatible with SilverStripe 6 — typed property mismatch on $title plus the wrong override target (run() is now concrete in BuildTask; subclasses implement execute(InputInterface, PolyOutput): int). The recent Add SilverStripe 6 compatibility commit didn't catch this because there were no tests. Now namespaced, typed, and using the SS6 API.

Infrastructure

  • .docker/ — FrankenPHP + MySQL stack (mirrors silverstripe-menustructure), per-worktree port allocation, db-init script that grants ss_tmpdb_* privileges.
  • .docker/app/ — Silverstripe project skeleton: composer.json (recipe-cms + dev deps), phpstan.neon.dist (level max with 100% type-coverage), phpunit.xml.dist (PHPUnit 11), rector.php.
  • .github/workflows/ci.yml — two jobs: static-analysis (PHPStan + Rector dry-run) and phpunit (matrix on PHP 8.3 / 8.4 / 8.5, junit output, dorny/test-reporter).
  • Makefileup, down, destroy, test, coverage, analyse, rector, rector-dry, dev-build, sh.

Composer narrowing

  • php ^8.1^8.3
  • silverstripe/framework ^5 || ^6^6
  • Drops friendsofphp/php-cs-fixer (replaced by Rector via wernerkrauss/silverstripe-rector in the dev project), drops silverstripe/mimevalidator from dev deps (still listed under suggest).

This is a breaking change for any consumer still on SilverStripe 5; in practice the prior ^5 || ^6 claim was already inaccurate because MigrateCurrentSvgsTask was broken on SS6.

Tests

12 tests, 25 assertions, 95% line / 86% method coverage for the production code:

  • tests/Assets/SvgTest.php (10 tests) — empty-file branches, getTag/getWidth/getHeight against a valid SVG fixture, manipulate() pass-through, onBeforeWrite() strips <script>/onclick/javascript: from a malicious fixture, lazy-loading config, and a mocked LoggerInterface to verify malformed content is logged without throwing.
  • tests/Task/MigrateCurrentSvgsTaskTest.php (2 tests) — fixture with mixed .svg/.png/.pdf File rows; verifies only .svg rows have ClassName flipped.
  • tests/fixtures/ — sample.svg, malicious.svg, invalid.svg, plus the migrate task YAML.

Test plan

  • make up && make analyse — PHPStan green
  • make rector-dry — Rector clean
  • make test — 12 / 12 passing
  • make coverage — coverage summary printed, HTML at coverage/html
  • CI runs both jobs across PHP 8.3 / 8.4 / 8.5 and reports junit results

The constructor accessed $this->logger inside a catch block, but Silverstripe
DI assigns properties from $dependencies *after* the constructor returns. Any
malformed SVG would have thrown 'property accessed before initialization' on
load. Parsing is now deferred until first access to getWidth/getHeight, by
which point logger is wired up.

Also tightens types so static analysis at level max with 100% type-coverage
passes: handle nullable filename in onBeforeWrite, and switch manipulate()
parameters to mixed (parent is untyped, narrowing isn't allowed).
Narrows composer constraints to silverstripe/framework ^6 and php ^8.3 so the
test matrix and dev environment can target a single supported stack. This
matches the SilverStripe 6 system requirements and the conventions used by
other recent WeDevelop modules (e.g. silverstripe-menustructure).

Also drops friendsofphp/php-cs-fixer in favour of Rector for code-style
automation, and removes silverstripe/mimevalidator from dev deps (it stays
as an optional consumer dependency via "suggest").
The previous implementation was incompatible with SilverStripe 6: BuildTask
now declares `protected string $title` and `protected static string $description`,
the run() method takes (InputInterface, PolyOutput) and is concrete, and
subclasses must implement `execute(InputInterface, PolyOutput): int`. Loading
the task on SS6 produced a fatal at bootstrap.

This change adds proper namespace, typed properties, and the new execute()
signature. Rector's BuildTaskUpdateRector also renames the routing constant
from `$segment` to `$commandName`.

The DB columns and table name are now ANSI-quoted to match the rest of the
codebase and avoid future schema-aware issues.
The previous Dockerfile shipped only a PHP 8.1 CLI container with no web
server or database. That was sufficient for static analysis but left
PHPUnit, Rector, and any integration testing without a real Silverstripe
runtime. The new setup mirrors silverstripe-menustructure and gives us:

  - FrankenPHP-based app container (php:8.3 by default, configurable via
    PHP_VERSION arg) with intl, gd, mysqli, pdo_mysql, zip, bcmath, exif,
    pcov for coverage.
  - MySQL 8 service with a db-init script that grants the silverstripe
    user the privileges needed for SapphireTest's ss_tmpdb_* databases.
  - Per-worktree port allocation (.docker/env.sh) so multiple checkouts
    can run in parallel without colliding.
  - The Silverstripe project skeleton (composer.json, phpstan.neon.dist,
    phpunit.xml.dist, rector.php) lives under .docker/app/ and the module
    itself is symlinked in via composer's path repository.

Makefile is rewritten with up/down/destroy/test/coverage/analyse/rector/
rector-dry/dev-build/sh targets. PHPStan and php-cs-fixer configs at the
repo root are removed in favour of the new locations.
Covers the two production classes:

  tests/Assets/SvgTest.php
    - Empty-file branches for getTag/getWidth/getHeight return zero values.
    - getTag/getWidth/getHeight parse a valid SVG fixture and report the
      authored 100x50 dimensions.
    - manipulate() returns the wrapped DBFile unchanged.
    - onBeforeWrite() strips <script>, onclick, and javascript: from a
      malicious SVG fixture before the file is persisted.
    - Lazy-loading config flag stays disabled.
    - Malformed SVG content is logged via the injected LoggerInterface
      (mocked) and does not throw.

  tests/Task/MigrateCurrentSvgsTaskTest.php
    - .svg files have their ClassName flipped to Svg.
    - Non-svg files (.png, .pdf) are left untouched.

Three SVG fixtures (sample, malicious, invalid) and one YAML fixture for
the migrate task live under tests/fixtures/.
Two jobs:

  - static-analysis: brings up the app container, then runs make analyse
    (PHPStan at level max with 100% type-coverage) and make rector-dry.

  - phpunit: matrixed across PHP 8.3, 8.4, and 8.5; brings up the full
    stack, runs PHPUnit with junit output, and surfaces results via
    dorny/test-reporter so failures are visible in PR checks.

Concurrency is scoped to the workflow + ref so superseded pushes are
cancelled.
@erikfrerejean
erikfrerejean changed the base branch from main to 3 May 7, 2026 12:13
@erikfrerejean
erikfrerejean merged commit 3c87850 into 3 May 7, 2026
4 checks passed
@erikfrerejean
erikfrerejean deleted the feature/tests branch May 7, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant