refactor: enforce frontend view model boundaries
This commit is contained in:
@@ -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"),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user