cover new useLogFilterLogic invariants: activeTab gate, filterByCurrentUser fallback, debounce negative, partial merge

Follow-up to the test refactor. Adds coverage for invariants the
refactored hook contract introduced but that the first pass didn't
assert:

- query enablement: expand the single accessToken-null case into an
  it.each over all four credential props (accessToken, token, userRole,
  userID), plus a separate test for activeTab !== "request logs"
- filterByCurrentUser: when true with a blank User ID filter, the
  outbound request carries user_id = userID
- debounce: also assert the negative case — no call in the first 100ms
  after a filter change (first waiting out the initial mount fire)
- handleFilterChange: partial updates merge without clobbering other
  filter keys (protects the spread + default-fill semantics)
- handleFilterReset: calls setCurrentPage(1) alongside restoring
  filters
This commit is contained in:
Ryan Crabbe 2026-04-20 11:58:23 -07:00
parent 3db143b932
commit 8b633a2efc
No known key found for this signature in database

View file

@ -109,6 +109,17 @@ describe("useLogFilterLogic", () => {
expect(result.current.filters["Status"]).toBe("");
});
it("calls setCurrentPage(1)", () => {
const setCurrentPage = vi.fn();
const { result } = renderFilterHook({ setCurrentPage });
act(() => {
result.current.handleFilterReset();
});
expect(setCurrentPage).toHaveBeenCalledWith(1);
});
it("triggers a fetch with all filter params undefined", async () => {
vi.mocked(uiSpendLogsCall).mockResolvedValue(emptyResponse);
const { result } = renderFilterHook();
@ -156,6 +167,22 @@ describe("useLogFilterLogic", () => {
expect(setCurrentPage).toHaveBeenCalledWith(1);
});
it("merges partial updates without clobbering other filter keys", () => {
const { result } = renderFilterHook();
act(() => {
result.current.handleFilterChange({ "Team ID": "team-a" });
});
expect(result.current.filters["Team ID"]).toBe("team-a");
act(() => {
result.current.handleFilterChange({ Model: "gpt-4" });
});
expect(result.current.filters["Team ID"]).toBe("team-a");
expect(result.current.filters["Model"]).toBe("gpt-4");
});
it("does not call setCurrentPage when filters are identical", async () => {
const setCurrentPage = vi.fn();
const { result } = renderFilterHook({ setCurrentPage });
@ -259,6 +286,24 @@ describe("useLogFilterLogic", () => {
await waitFor(() => expect(uiSpendLogsCall).toHaveBeenCalled(), { timeout: 500 });
});
it("does not call uiSpendLogsCall before the debounce elapses", async () => {
const { result } = renderFilterHook();
// Wait for the initial query fire, then reset the spy so we only observe
// calls triggered by the filter change below.
await waitFor(() => expect(uiSpendLogsCall).toHaveBeenCalled(), { timeout: 500 });
vi.mocked(uiSpendLogsCall).mockClear();
act(() => {
result.current.handleFilterChange({ "Key Alias": "alias-1" });
});
await new Promise((resolve) => setTimeout(resolve, 100));
expect(uiSpendLogsCall).not.toHaveBeenCalled();
await waitFor(() => expect(uiSpendLogsCall).toHaveBeenCalled(), { timeout: 500 });
});
});
describe("backend filtered logs", () => {
@ -455,8 +500,30 @@ describe("useLogFilterLogic", () => {
});
describe("query enablement", () => {
it("does not call uiSpendLogsCall when accessToken is null", async () => {
const { result } = renderFilterHook({ accessToken: null });
const nullCredentialCases: Array<{ name: string; override: HookOverrides }> = [
{ name: "accessToken", override: { accessToken: null } },
{ name: "token", override: { token: null } },
{ name: "userRole", override: { userRole: null } },
{ name: "userID", override: { userID: null } },
];
it.each(nullCredentialCases)(
"does not call uiSpendLogsCall when $name is null",
async ({ override }) => {
const { result } = renderFilterHook(override);
act(() => {
result.current.handleFilterChange({ "Key Alias": "alias-1" });
});
await new Promise((resolve) => setTimeout(resolve, 350));
expect(uiSpendLogsCall).not.toHaveBeenCalled();
},
);
it("does not call uiSpendLogsCall when activeTab is not 'request logs'", async () => {
const { result } = renderFilterHook({ activeTab: "audit logs" });
act(() => {
result.current.handleFilterChange({ "Key Alias": "alias-1" });
@ -468,6 +535,30 @@ describe("useLogFilterLogic", () => {
});
});
describe("filterByCurrentUser", () => {
it("sends user_id: userID when the User ID filter is blank", async () => {
const { result } = renderFilterHook({
filterByCurrentUser: true,
userID: "me-123",
});
act(() => {
result.current.handleFilterChange({ "Key Alias": "alias-1" });
});
await waitFor(
() => {
expect(uiSpendLogsCall).toHaveBeenCalledWith(
expect.objectContaining({
params: expect.objectContaining({ user_id: "me-123" }),
}),
);
},
{ timeout: 500 },
);
});
});
describe("error handling", () => {
it("does not crash when uiSpendLogsCall throws", async () => {
vi.mocked(uiSpendLogsCall).mockRejectedValue(new Error("Network error"));