import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import SystemPage from './SystemPage' import { withCodes } from '../codes/fixtures' import type { ResetTarget } from '../api/client' const mocks = vi.hoisted(() => ({ getResetPreview: vi.fn(), resetChannels: vi.fn(), })) vi.mock('../api/client', async (importOriginal) => ({ ...(await importOriginal()), getResetPreview: mocks.getResetPreview, resetChannels: mocks.resetChannels, })) function target(overrides: Partial = {}): ResetTarget { return { orgId: 1, orgNo: '001', orgName: '국제방송교류재단', stage: 1, channels: [ { kind: '문정원', channelId: 'ch-mj' }, { kind: '법률검토', channelId: 'ch-law' }, ], ...overrides, } } async function typeConfirmAndRun(phrase = '채널 초기화') { fireEvent.change(screen.getByLabelText(/실행하려면/), { target: { value: phrase } }) fireEvent.click(screen.getByText('채널 초기화 실행')) fireEvent.click(await screen.findByText('초기화 실행')) } describe('SystemPage', () => { beforeEach(() => { vi.clearAllMocks() mocks.getResetPreview.mockResolvedValue([target()]) mocks.resetChannels.mockResolvedValue({ archivedChannels: 2, clearedOrgs: 1, failures: [] }) }) it('초기화 대상을 기관·채널 수와 함께 미리 보여준다', async () => { mocks.getResetPreview.mockResolvedValue([ target(), target({ orgId: 2, orgNo: '002', orgName: '세종학당재단', channels: [{ kind: '문정원', channelId: 'c2' }] }), ]) render(withCodes()) expect(await screen.findByText('대상 2개 기관 · 채널 3개')).toBeTruthy() const rows = within(screen.getByTestId('reset-targets')).getAllByRole('row') expect(rows).toHaveLength(3) // 헤더 + 2 expect(rows[1].textContent).toContain('문정원 · 법률검토') }) it('확인 문구를 입력하기 전에는 실행 버튼이 잠겨 있다', async () => { render(withCodes()) await screen.findByTestId('reset-targets') expect(screen.getByText('채널 초기화 실행')).toHaveProperty('disabled', true) fireEvent.change(screen.getByLabelText(/실행하려면/), { target: { value: '초기화' } }) expect(screen.getByText('채널 초기화 실행')).toHaveProperty('disabled', true) fireEvent.change(screen.getByLabelText(/실행하려면/), { target: { value: '채널 초기화' } }) expect(screen.getByText('채널 초기화 실행')).toHaveProperty('disabled', false) }) it('실행 버튼을 눌러도 확인창에서 한 번 더 물어본다', async () => { render(withCodes()) await screen.findByTestId('reset-targets') fireEvent.change(screen.getByLabelText(/실행하려면/), { target: { value: '채널 초기화' } }) fireEvent.click(screen.getByText('채널 초기화 실행')) expect(await screen.findByText('정말 초기화할까요?')).toBeTruthy() expect(mocks.resetChannels).not.toHaveBeenCalled() fireEvent.click(screen.getByText('취소')) expect(mocks.resetChannels).not.toHaveBeenCalled() }) it('확인창에서 실행하면 문구와 함께 초기화를 요청하고 결과를 보여준다', async () => { render(withCodes()) await screen.findByTestId('reset-targets') await typeConfirmAndRun() await waitFor(() => expect(mocks.resetChannels).toHaveBeenCalledWith('채널 초기화')) expect(await screen.findByText('채널 2개 보관, 기관 1곳 초기화')).toBeTruthy() // 실행 후 목록을 다시 읽는다 expect(mocks.getResetPreview).toHaveBeenCalledTimes(2) }) it('실패가 있으면 사유를 그대로 보여준다', async () => { mocks.resetChannels.mockResolvedValue({ archivedChannels: 1, clearedOrgs: 0, failures: ['001 국제방송교류재단 (법률검토): 권한 없음'], }) render(withCodes()) await screen.findByTestId('reset-targets') await typeConfirmAndRun() expect(await screen.findByText(/실패 1건/)).toBeTruthy() expect(screen.getByText(/권한 없음/)).toBeTruthy() }) it('대상이 없으면 실행할 수 없다', async () => { mocks.getResetPreview.mockResolvedValue([]) render(withCodes()) expect(await screen.findByText('초기화할 채널이 없습니다.')).toBeTruthy() fireEvent.change(screen.getByLabelText(/실행하려면/), { target: { value: '채널 초기화' } }) expect(screen.getByText('채널 초기화 실행')).toHaveProperty('disabled', true) }) it('무엇이 남고 무엇이 사라지는지 화면에 적어 둔다', async () => { render(withCodes()) await screen.findByTestId('reset-targets') expect(screen.getByText(/보관\(아카이브\)/)).toBeTruthy() expect(screen.getByText(/그대로 남는다/)).toBeTruthy() expect(screen.getByText(/사용자 계정도 건드리지 않는다/)).toBeTruthy() }) })