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
+34 -5
View File
@@ -17,7 +17,9 @@ const bankCardsKey = apiQueryClient.queryOptions("get", "/api/v1/bank-cards", {
params: { query: { includeArchived: "false" } },
}).queryKey;
function createAuthWrapper(queryClient: ReturnType<typeof createTestQueryClient>): FC<PropsWithChildren> {
function createAuthWrapper(
queryClient: ReturnType<typeof createTestQueryClient>,
): FC<PropsWithChildren> {
return props => (
<QueryClientProvider client={queryClient}>
<AuthProvider>{props.children}</AuthProvider>
@@ -27,7 +29,10 @@ function createAuthWrapper(queryClient: ReturnType<typeof createTestQueryClient>
describe("AuthProvider", () => {
it("authorizes requests after restoring the initial session", async () => {
let authorization: null | string = /* eslint-disable-line unicorn/no-null -- Exercise the explicit null returned by the production API/DOM contract. */ null;
let authorization: null | string
// eslint-disable-next-line unicorn/no-null -- Exercise the explicit null returned by the production API/DOM contract.
= null;
server.use(
http.post("*/api/v1/auth/refresh", () =>
HttpResponse.json({
@@ -37,11 +42,13 @@ describe("AuthProvider", () => {
),
http.get("*/api/v1/brokerage-accounts", ({ request }) => {
authorization = request.headers.get("Authorization");
return HttpResponse.json(resource([]));
}),
);
const queryClient = createTestQueryClient();
const wrapper: FC<PropsWithChildren> = createAuthWrapper(queryClient);
const { rerender, result } = renderHook(
() => ({ ...useAuth(), queryClient: useQueryClient() }),
{ wrapper },
@@ -80,6 +87,7 @@ describe("AuthProvider", () => {
server.use(
http.post("*/api/v1/auth/refresh", () => {
refreshCalls += 1;
return refreshCalls === 1
? HttpResponse.json({
accessToken: "restored-token",
@@ -119,15 +127,18 @@ describe("AuthProvider", () => {
),
);
const queryClient = createTestQueryClient();
const statusKey = apiQueryClient.queryOptions(
"get",
"/api/v1/tbank-connection",
).queryKey;
const portfolioKey = apiQueryClient.queryOptions(
"get",
"/api/v1/brokerage-accounts/{accountId}/portfolio",
{ params: { path: { accountId: "account-1" } } },
).queryKey;
queryClient.setQueryData(statusKey, {
connected: true,
connectedAt: "2026-08-14T00:00:00Z",
@@ -157,7 +168,9 @@ describe("AuthProvider", () => {
])(
"serializes late refresh (failure=%s, login=%s)",
async (failure, loginAfterLogout) => {
const { promise: pending, resolve: release } = Promise.withResolvers<void>();
const { promise: pending, resolve: release }
= Promise.withResolvers<void>();
let calls = 0;
const order: string[] = [];
server.use(
@@ -167,6 +180,7 @@ describe("AuthProvider", () => {
await pending;
order.push("refresh-cookie");
}
return failure && calls > 1
? HttpResponse.error()
: HttpResponse.json({
@@ -176,10 +190,12 @@ describe("AuthProvider", () => {
}),
http.post("*/api/v1/auth/logout", () => {
order.push("logout-cookie");
return new HttpResponse(null, { status: 204 });
}),
http.post("*/api/v1/auth/login", () => {
order.push("login-cookie");
return HttpResponse.json({
accessToken: "b",
user: { email: "b@example.com", id: "b" },
@@ -193,11 +209,13 @@ describe("AuthProvider", () => {
const wrapper: FC<PropsWithChildren> = createAuthWrapper(queryClient);
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => expect(result.current.ready).toBe(true));
const oldRequest = apiClient
.GET("/api/v1/brokerage-accounts")
// eslint-disable-next-line unicorn/prefer-await -- Attach rejection handling before releasing the deliberately pending query.
.catch(() => {});
await waitFor(() => expect(order).toContain("refresh-start"));
let logout!: Promise<void>;
act(() => {
@@ -214,8 +232,14 @@ describe("AuthProvider", () => {
release();
await Promise.all([oldRequest, logout, login]);
});
expect(result.current.user?.id ?? /* eslint-disable-line unicorn/no-null -- Exercise the explicit null returned by the production API/DOM contract. */ null).toBe(
loginAfterLogout ? "b" : /* eslint-disable-line unicorn/no-null -- Exercise the explicit null returned by the production API/DOM contract. */ null,
expect(
result.current.user?.id
// eslint-disable-next-line unicorn/no-null -- Exercise the explicit null returned by the production API/DOM contract.
?? null,
).toBe(
loginAfterLogout
? "b"
: null, // eslint-disable-line unicorn/no-null -- Exercise the explicit null returned by the production API/DOM contract.
);
expect(order).toEqual([
"refresh-start",
@@ -244,17 +268,20 @@ describe("AuthProvider", () => {
await act(() => result.current.refresh());
expect(queryClient.getQueryData(bankCardsKey)).toEqual([{ id: "a-card" }]);
let release!: (value: string) => void;
const pending = queryClient
.query({
queryFn: () => {
const deferred = Promise.withResolvers<string>();
release = deferred.resolve;
return deferred.promise;
},
queryKey: ["pending-finance"],
})
// eslint-disable-next-line unicorn/prefer-await -- Attach rejection handling before releasing the deliberately pending query.
.catch(() => {});
identity = "b";
await act(() => result.current.refresh());
release("a-data");
@@ -278,6 +305,7 @@ describe("AuthProvider", () => {
);
const queryClient = createTestQueryClient();
const wrapper: FC<PropsWithChildren> = createAuthWrapper(queryClient);
const { result } = renderHook(
() => ({
auth: useAuth(),
@@ -289,6 +317,7 @@ describe("AuthProvider", () => {
}),
{ wrapper },
);
await waitFor(() =>
expect(result.current.cards.data?.[0].id).toBe("a-card"),
);