refactor: enforce frontend view model boundaries
Frontend verify / commits (pull_request) Successful in 28s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 8m19s

This commit is contained in:
2026-09-28 18:48:23 +03:00
parent 5b69003516
commit ca0ad4f824
287 changed files with 8558 additions and 4538 deletions
@@ -25,25 +25,32 @@ describe("sync connection and session boundaries", () => {
refresh: () => Promise.resolve(true),
});
const { promise: blocked, resolve: release } = Promise.withResolvers<void>();
const { promise: blocked, resolve: release }
= Promise.withResolvers<void>();
const accepted = {
id: "work",
now: "2026-09-25T12:00:00Z",
scope: { screen: "accounts" },
};
let started = 0;
let isChanged = false;
server.use(
http.post("*/api/v1/tbank-sync", async () => {
started += 1;
await blocked;
return HttpResponse.json(accepted, { status: 202 });
}),
http.get("*/api/v1/tbank-sync/:id", async () => {
const now = isChanged ? "2026-09-25T12:01:00Z" : accepted.now;
started += 1;
await blocked;
return HttpResponse.json((/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
return HttpResponse.json(
{
...accepted,
errorCode: null,
@@ -51,21 +58,26 @@ describe("sync connection and session boundaries", () => {
progress: { completed: 1, discovering: false, total: 1 },
resources: [],
state: "succeeded",
}
/* eslint-enable unicorn/no-null */));
},
/* eslint-enable unicorn/no-null */
);
}),
);
const { queryClient, wrapper } = createQueryWrapper();
const published: string[] = [];
const unsubscribe = queryClient.getQueryCache().subscribe((event) => {
if ((event.query.queryKey as readonly unknown[])[1]
!== "/api/v1/tbank-sync/{id}") {
if (
(event.query.queryKey as readonly unknown[])[1]
!== "/api/v1/tbank-sync/{id}"
) {
return;
}
const data = event.query.state.data as undefined | { now?: string };
if (data?.now) published.push(data.now);
});
const { result } = renderHook(
() => ({
mutation: useTbankSync(),
@@ -73,39 +85,49 @@ describe("sync connection and session boundaries", () => {
}),
{ wrapper },
);
const screen: InvestmentScreen = { screen: "accounts" };
let mutation!: Promise<unknown>;
act(() => {
mutation = result.current.mutation.mutateAsync({ body: screen });
});
// eslint-disable-next-line vitest/valid-expect -- Attach rejection handling before releasing the request; the assertion is awaited below.
const discarded = expect(mutation).rejects.toMatchObject({
name: "AbortError",
});
await waitFor(() => expect(started).toBe(2));
await act(async () => {
isChanged = true;
if (boundary === "connection")
commitTbankConnectionBoundary(queryClient, (/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
commitTbankConnectionBoundary(
/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
queryClient,
{
connected: false,
connectedAt: null,
}
/* eslint-enable unicorn/no-null */));
},
/* eslint-enable unicorn/no-null */
);
else {
generation += 1;
queryClient.clear();
}
release();
await blocked;
});
await discarded;
expect(result.current.mutation.data).toBeUndefined();
const key = apiQueryClient.queryOptions(
"get",
"/api/v1/tbank-sync/{id}",
{ params: { path: { id: "work" } } },
).queryKey;
// A new request may start after a successful connection boundary; an old result cannot win it.
if (boundary === "session")
// eslint-disable-next-line vitest/no-conditional-expect -- The fixed table case selects the expected outcome; every case asserts its own branch.