Files
Cibello-app/apps/mobile/test/api.test.ts
T
Sven (AAMOS AI) 0078084a2b field-fix: POST/PUT/PATCH without body sends '{}' in mobile api helper
- api() in apps/mobile/src/lib/api.ts now sends JSON.stringify({}) for
  methods with a body slot when no body is provided, instead of sending
  undefined with content-type: application/json.
- Fixes scan start crash (POST /v1/scans/:id/start) and latent
  resend-verification crash (POST /v1/auth/resend-verification).
- Server strictness left intact; uploadImage unchanged.
- Added unit tests for GET/HEAD/POST/PUT body behaviour.
2026-08-07 15:39:46 +07:00

102 lines
2.6 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
vi.mock("expo-constants", () => ({
default: {
expoConfig: {
extra: { apiBaseUrl: "http://test.example" },
},
},
}));
vi.mock("../src/lib/auth", () => ({
useAuth: {
getState: () => ({ accessToken: "test-token" }),
},
}));
import { api } from "../src/lib/api";
describe("api() helper", () => {
it("GET without body sends no body", async () => {
fetchMock.mockResolvedValueOnce({
status: 200,
ok: true,
json: async () => ({ ok: true }),
} as Response);
await api("/v1/scans/123/start", { method: "GET" });
expect(fetchMock).toHaveBeenCalledWith(
"http://test.example/v1/scans/123/start",
expect.objectContaining({ method: "GET", body: undefined }),
);
});
it("POST without body sends '{}'", async () => {
fetchMock.mockResolvedValueOnce({
status: 200,
ok: true,
json: async () => ({ ok: true }),
} as Response);
await api("/v1/scans/123/start", { method: "POST" });
expect(fetchMock).toHaveBeenCalledWith(
"http://test.example/v1/scans/123/start",
expect.objectContaining({
method: "POST",
headers: expect.objectContaining({ "content-type": "application/json" }),
body: "{}",
}),
);
});
it("POST with body stringifies the body", async () => {
fetchMock.mockResolvedValueOnce({
status: 200,
ok: true,
json: async () => ({ ok: true }),
} as Response);
await api("/v1/scans/123/start", { method: "POST", body: { foo: "bar" } });
expect(fetchMock).toHaveBeenCalledWith(
"http://test.example/v1/scans/123/start",
expect.objectContaining({ body: '{"foo":"bar"}' }),
);
});
it("PUT without body sends '{}'", async () => {
fetchMock.mockResolvedValueOnce({
status: 200,
ok: true,
json: async () => ({ ok: true }),
} as Response);
await api("/v1/items/abc", { method: "PUT" });
expect(fetchMock).toHaveBeenCalledWith(
"http://test.example/v1/items/abc",
expect.objectContaining({ method: "PUT", body: "{}" }),
);
});
it("HEAD without body sends no body", async () => {
fetchMock.mockResolvedValueOnce({
status: 200,
ok: true,
json: async () => ({ ok: true }),
} as Response);
await api("/v1/health", { method: "HEAD" });
expect(fetchMock).toHaveBeenCalledWith(
"http://test.example/v1/health",
expect.objectContaining({ method: "HEAD", body: undefined }),
);
});
});