Files
tech-log-frontend/tests/features/tech-log/use-studio-asset-gateway.test.tsx
T
DongHyeonkaandClaude Opus 5 2cab4974b7 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>
2026-08-18 03:07:02 +09:00

66 lines
2.2 KiB
TypeScript

// @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/,
);
});