fix: let the mock's dependency revision observe the Asset store
`createMockStudioGateway`'s default `dependencyRevision.current()` returned a literal constant and never consulted `dependencies.assets`, so the staleness guards in `createStudioPreview` and `publishStudioDocument` could not fire for an asset-store mutation between validate and preview. The live case: a document references an evidence key with `alt=""` and the store holds only a `decorative: true` Asset for it, so validation is correctly VALID with zero issues. A newer `decorative: false` Asset then wins that key. Preview succeeds, the figure resolves to `decorative: false, alt: ""`, and publish snapshots it verbatim -- a meaningful image with no accessible name, validated clean, with nothing anywhere reporting an error. The default now folds the Asset store into the revision. Each Asset is reduced to the fields the mock's own validation and projection read -- identity and resolution order (`id`, `assetKey`, `updatedAt`), resolvability (`managementStatus`, `publicPath`), the alt rule (`decorative`, `altText`), and what the published `ResolvedAsset` carries (`mediaType`, `width`, `height`) -- canonicalized with `stableStringify`, sorted, and folded into a 128-bit FNV-1a digest. Sorting the canonical strings is what makes it order-independent, which this mock's reproducibility across the suite depends on. It is a projection rather than the whole record because the excluded fields cost sensitivity without buying any. `usageCount` is the clearest: it counts referencing documents, so on a real backend publishing any document that uses an Asset would invalidate every other author's in-flight validation, while changing nothing the validator or renderer reads. An empty store still reports the bare catalog constant -- that is the world the seeded fixtures were validated against, and `fixtures.ts` now shares the one definition rather than retyping the literal. `findResolvableAsset` is untouched: it is a single-point-in-time predicate and is correct as it stands. Seeing a change *between* two points is the revision's job. A caller-supplied `dependencyRevision` still wins outright. The asset-picker test that reached `failureOf`'s `ContentFormatError` branch did so only because the revision could not move; it now pins its own revision to keep reaching the projection, and asserts the problem detail so the two 409 paths cannot be confused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cf45bcc7dc
commit
d84b57bb3f
@@ -945,10 +945,23 @@ test("a READY asset whose publicPath satisfies a different key's legacy conventi
|
||||
// A deterministic content failure between validate and preview is exactly
|
||||
// what VALIDATION_STALE/409 means, and it is not retryable without
|
||||
// re-validating first.
|
||||
//
|
||||
// The alignment follow-up that taught the *default* dependency revision to
|
||||
// read the Asset store closed exactly the gap this test used to reach the
|
||||
// projection through: deleting the Asset now moves the revision, so the guard
|
||||
// fires first and `failureOf`'s ContentFormatError branch would never run
|
||||
// again. That branch still has to hold -- a projection can refuse for reasons
|
||||
// the revision cannot see (a directive the parser rejects against a catalog
|
||||
// row) -- so this test pins the revision itself and keeps reaching the
|
||||
// projection, and asserts the *detail* to prove which of the two paths
|
||||
// produced the 409. `mock-dependency-revision.test.ts` covers the guard path.
|
||||
test("idempotent() maps a deterministic content failure to VALIDATION_STALE, never lets a raw error escape the port", async () => {
|
||||
const asset = assetFixture({ assetKey: "race-check" });
|
||||
const assets = new Map<string, Asset>([[asset.assetKey, asset]]);
|
||||
const gateway = createMockStudioGateway({ assets });
|
||||
const gateway = createMockStudioGateway({
|
||||
assets,
|
||||
dependencyRevision: { current: () => "pinned-so-the-guard-cannot-fire" },
|
||||
});
|
||||
|
||||
const created = await gateway.createDocument(
|
||||
{
|
||||
@@ -986,6 +999,9 @@ test("idempotent() maps a deterministic content failure to VALIDATION_STALE, nev
|
||||
assert.equal(error.code, "VALIDATION_STALE");
|
||||
assert.equal(error.status, 409);
|
||||
assert.equal(error.retryable, false);
|
||||
// The projection's failure, wrapped by `failureOf` -- not the guard's
|
||||
// own "Current validation without errors is required."
|
||||
assert.match(error.problem.detail ?? "", /no longer renders against current dependencies/);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user