fix: break the TechLog CSRF bootstrap cycle and close the review's fix-round-1 items
C1 (Critical): getStudioSession was stamped with the same TECH_LOG_STUDIO_SESSION auth profile as every other Studio operation, and that profile requires the CSRF header it is getStudioSession's own job to issue -- an unconditional cycle that recursed without bound in HTTP mode. Fixed with a credential-free TECH_LOG_STUDIO_BOOTSTRAP auth profile for getStudioSession alone, a synchronous re-entrancy guard in createCsrfTokenProvider as defense in depth, and a throwing stub in place of the prior `let x!: T` assertion. Added a composition-level regression test that wires the real executor, CSRF provider, and credential-attach function together and proves getStudioSession dispatches exactly once while its token reaches both a JSON operation and the multipart upload. Also: invalidate the cached CSRF token on a 401/403 from the upload path (I2), a throwing useStudioAssetGateway() accessor so Task 11 cannot silently compile a null-gateway UI (I3), and the M1-M5 minors from the review (guard a malformed success body, cover the untested error fallbacks, align aborted uploads with the JSON path's non-retryable CANCELLED mapping, derive the credential header name from one source instead of two, and correct the adapter review doc's operation count). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c9c832c365
commit
2cab4974b7
@@ -0,0 +1,65 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import type { ReactNode } from "react";
|
||||
import { test } from "vitest";
|
||||
|
||||
import type { StudioAssetGateway } from "../../../src/features/tech-log/application/ports/studio-asset-gateway.ts";
|
||||
import {
|
||||
StudioContext,
|
||||
useStudioAssetGateway,
|
||||
type StudioContextValue,
|
||||
} from "../../../src/features/tech-log/presentation/studio/use-studio.ts";
|
||||
|
||||
/**
|
||||
* I3 (Task 7 fix round 1). `StudioContextValue.assetGateway` stays nullable
|
||||
* so the many test harnesses that render `StudioProvider` without a
|
||||
* `createAssetGateway` prop keep compiling — but that nullability would let
|
||||
* Asset UI (Task 11) write `if (!assetGateway) return null` and ship a
|
||||
* confusingly empty screen with the type checker satisfied. This pins the
|
||||
* throwing accessor Asset UI must use instead.
|
||||
*/
|
||||
function contextValue(
|
||||
assetGateway: StudioAssetGateway | null,
|
||||
): StudioContextValue {
|
||||
return {
|
||||
gateway: {} as never,
|
||||
assetGateway,
|
||||
resolvePublishedLabel: () => undefined,
|
||||
now: () => new Date("2026-08-14T01:00:00.000Z"),
|
||||
editor: null,
|
||||
requestAnnouncement: "",
|
||||
setRequestAnnouncement: () => {},
|
||||
navigateInternal: () => {},
|
||||
beginEditor: () => {},
|
||||
updateEditorDraft: () => {},
|
||||
setEditorStatus: () => {},
|
||||
clearEditor: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
function wrapperFor(assetGateway: StudioAssetGateway | null) {
|
||||
return function Wrapper({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<StudioContext.Provider value={contextValue(assetGateway)}>
|
||||
{children}
|
||||
</StudioContext.Provider>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
test("returns the asset gateway when the provider supplied one", () => {
|
||||
const assetGateway = {} as StudioAssetGateway;
|
||||
const { result } = renderHook(() => useStudioAssetGateway(), {
|
||||
wrapper: wrapperFor(assetGateway),
|
||||
});
|
||||
assert.equal(result.current, assetGateway);
|
||||
});
|
||||
|
||||
test("throws a clear error instead of returning null when no asset gateway was supplied", () => {
|
||||
assert.throws(
|
||||
() => renderHook(() => useStudioAssetGateway(), { wrapper: wrapperFor(null) }),
|
||||
/useStudioAssetGateway must be used within a StudioProvider that was given a createAssetGateway prop/,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user