Deprecated & Removed APIs

aimock keeps mocking provider surfaces that upstream has deprecated, and declines to mock the success path of surfaces upstream has removed. Deprecated is not the same as removed, and the difference decides what belongs in the mock.

Deprecated Is Not Removed

The two words get used interchangeably in release notes, but for a mock they describe opposite situations. A deprecated endpoint still answers: the provider has announced an intent to retire it, published a migration path, and kept serving traffic in the meantime. A removed endpoint does not answer: the URL is gone, and calling it produces a 404 or a 410 rather than a result.

aimock treats those two states differently because a mock's users are in different positions in each one. Against a deprecated API they still have working production code. Against a removed one they do not.

Why Deprecated Surfaces Stay

Deprecation is the moment when test scaffolding matters most, not least. Someone with production code written against a deprecating API is in the middle of migrating, and a migration means running the old path and the new path side by side — comparing behavior, keeping the old one green while the new one lands, and proving the switchover before shipping it. Pulling the mock out from under that work removes the tool exactly when it is needed.

It also helps to be precise about what aimock users are testing. They are testing their own code, not the provider. Upstream deciding to deprecate an endpoint does not delete that endpoint from anybody's codebase. There is a long tail in which the API still serves, real applications still call it, and those applications still need a deterministic local target to test against.

Finally, the decision is asymmetric. Declining to add a surface costs a contributor a pull request and nothing else. Deleting a surface aimock already ships takes a route out from under everyone whose tests are wired to it. So the bar runs in one direction: be quick to decline, slow to delete. That asymmetry governs deletion, not honesty — when upstream kills an endpoint, the route stays and starts telling the truth about it.

Why a Removed API Is Different

A mock's entire contract is to behave like the real thing. When the real endpoint has stopped answering, a mock that returns 200 with a plausible body is manufacturing false green — a test that passes for code that cannot possibly work in production. The suite reports health for a call path that is already dead.

That is strictly worse than having no mock at all. With no mock, the test fails loudly in CI, where failures are cheap and someone is watching. With a lying mock, the failure moves to deploy, where it is expensive and nobody expected it.

The same reasoning reaches a surface aimock already ships. Once upstream pulls the endpoint, the shipped mock is the one doing the lying, and correcting it is not our breaking change — upstream's removal is. The endpoint is gone whether or not aimock reflects it. The user's code is already broken; the only thing still in our hands is whether they find that out in CI or in production.

A major version is not the instrument for it either. A major protects aimock's own interface — the CLI, the configuration, the way you start it and point a client at it. Fidelity to upstream is not our interface, it is our job, and correcting a mock to match reality is not us breaking a promise. Treating it as one would also be unworkable: providers retire things constantly, and tying every upstream removal to a major bump makes majors either meaningless or perpetual. The conversion ships in a normal release. It is consumer-visible and earns a loud CHANGELOG entry, but it is not a major-version event.

The objection to a removed surface is never “this API is old.” Age is not disqualifying — plenty of old endpoints still serve, and aimock mocks them. The objection is that the mock would assert something untrue about the world.

The Drift Consequence

A removed API also cannot be drift-tested. Drift detection works by comparing mock output against a live response from the real provider; when nothing live remains to compare against, there is no comparison to make. A dead surface left in the drift pipeline produces a permanent vacuous pass — a green check that verifies nothing — and that corrupts the signal that tells us the rest of the suite is healthy. Dead surfaces are therefore excluded from drift explicitly, rather than left to pass neutrally.

The Matrix

Two questions decide every case: what state is the surface in upstream, and are we looking at a new contribution or something aimock already ships?

Upstream state New contribution Surface already shipped
GA Normal bar Maintain
Deprecated, still serving Accept with a concrete user story; label it Keep; freeze feature growth; note in docs
Removed / sunset Decline the success-path mock Convert to the sunset behaviour in a normal release

Three Rules

  1. Never mock success for an endpoint that no longer serves. Faithfulness is the product, and it binds a surface aimock already ships exactly as it binds one being proposed.
  2. A sunset mock is legitimate if it mocks the sunset. Returning the 404 or 410 the real API now returns is a faithful mock, and a useful one — it lets users test that their code handles the removal. For a surface aimock already ships, that conversion is not merely permitted; it is the required path.
  3. Dead surfaces are explicitly excluded from drift, never left to pass neutrally.

What to Expect as a Contributor

If you are proposing a mock for a surface that upstream has deprecated but is still serving, bring a concrete user story: whose code calls this, and what are they testing. That is the whole bar. The mock gets labelled as covering a deprecated surface so nobody mistakes it for a recommended path, and it will not grow new features afterwards — it is there to support migration, not to keep pace with an API that is on its way out.

If you are proposing a mock for a surface upstream has removed, the success path will be declined. Reframing the same contribution as a sunset mock — one that returns the error the real API now returns — is welcome, and is the version that actually helps users, because handling the removal is the only thing left to test.

If a surface aimock already ships gets removed upstream, the route does not vanish from your next release — what it returns changes. It starts serving the rejection the real API now serves, it leaves the drift pipeline, and it stays there so you can still test that your code handles the shutdown. That change is consumer-visible and the CHANGELOG says so plainly, but it rides a normal release: matching upstream reality is aimock doing its job, not aimock breaking its interface.