diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9e6af5d3..4345a2e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,6 +42,8 @@ jobs: - name: Tests run: composer test + env: + EMBED_STRICT_CACHE: 1 phpstan: name: PHPStan Static Analysis diff --git a/README.md b/README.md index 538a0c46..57dab295 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,74 @@ Note: The built-in detectors does not require settings. This feature is only for --- +## Testing + +### Running tests + +```bash +composer test +# or +./vendor/bin/phpunit +``` + +### Snapshot modes + +The test suite uses cached HTTP responses and fixtures to avoid network requests during testing. You can control this behavior using environment variables: + +| Environment Variable | Description | +|---------------------|-------------| +| `UPDATE_EMBED_SNAPSHOTS=1` | Fetch from network and update both cache and fixtures | +| `EMBED_STRICT_CACHE=1` | Fail if cache or fixture doesn't exist (useful for CI) | + +By default (no environment variables set), tests read from cache and generate missing files automatically. + +**Note:** If both `UPDATE_EMBED_SNAPSHOTS` and `EMBED_STRICT_CACHE` are set, `UPDATE_EMBED_SNAPSHOTS` takes precedence. + +### Cache structure + +The test framework uses two types of cached data: + +- **Response cache** (`tests/cache/`): Cached HTTP responses from external sites +- **Fixtures** (`tests/fixtures/`): Expected test results (metadata extracted from cached responses) + +### How to update cache + +#### When a website changes its HTML structure + +If a website updates its HTML and you need to update the cached response and fixture: + +```bash +# Update cache and fixture for a specific test +UPDATE_EMBED_SNAPSHOTS=1 ./vendor/bin/phpunit --filter testYoutube +``` + +#### When adding a new test + +After adding a new URL to test: + +```bash +# This will fetch the response and create both cache and fixture +UPDATE_EMBED_SNAPSHOTS=1 ./vendor/bin/phpunit --filter testNewSite +``` + +#### Update all caches at once + +To refresh all cached responses and fixtures from the network: + +```bash +UPDATE_EMBED_SNAPSHOTS=1 ./vendor/bin/phpunit +``` + +#### For CI environments + +Ensure all tests run strictly from cache (fail if any cache is missing): + +```bash +EMBED_STRICT_CACHE=1 ./vendor/bin/phpunit +``` + +--- + [ico-version]: https://poser.pugx.org/embed/embed/v/stable [ico-license]: https://poser.pugx.org/embed/embed/license [ico-downloads]: https://poser.pugx.org/embed/embed/downloads diff --git a/tests/PagesTestCase.php b/tests/PagesTestCase.php index 7abdb0ff..46db1772 100644 --- a/tests/PagesTestCase.php +++ b/tests/PagesTestCase.php @@ -15,7 +15,27 @@ abstract class PagesTestCase extends TestCase { + /** + * Cache mode + * -1 = Read from cache (throws an exception if the cache doesn't exist) + * 0 = Read from cache (generate the files the first time) + * 1 = Read from net without overriding the cache files + * 2 = Read from net and override the cache files + * + * Can be overridden by environment variables: + * - UPDATE_EMBED_SNAPSHOTS=1 : Fetch from network and update both cache and fixtures + * - EMBED_STRICT_CACHE=1 : Fail if cache or fixture doesn't exist (mode -1) + */ const CACHE = 0; + + /** + * Fixtures mode + * 0 = Do not override the fixtures + * 1 = Override the fixtures + * + * Can be overridden by environment variable: + * - UPDATE_EMBED_SNAPSHOTS=1 : Update both cache and fixtures + */ const FIXTURES = 0; private const DETECTORS = [ @@ -42,6 +62,45 @@ abstract class PagesTestCase extends TestCase private static Embed $embed; + /** + * Get cache mode from environment variables or class constant + */ + protected static function getCacheMode(): int + { + // UPDATE_EMBED_SNAPSHOTS=1 : Fetch from network and override cache + if (getenv('UPDATE_EMBED_SNAPSHOTS')) { + return 2; + } + + // EMBED_STRICT_CACHE=1 : Fail if cache doesn't exist + if (getenv('EMBED_STRICT_CACHE')) { + return -1; + } + + return static::CACHE; + } + + /** + * Get fixtures mode from environment variable or class constant + */ + protected static function getFixturesMode(): int + { + // UPDATE_EMBED_SNAPSHOTS=1 : Override fixtures with new results + if (getenv('UPDATE_EMBED_SNAPSHOTS')) { + return 1; + } + + return static::FIXTURES; + } + + /** + * Check if strict cache mode is enabled + */ + protected static function isStrictMode(): bool + { + return (bool) getenv('EMBED_STRICT_CACHE'); + } + private static function getEmbed(): Embed { if (isset(self::$embed)) { @@ -49,7 +108,7 @@ private static function getEmbed(): Embed } $dispatcher = new FileClient(__DIR__.'/cache'); - $dispatcher->setMode(static::CACHE); + $dispatcher->setMode(self::getCacheMode()); return self::$embed = new Embed(new Crawler($dispatcher), self::getExtractorFactory()); } @@ -63,7 +122,18 @@ protected function assertEmbed(string $url) $data = self::getData($extractor); $expected = self::readData($uri); - if (!$expected || static::FIXTURES === 1) { + // In strict mode, fail if fixture is missing + if (!$expected && self::isStrictMode()) { + $this->fail("Fixture not found for {$url}"); + } + + // Update mode: save fixture and skip + if (self::getFixturesMode() === 1) { + self::writeData($uri, $data); + echo PHP_EOL."Save fixture: {$url}"; + $this->markTestSkipped('Skipped assertion for '.$url); + } elseif (!$expected) { + // Missing fixture in non-strict mode: create and skip self::writeData($uri, $data); echo PHP_EOL."Save fixture: {$url}"; $this->markTestSkipped('Skipped assertion for '.$url);