From e0067c6f55f0be4353a3c7d7894d921c3fb810d3 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sat, 27 Jun 2026 21:16:19 +0900 Subject: [PATCH] =?UTF-8?q?=EC=88=98=EC=A0=95:=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=EC=9E=90=20e2e=20=EC=9D=B8=EC=A6=9D=20=ED=9D=90=EB=A6=84=20?= =?UTF-8?q?=EC=95=88=EC=A0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEPLOYMENT_SUMMARY.md | 30 ++--- FINAL_SUMMARY.md | 121 +++++++++--------- ROADMAP_WBS.md | 12 +- .../Components/Admin/Pages/AdminIndex.razor | 1 + .../Admin/Pages/Blog/BlogCreate.razor | 1 + .../Admin/Pages/Blog/BlogList.razor | 1 + .../Components/Admin/Pages/Dashboard.razor | 1 + .../Admin/Pages/Inquiries/InquiryDetail.razor | 1 + .../Admin/Pages/Inquiries/InquiryList.razor | 1 + .../Admin/Pages/Settings/SiteSettings.razor | 1 + .../Components/Admin/RedirectToLogin.razor | 9 ++ TaxBaik.Web/Components/Admin/Routes.razor | 6 +- tests/e2e/admin-login.spec.ts | 18 +-- tests/e2e/admin-password-change.spec.ts | 20 +-- tests/e2e/admin-smoke.spec.ts | 20 +-- tests/e2e/contact-submit.spec.ts | 18 +-- tests/e2e/helpers/admin-auth.ts | 21 +++ tests/e2e/inquiry-detail.spec.ts | 18 +-- 18 files changed, 140 insertions(+), 160 deletions(-) create mode 100644 TaxBaik.Web/Components/Admin/RedirectToLogin.razor create mode 100644 tests/e2e/helpers/admin-auth.ts diff --git a/DEPLOYMENT_SUMMARY.md b/DEPLOYMENT_SUMMARY.md index 5e8258f..f003b46 100644 --- a/DEPLOYMENT_SUMMARY.md +++ b/DEPLOYMENT_SUMMARY.md @@ -3,15 +3,15 @@ > 이 문서는 현재 WBS 기준의 검증 문서가 아니라, 과거 배포 요약의 기록이다. > 최신 상태는 `ROADMAP_WBS.md`와 CI 로그를 기준으로 판단한다. -## 📊 최종 완성 현황 +## 📊 과거 기록 현황 ### ⚠️ 과거 기준 기록 | 단계 | 항목 | 상태 | |------|------|------| -| W0 | 프로젝트 기반 구축 | ✅ 완료 | -| W1 | LLM 개발 지침 (CLAUDE.md) | ✅ 완료 | -| W2 | 도메인/인프라/서비스 레이어 | ✅ 완료 | +| W0 | 프로젝트 기반 구축 | 과거 기록 | +| W1 | LLM 개발 지침 (CLAUDE.md) | 과거 기록 | +| W2 | 도메인/인프라/서비스 레이어 | 과거 기록 | | **W3** | **공개 홈페이지 (Razor Pages SSR)** | 과거 기록 | | **W4** | **관리자 백오피스 (Blazor Server)** | 과거 기록 | | **W5** | **스타일링 및 모바일 UX** | 과거 기록 | @@ -31,7 +31,7 @@ ### 관리자 - 🔐 **로그인**: http://178.104.200.7/taxbaik/admin/login - 📊 **대시보드**: http://178.104.200.7/taxbaik/admin/dashboard -- 👤 **기본 계정**: admin / admin123 +- 계정 정보는 문서에 기록하지 않고 Gitea Secrets 또는 서버 환경변수로만 관리한다. --- @@ -61,9 +61,9 @@ ## 📊 과거 데이터베이스 기록 ### 초기 데이터 -- ✅ **5개 카테고리**: 사업자세무, 부동산세금, 종합소득세, 부가가치세, 가족자산증여 -- ✅ **5개 블로그 포스트**: 초기 콘텐츠 포함 -- ✅ **1개 관리자 계정**: admin/admin123 +- 5개 카테고리: 사업자세무, 부동산세금, 종합소득세, 부가가치세, 가족자산증여 +- 5개 블로그 포스트: 초기 콘텐츠 포함 +- 관리자 계정: 비밀번호는 문서화하지 않는다. --- @@ -101,13 +101,13 @@ e7e01d0 마이그레이션 및 보안 수정 ## ✨ 주요 특징 -- ✅ SEO 최적화 (Server-Side Rendering) -- ✅ 무중단 배포 (Shadow Copy) -- ✅ 반응형 모바일 UI -- ✅ 한국어 완전 지원 -- ✅ 자동 마이그레이션 -- ✅ 안전한 인증 (쿠키 + 인증) -- ✅ 체계적인 레이어 구조 +- SEO 항목 (Server-Side Rendering) +- 심링크 기반 배포 +- 반응형 모바일 UI +- 한국어 UI +- 자동 마이그레이션 +- 인증 항목 +- 레이어 구조 - 기록용 요약일 뿐, 현재 완료 판정 기준은 아니다. --- diff --git a/FINAL_SUMMARY.md b/FINAL_SUMMARY.md index 7bd4764..cb4564c 100644 --- a/FINAL_SUMMARY.md +++ b/FINAL_SUMMARY.md @@ -8,10 +8,10 @@ ## 📌 프로젝트 개요 -### 비즈니스 목표 -- ✅ 온라인 전문성 표현 -- ✅ 블로그 SEO 유입 -- ✅ 전국 고객 확보 +### 비즈니스 목표 기록 +- 온라인 전문성 표현 +- 블로그 SEO 유입 +- 전국 고객 확보 ### 핵심 포지셔닝 > "사업자 세금 + 부동산 + 가족자산 = 맞춤형 세무 파트너" @@ -95,24 +95,23 @@ TaxBaik.Admin/ 95 KB (Blazor Server) ## ✨ 주요 기능 ### 공개 사이트 -- ✅ SEO 최적화 블로그 (5개 카테고리) -- ✅ 온라인 상담 신청 폼 -- ✅ 반응형 디자인 (모바일 375px+) -- ✅ 성능 최적화 (gzip, lazy load) +- SEO 블로그 +- 온라인 상담 신청 폼 +- 반응형 디자인 +- 성능 최적화 항목 ### 관리자 백오피스 -- ✅ 대시보드 (KPI 카드) -- ✅ 블로그 CRUD -- ✅ 문의 관리 (상태 변경) -- ✅ 사이트 설정 +- 대시보드 +- 블로그 관리 +- 문의 관리 +- 사이트 설정 ### 보안 & 성능 -- ✅ SQL Injection 방지 (파라미터화 쿼리) -- ✅ CSRF 보호 ([ValidateAntiForgeryToken]) -- ✅ Cookie 기반 인증 (8시간 세션) -- ✅ gzip 응답 압축 -- ✅ 이미지 lazy load -- ✅ 폰트 preconnect +- SQL Injection 방지 항목 +- 인증/인가 항목 +- gzip 응답 압축 +- 이미지 lazy load +- 폰트 preconnect --- @@ -130,7 +129,7 @@ Gitea Actions 트리거 4. 심링크 스왑 5. systemctl restart ↓ -배포 완료 (무중단) +배포 기록 생성 ``` ### 자동 마이그레이션 @@ -143,7 +142,7 @@ schema_migrations 테이블 확인 ↓ 미실행 마이그레이션 자동 실행 ↓ -DB 준비 완료 +DB 준비 기록 생성 ``` --- @@ -164,32 +163,32 @@ DB 준비 완료 ## 🎯 과거 수락 기준 기록 ### 기술적 요구사항 -- [x] ASP.NET Core 8 + C#11 기반 -- [x] Dapper + PostgreSQL 사용 -- [x] Razor Pages SSR (공개 사이트) -- [x] Blazor Server (관리자) -- [x] 계층화된 아키텍처 (Domain → Infrastructure → Application → Web/Admin) -- [x] 모든 UI 문자열 한국어 +- ASP.NET Core 기반 +- Dapper + PostgreSQL 사용 +- Razor Pages SSR (공개 사이트) +- Blazor Server (관리자) +- 계층화된 아키텍처 +- UI 문자열 한국어 ### 기능 요구사항 -- [x] 블로그 (5개 카테고리, SEO 최적화) -- [x] 온라인 문의 폼 -- [x] 관리자 백오피스 (블로그 + 문의 관리) -- [x] 반응형 디자인 -- [x] 성능 최적화 +- 블로그 +- 온라인 문의 폼 +- 관리자 백오피스 +- 반응형 디자인 +- 성능 최적화 ### 배포 요구사항 -- [x] CI/CD 파이프라인 (Gitea Actions) -- [x] 자동 마이그레이션 -- [x] 무중단 배포 (심링크 스왑) -- [x] systemd 서비스 파일 -- [x] Nginx 리버스 프록시 설정 +- CI/CD 파이프라인 +- 자동 마이그레이션 +- 심링크 배포 +- systemd 서비스 파일 +- Nginx 리버스 프록시 설정 ### 문서 요구사항 -- [x] CLAUDE.md (개발 지침) -- [x] DEPLOYMENT_GUIDE.md (배포 가이드) -- [x] README.md (프로젝트 개요) -- [x] 서버 설치 스크립트 +- CLAUDE.md +- DEPLOYMENT_GUIDE.md +- README.md +- 서버 설치 스크립트 --- @@ -231,31 +230,31 @@ b300cd7 완성: 빌드 성공 및 최종 통합 (W0~W6 완료) ## 과거 체크리스트 기록 -### 개발 완료 -- [x] 코드 작성 -- [x] 로컬 빌드 성공 -- [x] Git 커밋/푸시 +### 개발 기록 +- 코드 작성 기록 +- 로컬 빌드 기록 +- Git 커밋/푸시 기록 -### 검증 완료 -- [x] 아키텍처 검증 -- [x] 코드 구조 검증 -- [x] 보안 검증 -- [x] 성능 검증 -- [x] SEO 검증 +### 검증 기록 +- 아키텍처 검토 기록 +- 코드 구조 검토 기록 +- 보안 검토 기록 +- 성능 검토 기록 +- SEO 검토 기록 ### 배포 준비 -- [x] CI/CD 파이프라인 -- [x] 자동 마이그레이션 -- [x] 배포 스크립트 -- [x] 배포 가이드 -- [x] 모니터링 설정 +- CI/CD 파이프라인 +- 자동 마이그레이션 +- 배포 스크립트 +- 배포 가이드 +- 모니터링 설정 -### 문서 완성 -- [x] README.md -- [x] CLAUDE.md -- [x] DEPLOYMENT_GUIDE.md -- [x] PRODUCTION_CHECKLIST.md -- [x] SERVER_SETUP.sh +### 문서 기록 +- README.md +- CLAUDE.md +- DEPLOYMENT_GUIDE.md +- PRODUCTION_CHECKLIST.md +- SERVER_SETUP.sh --- diff --git a/ROADMAP_WBS.md b/ROADMAP_WBS.md index f07e3f1..2c4e21f 100644 --- a/ROADMAP_WBS.md +++ b/ROADMAP_WBS.md @@ -98,12 +98,12 @@ Todo: Todo: - [x] README 테스트/배포 섹션 갱신 - [x] CLAUDE.md E2E 기준 갱신 -- [ ] 오래된 최종 보고 문서의 허위 완료 표현 정정 +- [x] 오래된 최종 보고 문서의 허위 완료 표현 정정 ### 현재 검증 메모 -- 로컬 빌드 성공 -- 관리자 smoke 성공 -- 공개 smoke 성공 -- 블로그 상세 SEO는 원격 배포본 반영 대기 -- 문의 제출 E2E는 원격 배포 반영 대기 +- `dotnet build TaxBaik.sln` 성공 +- 시크릿 없는 로컬 Playwright 전체 실행: 공개 smoke, blog SEO 통과 / 관리자 시나리오는 자격 증명 미설정으로 스킵 +- 배포본 `version.txt`: `8f0cb69` +- 배포본 블로그 상세: HTTP 200 +- CI Playwright 전체 통과는 최신 커밋 배포 후 재확인 필요 - 비밀번호 변경 E2E는 배포 환경 자격 증명 확인 대기 diff --git a/TaxBaik.Web/Components/Admin/Pages/AdminIndex.razor b/TaxBaik.Web/Components/Admin/Pages/AdminIndex.razor index c2d9169..fa9272c 100644 --- a/TaxBaik.Web/Components/Admin/Pages/AdminIndex.razor +++ b/TaxBaik.Web/Components/Admin/Pages/AdminIndex.razor @@ -1,4 +1,5 @@ @page "/admin" +@attribute [Authorize] @inject NavigationManager NavigationManager @code { diff --git a/TaxBaik.Web/Components/Admin/Pages/Blog/BlogCreate.razor b/TaxBaik.Web/Components/Admin/Pages/Blog/BlogCreate.razor index 059495c..b29adb4 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Blog/BlogCreate.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Blog/BlogCreate.razor @@ -1,4 +1,5 @@ @page "/admin/blog/create" +@attribute [Authorize] @using TaxBaik.Application.DTOs @using TaxBaik.Application.Services @using TaxBaik.Domain.Interfaces diff --git a/TaxBaik.Web/Components/Admin/Pages/Blog/BlogList.razor b/TaxBaik.Web/Components/Admin/Pages/Blog/BlogList.razor index a94b0e1..1e21a82 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Blog/BlogList.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Blog/BlogList.razor @@ -1,4 +1,5 @@ @page "/admin/blog" +@attribute [Authorize] @inject IApiClient ApiClient @inject ISnackbar Snackbar diff --git a/TaxBaik.Web/Components/Admin/Pages/Dashboard.razor b/TaxBaik.Web/Components/Admin/Pages/Dashboard.razor index 8afdfa7..ca5287e 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Dashboard.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Dashboard.razor @@ -1,4 +1,5 @@ @page "/admin/dashboard" +@attribute [Authorize] @using TaxBaik.Application.Services @inject AdminDashboardService DashboardService diff --git a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryDetail.razor b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryDetail.razor index 16e9339..c5a79cb 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryDetail.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryDetail.razor @@ -1,4 +1,5 @@ @page "/admin/inquiries/{InquiryId:int}" +@attribute [Authorize] @using TaxBaik.Application.Services @inject InquiryService InquiryService @inject NavigationManager Navigation diff --git a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor index 99c91b0..f54d239 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Inquiries/InquiryList.razor @@ -1,4 +1,5 @@ @page "/admin/inquiries" +@attribute [Authorize] @using TaxBaik.Domain.Interfaces @inject IInquiryRepository InquiryRepository diff --git a/TaxBaik.Web/Components/Admin/Pages/Settings/SiteSettings.razor b/TaxBaik.Web/Components/Admin/Pages/Settings/SiteSettings.razor index 8f2a981..3097e5b 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Settings/SiteSettings.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Settings/SiteSettings.razor @@ -1,4 +1,5 @@ @page "/admin/settings" +@attribute [Authorize] @using System.ComponentModel.DataAnnotations @using System.Collections.Generic @using TaxBaik.Web.Services diff --git a/TaxBaik.Web/Components/Admin/RedirectToLogin.razor b/TaxBaik.Web/Components/Admin/RedirectToLogin.razor new file mode 100644 index 0000000..90ea4b2 --- /dev/null +++ b/TaxBaik.Web/Components/Admin/RedirectToLogin.razor @@ -0,0 +1,9 @@ +@inject NavigationManager Navigation + +@code { + protected override void OnInitialized() + { + var returnUrl = Uri.EscapeDataString(Navigation.ToBaseRelativePath(Navigation.Uri)); + Navigation.NavigateTo($"/taxbaik/admin/login?returnUrl={returnUrl}", replace: true); + } +} diff --git a/TaxBaik.Web/Components/Admin/Routes.razor b/TaxBaik.Web/Components/Admin/Routes.razor index 78657b1..632d41d 100644 --- a/TaxBaik.Web/Components/Admin/Routes.razor +++ b/TaxBaik.Web/Components/Admin/Routes.razor @@ -2,7 +2,11 @@ - + + + + + diff --git a/tests/e2e/admin-login.spec.ts b/tests/e2e/admin-login.spec.ts index 91ceaa0..098ab7f 100644 --- a/tests/e2e/admin-login.spec.ts +++ b/tests/e2e/admin-login.spec.ts @@ -22,22 +22,10 @@ test.describe('admin authentication', () => { await expect(page.locator('input[placeholder="사용자명"]')).toBeVisible(); await expect(page.locator('input[placeholder="비밀번호"]')).toBeVisible(); - const token = await page.evaluate(async ({ baseUrl, username, password }) => { - const response = await fetch(`${baseUrl}/api/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }), - }); - if (!response.ok) { - return null; - } - const body = await response.json(); - return body?.token ?? null; - }, { baseUrl, username, password }); - expect(token, 'login API should return a token').toBeTruthy(); + await page.locator('input[placeholder="사용자명"]').fill(username); + await page.locator('input[placeholder="비밀번호"]').fill(password); + await page.getByRole('button', { name: '로그인' }).click(); - await page.addInitScript(value => localStorage.setItem('auth_token', value), token); - await page.goto(`${baseUrl}/admin/dashboard`); await expect(page).toHaveURL(/\/taxbaik\/admin\/dashboard$/); await expect(page.locator('text=대시보드')).toBeVisible({ timeout: 20_000 }); await expect(page.getByRole('link', { name: /로그아웃/ })).toBeVisible(); diff --git a/tests/e2e/admin-password-change.spec.ts b/tests/e2e/admin-password-change.spec.ts index 62de688..670a10a 100644 --- a/tests/e2e/admin-password-change.spec.ts +++ b/tests/e2e/admin-password-change.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test'; +import { getAdminToken, installAdminToken } from './helpers/admin-auth'; const username = process.env.E2E_ADMIN_USERNAME ?? 'admin'; const currentPassword = process.env.E2E_ADMIN_CURRENT_PASSWORD; @@ -6,24 +7,11 @@ const newPassword = process.env.E2E_ADMIN_NEW_PASSWORD; const baseUrl = (process.env.E2E_BASE_URL ?? 'http://178.104.200.7/taxbaik').replace(/\/$/, ''); test.describe('admin password change', () => { - test('changes password through the real admin UI', async ({ page }) => { + test('changes password through the real admin UI', async ({ page, request }) => { test.skip(!currentPassword || !newPassword, 'E2E_ADMIN_CURRENT_PASSWORD and E2E_ADMIN_NEW_PASSWORD are required.'); - const token = await page.evaluate(async ({ baseUrl, username, password }) => { - const response = await fetch(`${baseUrl}/api/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }), - }); - if (!response.ok) { - return null; - } - const body = await response.json(); - return body?.token ?? null; - }, { baseUrl, username, password: currentPassword }); - expect(token, 'login API should return a token').toBeTruthy(); - - await page.addInitScript(value => localStorage.setItem('auth_token', value), token); + const token = await getAdminToken(request, baseUrl, username, currentPassword); + await installAdminToken(page, token); await page.goto(`${baseUrl}/admin/settings`); await expect(page.getByRole('heading', { name: /사이트 설정|설정/ })).toBeVisible(); diff --git a/tests/e2e/admin-smoke.spec.ts b/tests/e2e/admin-smoke.spec.ts index 58ffe6e..2aa6445 100644 --- a/tests/e2e/admin-smoke.spec.ts +++ b/tests/e2e/admin-smoke.spec.ts @@ -1,11 +1,12 @@ import { expect, test } from '@playwright/test'; +import { getAdminToken, installAdminToken } from './helpers/admin-auth'; const username = process.env.E2E_ADMIN_USERNAME ?? 'admin'; const password = process.env.E2E_ADMIN_PASSWORD; const baseUrl = (process.env.E2E_BASE_URL ?? 'http://178.104.200.7/taxbaik').replace(/\/$/, ''); test.describe('admin smoke', () => { - test('navigates the main admin menus without circuit errors', async ({ page }) => { + test('navigates the main admin menus without circuit errors', async ({ page, request }) => { test.skip(!password, 'E2E_ADMIN_PASSWORD is required.'); const consoleErrors: string[] = []; @@ -18,21 +19,8 @@ test.describe('admin smoke', () => { consoleErrors.push(error.message); }); - const token = await page.evaluate(async ({ baseUrl, username, password }) => { - const response = await fetch(`${baseUrl}/api/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }), - }); - if (!response.ok) { - return null; - } - const body = await response.json(); - return body?.token ?? null; - }, { baseUrl, username, password }); - expect(token, 'login API should return a token').toBeTruthy(); - - await page.addInitScript(value => localStorage.setItem('auth_token', value), token); + const token = await getAdminToken(request, baseUrl, username, password); + await installAdminToken(page, token); const menuChecks = [ { path: '/admin/dashboard', heading: /대시보드/ }, diff --git a/tests/e2e/contact-submit.spec.ts b/tests/e2e/contact-submit.spec.ts index 4b3485a..e965086 100644 --- a/tests/e2e/contact-submit.spec.ts +++ b/tests/e2e/contact-submit.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test'; +import { getAdminToken, installAdminToken } from './helpers/admin-auth'; const username = process.env.E2E_ADMIN_USERNAME ?? 'admin'; const password = process.env.E2E_ADMIN_PASSWORD; @@ -27,21 +28,8 @@ test.describe('contact submit', () => { test.skip(!password, 'E2E_ADMIN_PASSWORD is required to verify the admin list.'); - const token = await page.evaluate(async ({ baseUrl, username, password }) => { - const response = await fetch(`${baseUrl}/api/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }), - }); - if (!response.ok) { - return null; - } - const body = await response.json(); - return body?.token ?? null; - }, { baseUrl, username, password }); - expect(token, 'login API should return a token').toBeTruthy(); - - await page.addInitScript(value => localStorage.setItem('auth_token', value), token); + const token = await getAdminToken(request, baseUrl, username, password); + await installAdminToken(page, token); await page.goto(`${baseUrl}/admin/inquiries`); await expect(page.getByText(name)).toBeVisible({ timeout: 20_000 }); await expect(page.getByText(phone)).toBeVisible(); diff --git a/tests/e2e/helpers/admin-auth.ts b/tests/e2e/helpers/admin-auth.ts new file mode 100644 index 0000000..8f4f68c --- /dev/null +++ b/tests/e2e/helpers/admin-auth.ts @@ -0,0 +1,21 @@ +import { expect, type APIRequestContext, type Page } from '@playwright/test'; + +export async function getAdminToken( + request: APIRequestContext, + baseUrl: string, + username: string, + password: string, +) { + const response = await request.post(`${baseUrl}/api/auth/login`, { + data: { username, password }, + }); + + expect(response.status(), 'login API should accept the configured admin credentials').toBe(200); + const body = await response.json(); + expect(body?.token, 'login API should return a token').toBeTruthy(); + return body.token as string; +} + +export async function installAdminToken(page: Page, token: string) { + await page.addInitScript(value => localStorage.setItem('auth_token', value), token); +} diff --git a/tests/e2e/inquiry-detail.spec.ts b/tests/e2e/inquiry-detail.spec.ts index 9b2e036..4fd97e0 100644 --- a/tests/e2e/inquiry-detail.spec.ts +++ b/tests/e2e/inquiry-detail.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from '@playwright/test'; +import { getAdminToken, installAdminToken } from './helpers/admin-auth'; const username = process.env.E2E_ADMIN_USERNAME ?? 'admin'; const password = process.env.E2E_ADMIN_PASSWORD; @@ -27,21 +28,8 @@ test.describe('inquiry detail', () => { test.skip(!password, 'E2E_ADMIN_PASSWORD is required to verify inquiry detail.'); - const token = await page.evaluate(async ({ baseUrl, username, password }) => { - const response = await fetch(`${baseUrl}/api/auth/login`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }), - }); - if (!response.ok) { - return null; - } - const body = await response.json(); - return body?.token ?? null; - }, { baseUrl, username, password }); - expect(token, 'login API should return a token').toBeTruthy(); - - await page.addInitScript(value => localStorage.setItem('auth_token', value), token); + const token = await getAdminToken(request, baseUrl, username, password); + await installAdminToken(page, token); await page.goto(`${baseUrl}/admin/inquiries`); const row = page.getByRole('row').filter({ hasText: name }).first(); await expect(row).toBeVisible({ timeout: 20_000 });