181 lines
6.6 KiB
TypeScript
181 lines
6.6 KiB
TypeScript
import { renderHook, waitFor } from "@testing-library/react";
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { useSessionExtendConfirm } from "../../../../packages/common/components/src/lib/use-session-extend-confirm";
|
|
import { getRemainingMs } from "@common/auth";
|
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
|
|
// useConfirm 모킹
|
|
const mockConfirm = vi.fn();
|
|
vi.mock("@frontend-opensource/use-react-hooks", () => ({
|
|
useConfirm: () => ({
|
|
confirm: mockConfirm,
|
|
}),
|
|
}));
|
|
|
|
// @common/auth 모킹
|
|
const mockGetRemainingMs = vi.fn();
|
|
vi.mock("@common/auth", () => ({
|
|
getRemainingMs: (...args: any[]) => mockGetRemainingMs(...args),
|
|
SESSION_EXTEND_CONFIRM_BUFFER_MS: 300000,
|
|
}));
|
|
|
|
describe("useSessionExtendConfirm 커스텀 훅 테스트 (Vitest)", () => {
|
|
const mockGetAccessToken = vi.fn();
|
|
const mockSilentRefresh = vi.fn();
|
|
const mockDeclineSessionExtend = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("1. enabled가 false일 때는 어떤 타이머나 컨펌도 동작하지 않아야 함", () => {
|
|
mockGetAccessToken.mockReturnValue("mock-token-123");
|
|
mockGetRemainingMs.mockReturnValue(10000); // 10초 뒤 컨펌 필요
|
|
|
|
renderHook(() =>
|
|
useSessionExtendConfirm({
|
|
enabled: false,
|
|
getAccessToken: mockGetAccessToken,
|
|
silentRefresh: mockSilentRefresh,
|
|
declineSessionExtend: mockDeclineSessionExtend,
|
|
})
|
|
);
|
|
|
|
// 타이머가 동작하지 않으므로 시간 경과 후에도 confirm이 실행되지 않아야 함
|
|
vi.advanceTimersByTime(10000);
|
|
expect(mockConfirm).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("2. 토큰 만료 버퍼 진입 전인 경우, 해당 시간만큼 대기 후 컨펌창이 나타나야 함", async () => {
|
|
mockGetAccessToken.mockReturnValue("mock-token-123");
|
|
// getRemainingMs(token, buffer) -> 10000ms (10초 후에 버퍼 시점 도달)
|
|
// getRemainingMs(token, 0) -> 310000ms (토큰 유효함)
|
|
mockGetRemainingMs.mockImplementation((token, buffer) => {
|
|
return buffer === 0 ? 310000 : 10000;
|
|
});
|
|
mockConfirm.mockResolvedValue(true);
|
|
|
|
renderHook(() =>
|
|
useSessionExtendConfirm({
|
|
enabled: true,
|
|
getAccessToken: mockGetAccessToken,
|
|
silentRefresh: mockSilentRefresh,
|
|
declineSessionExtend: mockDeclineSessionExtend,
|
|
})
|
|
);
|
|
|
|
// 9초 지났을 때는 아직 컨펌이 뜨지 않아야 함
|
|
vi.advanceTimersByTime(9000);
|
|
expect(mockConfirm).not.toHaveBeenCalled();
|
|
|
|
// 10초째에 진입했을 때 컨펌창 노출
|
|
await vi.advanceTimersByTimeAsync(1000);
|
|
expect(mockConfirm).toHaveBeenCalledWith("로그인을 연장하시겠습니까?");
|
|
});
|
|
|
|
it("3. 컨펌창에서 연장을 승인(Accept)하면 silentRefresh가 호출되고 재스케줄링되어야 함", async () => {
|
|
let token = "mock-token-123";
|
|
mockGetAccessToken.mockImplementation(() => token);
|
|
mockGetRemainingMs.mockImplementation((t, buffer) => {
|
|
return buffer === 0 ? 310000 : 10000;
|
|
});
|
|
|
|
mockConfirm.mockResolvedValue(true); // 연장 승인
|
|
mockSilentRefresh.mockImplementation(async () => {
|
|
token = "new-mock-token-456"; // 토큰 갱신 모사
|
|
});
|
|
|
|
renderHook(() =>
|
|
useSessionExtendConfirm({
|
|
enabled: true,
|
|
getAccessToken: mockGetAccessToken,
|
|
silentRefresh: mockSilentRefresh,
|
|
declineSessionExtend: mockDeclineSessionExtend,
|
|
})
|
|
);
|
|
|
|
// 10초 대기하여 컨펌 활성화 및 연장 승인 처리 진행
|
|
await vi.advanceTimersByTimeAsync(10000);
|
|
|
|
expect(mockSilentRefresh).toHaveBeenCalled();
|
|
// 신규 발급된 토큰("new-mock-token-456")을 기반으로 getAccessToken이 재차 호출되었는지 확인
|
|
expect(mockGetAccessToken).toHaveBeenCalled();
|
|
});
|
|
|
|
it("4. 컨펌창에서 연장을 거절(Decline)하면 declineSessionExtend가 실행되고, 해당 토큰에 대해 재확인하지 않아야 함", async () => {
|
|
mockGetAccessToken.mockReturnValue("mock-token-123");
|
|
mockGetRemainingMs.mockImplementation((t, buffer) => {
|
|
return buffer === 0 ? 310000 : 10000;
|
|
});
|
|
mockConfirm.mockResolvedValue(false); // 연장 거절
|
|
|
|
const { rerender } = renderHook(() =>
|
|
useSessionExtendConfirm({
|
|
enabled: true,
|
|
getAccessToken: mockGetAccessToken,
|
|
silentRefresh: mockSilentRefresh,
|
|
declineSessionExtend: mockDeclineSessionExtend,
|
|
})
|
|
);
|
|
|
|
// 10초 대기하여 거절 실행
|
|
await vi.advanceTimersByTimeAsync(10000);
|
|
|
|
expect(mockDeclineSessionExtend).toHaveBeenCalled();
|
|
expect(mockSilentRefresh).not.toHaveBeenCalled();
|
|
|
|
// 거절 이후 훅이 다시 실행되어도(혹은 상태가 업데이트되어 스케줄이 재호출되어도)
|
|
// dismissedTokenRef에 동일 토큰이 설정되어 있으므로 더 이상 컨펌창이 호출되지 않아야 함
|
|
mockConfirm.mockClear();
|
|
rerender();
|
|
await vi.advanceTimersByTimeAsync(10000);
|
|
expect(mockConfirm).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("5. 이미 만료 버퍼 이하로 남은 경우, 대기 시간 없이 즉시 컨펌창이 나타나야 함", async () => {
|
|
mockGetAccessToken.mockReturnValue("mock-token-123");
|
|
// getRemainingMs(token, buffer) -> 0ms (이미 버퍼 시간 이내임)
|
|
// getRemainingMs(token, 0) -> 50000ms (토큰 자체는 살아있음)
|
|
mockGetRemainingMs.mockImplementation((t, buffer) => {
|
|
return buffer === 0 ? 50000 : 0;
|
|
});
|
|
mockConfirm.mockResolvedValue(false); // 거절 처리하여 무한 루프 방지
|
|
|
|
renderHook(() =>
|
|
useSessionExtendConfirm({
|
|
enabled: true,
|
|
getAccessToken: mockGetAccessToken,
|
|
silentRefresh: mockSilentRefresh,
|
|
declineSessionExtend: mockDeclineSessionExtend,
|
|
})
|
|
);
|
|
|
|
// 타이머 흐름 대기 없이 즉시 동기/비동기 마이크로태스크 단계에서 컨펌 실행
|
|
await vi.advanceTimersByTimeAsync(1);
|
|
expect(mockConfirm).toHaveBeenCalled();
|
|
});
|
|
|
|
it("6. 토큰이 아예 유효하지 않거나 이미 만료된 경우 컨펌창이 뜨지 않아야 함", () => {
|
|
mockGetAccessToken.mockReturnValue("expired-token");
|
|
// getRemainingMs(token, 0) -> 0ms (만료됨)
|
|
mockGetRemainingMs.mockReturnValue(0);
|
|
|
|
renderHook(() =>
|
|
useSessionExtendConfirm({
|
|
enabled: true,
|
|
getAccessToken: mockGetAccessToken,
|
|
silentRefresh: mockSilentRefresh,
|
|
declineSessionExtend: mockDeclineSessionExtend,
|
|
})
|
|
);
|
|
|
|
vi.advanceTimersByTime(10000);
|
|
expect(mockConfirm).not.toHaveBeenCalled();
|
|
});
|
|
});
|