feat: 기능 추가 과정중
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import { HttpResponse, http } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { createExternalAuthSessionAdapter } from "../../src/adapters/auth/external-session-adapter.ts";
|
||||
import { createHttpClient } from "../../src/adapters/http/client.ts";
|
||||
import { TEST_HTTP_CONTRACT } from "../helpers/http-contract-fixture.ts";
|
||||
|
||||
let responseStatuses: number[] = [];
|
||||
const server = setupServer(
|
||||
http.get("https://api.test/api/entities", () => {
|
||||
const status = responseStatuses.shift() ?? 200;
|
||||
if (status === 401) {
|
||||
return HttpResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { code: "UNAUTHENTICATED" },
|
||||
meta: { requestId: "request-1", traceId: "trace-1" },
|
||||
},
|
||||
{ status },
|
||||
);
|
||||
}
|
||||
return HttpResponse.json({
|
||||
success: true,
|
||||
data: [{ id: "resource-1", name: "Example" }],
|
||||
meta: { requestId: "request-2", traceId: "trace-1" },
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
||||
afterEach(() => {
|
||||
responseStatuses = [];
|
||||
server.resetHandlers();
|
||||
});
|
||||
afterAll(() => server.close());
|
||||
|
||||
const clock = { now: () => 0, sleep: async () => {} };
|
||||
|
||||
type HttpDependencies = Parameters<typeof createHttpClient>[0];
|
||||
|
||||
function testClient(options: HttpDependencies) {
|
||||
return createHttpClient({ ...TEST_HTTP_CONTRACT, ...options });
|
||||
}
|
||||
|
||||
type ExternalSessionOwner = Parameters<
|
||||
typeof createExternalAuthSessionAdapter
|
||||
>[0];
|
||||
|
||||
function createOwner(
|
||||
overrides: Partial<ExternalSessionOwner> = {},
|
||||
): ExternalSessionOwner {
|
||||
return {
|
||||
readState: () => "authenticated",
|
||||
subscribe: () => () => {},
|
||||
beginSignIn: async () => {},
|
||||
signOut: async () => {},
|
||||
attachCredential: async () => ({ headers: {} }),
|
||||
recoverSession: async () => "restored",
|
||||
notifyUnauthenticated: () => {},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("bounded 401 session recovery", () => {
|
||||
it("calls recovery once and replays a safe request once", async () => {
|
||||
responseStatuses = [401, 200];
|
||||
const recoverSession = vi.fn(async () => "restored" as const);
|
||||
const authSession = createExternalAuthSessionAdapter(createOwner({
|
||||
attachCredential: async () => ({ headers: {} }),
|
||||
recoverSession,
|
||||
notifyUnauthenticated: vi.fn(),
|
||||
}));
|
||||
const client = testClient({
|
||||
baseUrl: "https://api.test",
|
||||
authSession,
|
||||
clock,
|
||||
});
|
||||
|
||||
await expect(client.execute("LIST_ENTITIES")).resolves.toMatchObject({
|
||||
ok: true,
|
||||
});
|
||||
expect(recoverSession).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("stops after a second 401 and notifies unauthenticated once", async () => {
|
||||
responseStatuses = [401, 401];
|
||||
const notifyUnauthenticated = vi.fn();
|
||||
const authSession = createExternalAuthSessionAdapter(createOwner({
|
||||
attachCredential: async () => ({ headers: {} }),
|
||||
recoverSession: async () => "restored",
|
||||
notifyUnauthenticated,
|
||||
}));
|
||||
const client = testClient({
|
||||
baseUrl: "https://api.test",
|
||||
authSession,
|
||||
clock,
|
||||
});
|
||||
|
||||
await expect(client.execute("LIST_ENTITIES")).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: { kind: "AUTH_REQUIRED" },
|
||||
});
|
||||
expect(notifyUnauthenticated).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("normalizes attach and invalid recovery failures", async () => {
|
||||
const attachFailure = createExternalAuthSessionAdapter(createOwner({
|
||||
attachCredential: async () => {
|
||||
throw new Error("credential detail");
|
||||
},
|
||||
recoverSession: async () => "restored",
|
||||
notifyUnauthenticated: vi.fn(),
|
||||
}));
|
||||
const client = testClient({
|
||||
baseUrl: "https://api.test",
|
||||
authSession: attachFailure,
|
||||
clock,
|
||||
});
|
||||
|
||||
await expect(client.execute("LIST_ENTITIES")).resolves.toMatchObject({
|
||||
ok: false,
|
||||
error: { kind: "AUTH_INTEGRATION_FAILURE" },
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user