perf(tbank-connection): poll screen sync every five seconds
Frontend verify / commits (pull_request) Successful in 48s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 6m57s
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 11s
Frontend verify / verify (push) Successful in 8m21s
Frontend verify / commits (pull_request) Successful in 48s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 6m57s
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 11s
Frontend verify / verify (push) Successful in 8m21s
During active screen sync, status checks and observe reads run every 5s instead of 1s; terminal state observes resources immediately. OpenSpec change: WealthLedger/specs#21
This commit was merged in pull request #47.
This commit is contained in:
@@ -420,7 +420,7 @@ describe("page-owned screen synchronization", () => {
|
|||||||
it.each(["leave", "logout"])(
|
it.each(["leave", "logout"])(
|
||||||
"publishes completed empty coverage while history runs, then stops on %s",
|
"publishes completed empty coverage while history runs, then stops on %s",
|
||||||
async (boundary) => {
|
async (boundary) => {
|
||||||
vi.useFakeTimers({ toFake: ["Date"] });
|
vi.useFakeTimers();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auth();
|
auth();
|
||||||
@@ -429,15 +429,13 @@ describe("page-owned screen synchronization", () => {
|
|||||||
const modes: (null | string)[] = [];
|
const modes: (null | string)[] = [];
|
||||||
|
|
||||||
const snapshot = () =>
|
const snapshot = () =>
|
||||||
resource(
|
resource(isCompleted ? [] : null, {
|
||||||
isCompleted
|
dataRevision: isCompleted ? "covered" : "initial",
|
||||||
? []
|
sync: { errorCode: null, id: "history", state: "running" },
|
||||||
: null,
|
unavailableReason: isCompleted
|
||||||
{
|
? undefined
|
||||||
dataRevision: isCompleted ? "covered" : "initial",
|
: "CALENDAR_OR_ACCOUNT_HISTORY",
|
||||||
sync: { errorCode: null, id: "history", state: "running" },
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
http.get("*/api/v1/brokerage-accounts", ({ request }) => {
|
http.get("*/api/v1/brokerage-accounts", ({ request }) => {
|
||||||
@@ -472,31 +470,26 @@ describe("page-owned screen synchronization", () => {
|
|||||||
{ wrapper: createQueryWrapper().wrapper },
|
{ wrapper: createQueryWrapper().wrapper },
|
||||||
);
|
);
|
||||||
|
|
||||||
await waitFor(() =>
|
await vi.waitFor(() => expect(modes).toEqual([null, "observe"]));
|
||||||
expect(result.current.accounts.meta?.sync.id).toBe("history"),
|
expect(polls).toBe(1);
|
||||||
);
|
|
||||||
await waitFor(() => expect(polls).toBeGreaterThan(0));
|
|
||||||
expect(result.current.sync.refreshing).toBe(true);
|
expect(result.current.sync.refreshing).toBe(true);
|
||||||
expect(result.current.accounts.data).toBeUndefined();
|
expect(result.current.accounts.data).toBeUndefined();
|
||||||
isCompleted = true;
|
|
||||||
vi.setSystemTime(Date.now() + 61_000);
|
|
||||||
await waitFor(() => expect(result.current.accounts.data).toEqual([]), {
|
|
||||||
timeout: 3000,
|
|
||||||
});
|
|
||||||
expect(result.current.sync.refreshing).toBe(true);
|
|
||||||
await act(async () => {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1100));
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(modes[0]).toBeNull();
|
isCompleted = true;
|
||||||
expect(modes.slice(1).every(mode => mode === "observe")).toBe(true);
|
await act(() => vi.advanceTimersByTimeAsync(5000));
|
||||||
|
await vi.waitFor(() =>
|
||||||
|
expect(result.current.accounts.data).toEqual([]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.current.sync.refreshing).toBe(true);
|
||||||
|
expect(modes).toEqual([null, "observe", "observe"]);
|
||||||
|
|
||||||
if (boundary === "leave") unmount();
|
if (boundary === "leave") unmount();
|
||||||
else act(() => configureAuthClient());
|
else act(() => configureAuthClient());
|
||||||
const stopped = polls;
|
|
||||||
await act(async () => {
|
await act(() => vi.advanceTimersByTimeAsync(60_000));
|
||||||
await new Promise(resolve => setTimeout(resolve, 1100));
|
expect(polls).toBe(2);
|
||||||
});
|
expect(modes).toHaveLength(3);
|
||||||
expect(polls).toBe(stopped);
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
@@ -504,6 +497,144 @@ describe("page-owned screen synchronization", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it("runs the next status poll and observe read only after five seconds", async () => {
|
||||||
|
auth();
|
||||||
|
const modes: (null | string)[] = [];
|
||||||
|
const pollGaps: number[] = [];
|
||||||
|
let polls = 0;
|
||||||
|
let previousPollAt = 0;
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.get("*/api/v1/brokerage-accounts", ({ request }) => {
|
||||||
|
modes.push(new URL(request.url).searchParams.get("sync"));
|
||||||
|
|
||||||
|
return HttpResponse.json(
|
||||||
|
resource([], {
|
||||||
|
sync: { errorCode: null, id: "history", state: "running" },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
http.get("*/api/v1/tbank-sync/:id", () => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (previousPollAt) pollGaps.push(now - previousPollAt);
|
||||||
|
previousPollAt = now;
|
||||||
|
polls++;
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
errorCode: null,
|
||||||
|
id: "history",
|
||||||
|
now: "2026-09-25T00:00:00Z",
|
||||||
|
scope: null,
|
||||||
|
state: "running",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.useFakeTimers();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { unmount } = renderHook(
|
||||||
|
() => {
|
||||||
|
const accounts = useBrokerageAccounts();
|
||||||
|
|
||||||
|
return {
|
||||||
|
accounts,
|
||||||
|
sync: useScreenSync({ screen: "accounts" }, [accounts]),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ wrapper: createQueryWrapper().wrapper },
|
||||||
|
);
|
||||||
|
|
||||||
|
await vi.waitFor(() => expect(modes).toEqual([null, "observe"]));
|
||||||
|
expect(polls).toBe(1);
|
||||||
|
|
||||||
|
await act(() => vi.advanceTimersByTimeAsync(2500));
|
||||||
|
expect(polls).toBe(1);
|
||||||
|
expect(modes).toEqual([null, "observe"]);
|
||||||
|
|
||||||
|
await act(() => vi.advanceTimersByTimeAsync(5000));
|
||||||
|
await vi.waitFor(() => expect(polls).toBe(2));
|
||||||
|
|
||||||
|
expect(modes).toEqual([null, "observe", "observe"]);
|
||||||
|
expect(pollGaps).toHaveLength(1);
|
||||||
|
expect(pollGaps[0]).toBeGreaterThanOrEqual(5000);
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
await act(() => vi.advanceTimersByTimeAsync(60_000));
|
||||||
|
expect(polls).toBe(2);
|
||||||
|
expect(modes).toHaveLength(3);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
vi.useRealTimers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("observes resources immediately when a status response turns terminal", async () => {
|
||||||
|
auth();
|
||||||
|
const modes: (null | string)[] = [];
|
||||||
|
let isCompleted = false;
|
||||||
|
let polls = 0;
|
||||||
|
|
||||||
|
const meta = () =>
|
||||||
|
resource([], {
|
||||||
|
dataRevision: isCompleted ? "covered" : "initial",
|
||||||
|
sync: { errorCode: null, id: "history", state: "running" },
|
||||||
|
});
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.get("*/api/v1/brokerage-accounts", ({ request }) => {
|
||||||
|
modes.push(new URL(request.url).searchParams.get("sync"));
|
||||||
|
|
||||||
|
return HttpResponse.json(meta());
|
||||||
|
}),
|
||||||
|
http.get("*/api/v1/tbank-sync/:id", () => {
|
||||||
|
polls++;
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
errorCode: null,
|
||||||
|
id: "history",
|
||||||
|
now: "2026-09-25T00:00:00Z",
|
||||||
|
scope: null,
|
||||||
|
state: isCompleted ? "succeeded" : "running",
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
vi.useFakeTimers();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { result } = renderHook(
|
||||||
|
() => {
|
||||||
|
const accounts = useBrokerageAccounts();
|
||||||
|
|
||||||
|
return {
|
||||||
|
accounts,
|
||||||
|
sync: useScreenSync({ screen: "accounts" }, [accounts]),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ wrapper: createQueryWrapper().wrapper },
|
||||||
|
);
|
||||||
|
|
||||||
|
await vi.waitFor(() => expect(modes).toEqual([null, "observe"]));
|
||||||
|
expect(polls).toBe(1);
|
||||||
|
expect(result.current.sync.refreshing).toBe(true);
|
||||||
|
|
||||||
|
isCompleted = true;
|
||||||
|
await act(() => vi.advanceTimersByTimeAsync(5000));
|
||||||
|
await vi.waitFor(() => expect(modes).toEqual([null, "observe", "observe"]));
|
||||||
|
|
||||||
|
expect(polls).toBe(2);
|
||||||
|
expect(result.current.accounts.meta?.dataRevision).toBe("covered");
|
||||||
|
expect(result.current.sync.refreshing).toBe(false);
|
||||||
|
|
||||||
|
await act(() => vi.advanceTimersByTimeAsync(60_000));
|
||||||
|
expect(polls).toBe(2);
|
||||||
|
expect(modes).toHaveLength(3);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
vi.useRealTimers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("clears private snapshots when status confirms access loss", async () => {
|
it("clears private snapshots when status confirms access loss", async () => {
|
||||||
auth();
|
auth();
|
||||||
server.use(
|
server.use(
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ type ScreenResource = {
|
|||||||
refetch: () => Promise<unknown>;
|
refetch: () => Promise<unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATUS_INTERVAL_MS = 1000;
|
const STATUS_INTERVAL_MS = 5000;
|
||||||
const FIRST_SOURCE_RECOVERY_DELAY_MS = 5000;
|
const FIRST_SOURCE_RECOVERY_DELAY_MS = 5000;
|
||||||
const SECOND_SOURCE_RECOVERY_DELAY_MS = 15_000;
|
const SECOND_SOURCE_RECOVERY_DELAY_MS = 15_000;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user