feat(investments): warn and offer retry after exhausted read retries (WealthLedger/frontend#55) (#136)
This commit was merged in pull request #136.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { MemoryRouter, Route, Routes } from "react-router";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { configureAuthClient } from "@/shared/api";
|
||||
import { createTestQueryClient } from "@/test/query";
|
||||
import { resource } from "@/test/resource";
|
||||
import { server } from "@/test/server";
|
||||
|
||||
import { AccountEventsPage } from "./AccountEventsPage";
|
||||
|
||||
// Automatic read retries wait 1s + 2s before the failure is final.
|
||||
const RETRIES_EXHAUSTED_MS = 6000;
|
||||
const TEST_MS = 10_000;
|
||||
|
||||
const snapshot = resource({
|
||||
events: [
|
||||
{
|
||||
date: "2026-10-01",
|
||||
dateKind: "event",
|
||||
displayName: "Synthetic coupon",
|
||||
figi: null,
|
||||
instrumentUid: null,
|
||||
isin: null,
|
||||
logoName: null,
|
||||
projectedAmount: { currency: "RUB", value: "123.45" },
|
||||
ticker: "SYN",
|
||||
type: "coupon",
|
||||
},
|
||||
],
|
||||
incomeTotalsByCurrency: [{ currency: "RUB", value: "123.45" }],
|
||||
});
|
||||
|
||||
function renderEvents() {
|
||||
configureAuthClient({
|
||||
accessToken: () => "token",
|
||||
clear: vi.fn(),
|
||||
refresh: () => Promise.resolve(true),
|
||||
});
|
||||
const queryClient = createTestQueryClient();
|
||||
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MantineProvider env="test">
|
||||
<MemoryRouter initialEntries={["/accounts/account-1/events"]}>
|
||||
<Routes>
|
||||
<Route
|
||||
element={<AccountEventsPage />}
|
||||
path="/accounts/:accountId/events"
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
return { queryClient };
|
||||
}
|
||||
|
||||
describe("AccountEventsPage read retry", () => {
|
||||
it(
|
||||
"offers retry after an initial failure and recovers in place",
|
||||
{ timeout: TEST_MS },
|
||||
async () => {
|
||||
let isFailing = true;
|
||||
let requests = 0;
|
||||
server.use(
|
||||
http.get("*/api/v1/brokerage-accounts/:accountId/events", () => {
|
||||
requests += 1;
|
||||
|
||||
return isFailing
|
||||
? HttpResponse.json({ code: "TBANK_UNAVAILABLE" }, { status: 503 })
|
||||
: HttpResponse.json(snapshot);
|
||||
}),
|
||||
);
|
||||
renderEvents();
|
||||
|
||||
const retry = await screen.findByRole(
|
||||
"button",
|
||||
{ name: "Повторить" },
|
||||
{ timeout: RETRIES_EXHAUSTED_MS },
|
||||
);
|
||||
|
||||
expect(requests).toBe(3);
|
||||
|
||||
isFailing = false;
|
||||
fireEvent.click(retry);
|
||||
|
||||
expect(await screen.findByText("Купон · Synthetic coupon")).toBeInTheDocument();
|
||||
expect(requests).toBe(4);
|
||||
},
|
||||
);
|
||||
|
||||
it(
|
||||
"keeps cached events, warns and retries the same GET",
|
||||
{ timeout: TEST_MS },
|
||||
async () => {
|
||||
let isFailing = false;
|
||||
let requests = 0;
|
||||
server.use(
|
||||
http.get("*/api/v1/brokerage-accounts/:accountId/events", () => {
|
||||
requests += 1;
|
||||
|
||||
return isFailing
|
||||
? HttpResponse.json({ code: "TBANK_UNAVAILABLE" }, { status: 503 })
|
||||
: HttpResponse.json(snapshot);
|
||||
}),
|
||||
);
|
||||
const { queryClient } = renderEvents();
|
||||
await screen.findByText("Купон · Synthetic coupon");
|
||||
expect(screen.queryByRole("button", { name: "Повторить" }))
|
||||
.not.toBeInTheDocument();
|
||||
|
||||
isFailing = true;
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: ["get", "/api/v1/brokerage-accounts/{accountId}/events"],
|
||||
});
|
||||
|
||||
const retry = await screen.findByRole(
|
||||
"button",
|
||||
{ name: "Повторить" },
|
||||
{ timeout: RETRIES_EXHAUSTED_MS },
|
||||
);
|
||||
|
||||
expect(screen.getByText("Не удалось обновить данные")).toBeInTheDocument();
|
||||
expect(screen.getByText("Купон · Synthetic coupon")).toBeInTheDocument();
|
||||
expect(requests).toBe(4);
|
||||
|
||||
isFailing = false;
|
||||
fireEvent.click(retry);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByText("Не удалось обновить данные"))
|
||||
.not.toBeInTheDocument());
|
||||
expect(requests).toBe(5);
|
||||
},
|
||||
);
|
||||
|
||||
it("does not offer a plain retry when the account is gone", async () => {
|
||||
server.use(
|
||||
http.get("*/api/v1/brokerage-accounts/:accountId/events", () =>
|
||||
HttpResponse.json({ code: "NOT_FOUND" }, { status: 404 })),
|
||||
);
|
||||
renderEvents();
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole("heading", { name: "Загрузка" }))
|
||||
.not.toBeInTheDocument());
|
||||
expect(screen.queryByRole("button", { name: "Повторить" }))
|
||||
.not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user