feat(bridge-vcr): PHP-VCR bridge#267
Open
roxblnfk wants to merge 10 commits into
Open
Conversation
Starter layout for testo/bridge-vcr: the `\Testo\Bridge\VCR` attribute (cassette name only for now), `VcrPlugin`, and a minimal `VcrInterceptor` that inserts/ejects the cassette around each tagged test. Concurrency isolation for VCR global state is left as a documented TODO. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Register testo/bridge-vcr in the release/split pipeline so its mirror repo (php-testo/bridge-vcr) is populated and versioned like every other bridge: - split-publish.yml: publish on `bridge-vcr-*` tags. - release-please config + manifest (seeded at 0.0.0 for the first release). - root composer.json: path-repo dev-alias and test autoload namespace. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Extend the #[VCR] attribute with `mode` (RecordMode enum: NewEpisodes/Once/ None) and `match` (list<Matcher>: method/url/host/query_string/body/ post_fields/headers/soap_operation), applied per test via VCR::configure() before the cassette is inserted. The enum is named `Matcher` because `Match` is a reserved keyword; there is no `path` matcher (Url covers it). Matchers are transport-agnostic — php-vcr normalizes curl/stream/soap into one Request — so this stays one attribute, not one per transport. The interceptor keeps ORDER_CLOSE_TO_TEST, which sits inside retry/repeat (each attempt re-inserts the cassette) and outside lifecycle hooks. Global- state isolation under concurrency remains a documented NOTE/TODO. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
php-vcr is process-global, so a #[VCR] test must own the single cassette exclusively. The interceptor now contains the test in a private fiber and drives it to completion, absorbing any suspension instead of propagating it to the parent scheduler, so the turnOn..turnOff window never yields control and no sibling can clobber the active cassette. A process-wide lock brackets the window and fails loudly if two #[VCR] windows ever overlap. Testo respects fibers but does not schedule tests concurrently itself, so this is future-proofing; a #[VCR] test is synchronous by design. Documented on the #[VCR] attribute. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Add a cassette-path option to VcrPlugin and the bridge's test suites: - Acceptance: a #[VCR(mode: None)] test replays a committed cassette with no network access. - Self: a class-level #[VCR] applies to every method, proving both the class fallback and that the exclusive window is released between sequential tests. - Feature: status mapping via TestRunner — replay hit passes, an unrecorded request in None mode is Error, an untagged test passes through untouched. Validated locally against php-vcr's Symfony 8 branch (feat/441-symfony-8); the integrated run wires in once php-vcr tags a Symfony 8 release. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Add testo/bridge-vcr (+ php-vcr) to the root require-dev and register its suites in testo.php, so the bridge's tests run in CI alongside the others. Until php-vcr tags a Symfony 8 release (php-vcr#442), composer resolves this by downgrading Symfony to 7.x; re-run CI once that release lands for a clean Symfony 8 run. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Model #[VCR] on the retry attribute: it now implements Interceptable and declares #[FallbackInterceptor(VcrInterceptor)], so the interceptor is wired into the pipeline (at its own order) only for tagged tests, receiving the resolved VCR instance via the constructor — no global registration and no per-test reflection. Class-vs-method precedence is handled by ConflictPolicy::Last (class = default, method overrides). VcrPlugin is now optional: it only sets a custom cassette path. #[VCR] works without it. Adds a method-overrides-class test (+ other.yml fixture); all 7 bridge tests pass locally. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Reflect the Interceptable refactor in the README: the attribute works without registering a plugin; VcrPlugin is only for a custom cassette path. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
…idge\VCR Follow the package-level-attribute layout used by plugin/retry: the #[VCR] attribute lives at the package root (VCR.php) and is loaded via composer's `files` autoload, while everything else moves under the matching namespace Testo\Bridge\VCR\ (src/ → PSR-4 "Testo\\Bridge\\VCR\\"). So the src namespace and the attribute FQN are consistent (VCR, not the earlier mixed-case Vcr). Renames RecordMode/Matcher/VcrPlugin into src/ root and VcrInterceptor into src/Internal/; updates all namespaces, use statements, test namespaces (Tests\Bridge\VCR) and the root autoload-dev entry. All 7 bridge tests pass. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PHP-VCR bridge (
testo/bridge-vcr)Adds a PHP-VCR bridge: mark a test with
#[VCR]and its HTTP interactions are recorded to a cassette on the first run and replayed offline afterwards.Closes #266. Full API + examples live in that issue body.
What's here
#[VCR]attribute (Testo\Bridge\VCR) —name,mode,match; on a method or a whole class (class-level = default, method overrides viaConflictPolicy::Last). Self-wiring: it isInterceptablewith a#[FallbackInterceptor], so the interceptor is inserted (at its own order) only for tagged tests, receiving the resolved attribute via the constructor — no global registration, no per-test reflection.RecordModeenum —NewEpisodes/Once/None(we deliberately omit php-vcr'sall).Matcherenum —Method/Url/Host/QueryString/Body/PostFields/Headers/SoapOperation. Transport-agnostic (php-vcr normalizes stream/curl/soap into one request), so it's one attribute, not one per transport.VcrPlugin(cassettePath:)— optional; only points php-vcr at a non-default cassette dir.#[VCR]works without it.#[VCR]test runs as an exclusive, non-yielding window (contained in its own fiber and driven to completion) plus a process lock. Fiber-aware for the future; synchronous today.resources/version.jsonseed, path-repo dev-alias; mirror repophp-testo/bridge-vcrcreated.require-devand its suites are registered intesto.php, so its tests run in CI with the rest.Tests
Acceptance / Self / Feature suites (offline replay, class-level default + method override, status mapping) — 7/7 green, validated locally against php-vcr's Symfony 8 branch.
Released php-vcr constrains
symfony/event-dispatcherto^4–^7, so on this Symfony 8 repo composer can only satisfy it by downgrading Symfony to 7.x (console → 8.0.x, event-dispatcher → 7.4). Symfony 8 support sits in php-vcr #442 (CI green across PHP 8.0–8.5, mergeable but awaiting maintainer approval — asked for a timeline).Follow-up once php-vcr tags a Symfony 8 release: bump the bridge's
php-vcr/php-vcrlower bound and just re-run CI here for a clean Symfony 8 run.