refactor(ui): route behavior-preserving networking calls through apiClient (#29806)

* refactor(ui): route callbacks/nudges calls through apiClient

* refactor(ui): route alerting + key/user/team delete calls through apiClient

* fix(ui): late-bind fetch in apiClient so global.fetch swaps take effect

createApiClient captured fetch at construction time, so reassigning
global.fetch (as tests do) had no effect and a real network call leaked.
Resolve fetch per request instead; harmless in production where fetch is
never swapped, and required for apiClient-based calls to be testable.

* refactor(ui): route behavior-preserving networking calls through apiClient

Collapse ~61 hand-rolled fetch() calls whose semantics already match the
shared apiClient (auth header, JSON body, json-error + deriveErrorMessage +
handleError) into apiClient.get/post/etc. Query-string builders and the
divergent error-handling functions (no-check, custom messages, text-error)
are intentionally left for a follow-up normalization pass, since converting
them changes wire encoding or error behavior. Prunes the now-stale
no-restricted-syntax suppressions for the removed fetch calls.

* refactor(ui): convert remaining admin/guardrail GETs, guard late-bind fetch

Routes adminspendByProvider, adminGlobalActivity, and the three guardrail
submission calls (list/approve/reject) through apiClient so they match their
already-converted siblings instead of staying on raw fetch. Adds a
client.test.ts case that swaps globalThis.fetch after createApiClient() and
asserts the swap takes effect, which fails on the pre-fix captured-fetch line
and locks in the per-call resolution
This commit is contained in:
ryan-crabbe-berri
2026-06-05 20:40:41 -07:00
committed by GitHub
parent 4ec4ab99d0
commit 6955e6f2c2
4 changed files with 162 additions and 1267 deletions
@@ -1504,7 +1504,7 @@
"count": 23
},
"no-restricted-syntax": {
"count": 241
"count": 175
}
},
"src/components/object_permissions_view.tsx": {
File diff suppressed because it is too large Load Diff
@@ -85,4 +85,19 @@ describe("createApiClient", () => {
const [, init] = fetchImpl.mock.calls[0];
expect(init.headers).toEqual({ "Content-Type": "application/json" });
});
it("resolves the global fetch per call, so a swap after construction takes effect", async () => {
const client = createApiClient({ getBaseUrl: () => "" });
const original = globalThis.fetch;
const swapped = vi.fn(async () => okResponse({ swapped: true }));
globalThis.fetch = swapped as unknown as typeof fetch;
try {
const result = await client.get("/ping", { accessToken: "sk" });
expect(result).toEqual({ swapped: true });
expect(swapped).toHaveBeenCalledTimes(1);
} finally {
globalThis.fetch = original;
}
});
});
+1 -1
View File
@@ -98,7 +98,7 @@ const appendQuery = (url: string, query: QueryParams | undefined): string => {
export function createApiClient(config: ApiClientConfig): ApiClient {
const { getBaseUrl, getAuthHeaderName, onError, fetchImpl } = config;
const doFetch = fetchImpl ?? fetch;
const doFetch: typeof fetch = (input, init) => (fetchImpl ?? fetch)(input, init);
async function request<T = any>(method: HttpMethod, path: string, options: RequestOptions = {}): Promise<T> {
const { accessToken, body, rawBody, query, headers: extraHeaders, signal } = options;