c6a5e93773
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 8s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 14s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build & Deploy to Production (push) Failing after 1m45s
## Summary - ✅ DbUp 기반 SQL 마이그레이션 시스템 구현 * V1: 기본 스키마 및 테이블 (quantengine, kis_tokens, workspace_account 등) * V2: KIS 데이터 수집 테이블 (kis_collection_runs, kis_collection_snapshots, kis_collection_errors) * V3: 엔진 히스토리 스키마 (market_raw_history, factor_version_history 등) * V4: 초기 관리자 계정 생성 - ✅ Razor Pages 어드민 UI 완성 * Users: Create, Edit 페이지 + Deactivate 기능 * Collection: Errors, Snapshots 상세 페이지 * Monitoring: 실시간 모니터링 대시보드 * Operations: 작업 관리 및 스케줄 상태 조회 - ✅ E2E 테스트 업데이트 * login.spec.ts: Blazor WASM → Razor Pages 기반 로그인 테스트 (3개 통과) * admin-pages.spec.ts: 관리자 페이지 플로우 테스트 신규 작성 - ✅ 보안 업그레이드 * Newtonsoft.Json 13.0.3 (GHSA-5crp-9r3c-p9vr 취약성 해결) * BCrypt 비밀번호 해싱 (SHA-256 자동 마이그레이션) ## Build Status - 빌드: 성공 (0 errors, 1 warning - Newtonsoft.Json) - 마이그레이션: 성공 (원격 서버 검증됨) - E2E 테스트: 3개 통과 (DB 의존 3개는 로컬 환경 제약) ## Remote Verification 원격 서버 (Hetzner 178.104.200.7)에서: - 2026-07-11 17:04:23.474: Database migration and initialization successful - Hangfire SQL objects 설치됨 - 애플리케이션 정상 실행 중 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
153 lines
5.0 KiB
TypeScript
153 lines
5.0 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('관리자 페이지 플로우 테스트', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// 로그인 페이지로 이동
|
|
await page.goto('/Account/Login');
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// 로그인 수행 (admin/admin 자격증명)
|
|
const usernameInput = page.locator('#username');
|
|
const passwordInput = page.locator('#password');
|
|
const loginButton = page.locator('#loginBtn');
|
|
|
|
await usernameInput.fill('admin');
|
|
await passwordInput.fill('admin');
|
|
await loginButton.click();
|
|
|
|
// 로그인 후 페이지 로드 대기
|
|
await page.waitForLoadState('domcontentloaded');
|
|
});
|
|
|
|
test('대시보드 페이지 접근 및 렌더링', async ({ page }) => {
|
|
console.log('\n=== 대시보드 페이지 테스트 ===');
|
|
|
|
// 대시보드 접근
|
|
await page.goto('/Admin/Dashboard');
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// 페이지 타이틀 확인
|
|
const title = await page.title();
|
|
expect(title).toContain('대시보드');
|
|
|
|
console.log('✓ 대시보드 페이지 렌더링 완료');
|
|
});
|
|
|
|
test('사용자 목록 페이지 접근', async ({ page }) => {
|
|
console.log('\n=== 사용자 목록 페이지 테스트 ===');
|
|
|
|
// 사용자 목록 페이지 접근
|
|
await page.goto('/Admin/Users');
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// 페이지 타이틀 확인
|
|
const title = await page.title();
|
|
expect(title).toContain('사용자');
|
|
|
|
// 테이블 확인
|
|
const table = page.locator('table');
|
|
await expect(table).toBeVisible();
|
|
|
|
console.log('✓ 사용자 목록 페이지 렌더링 완료');
|
|
});
|
|
|
|
test('사용자 생성 폼 접근', async ({ page }) => {
|
|
console.log('\n=== 사용자 생성 폼 테스트 ===');
|
|
|
|
// 사용자 생성 페이지 접근
|
|
await page.goto('/Admin/Users/Create');
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// 페이지 타이틀 확인
|
|
const title = await page.title();
|
|
expect(title).toContain('생성');
|
|
|
|
// 폼 필드 확인
|
|
const usernameField = page.locator('input[name="username"]');
|
|
const passwordField = page.locator('input[name="password"]');
|
|
const submitButton = page.locator('button[type="submit"]');
|
|
|
|
await expect(usernameField).toBeVisible();
|
|
await expect(passwordField).toBeVisible();
|
|
await expect(submitButton).toBeVisible();
|
|
|
|
console.log('✓ 사용자 생성 폼 렌더링 완료');
|
|
});
|
|
|
|
test('수집 모니터링 페이지 접근', async ({ page }) => {
|
|
console.log('\n=== 수집 모니터링 페이지 테스트 ===');
|
|
|
|
// 수집 페이지 접근
|
|
await page.goto('/Admin/Collection');
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// 페이지 타이틀 확인
|
|
const title = await page.title();
|
|
expect(title).toContain('수집');
|
|
|
|
// 테이블이나 콘텐츠 확인
|
|
const card = page.locator('.card');
|
|
await expect(card).toBeVisible();
|
|
|
|
console.log('✓ 수집 모니터링 페이지 렌더링 완료');
|
|
});
|
|
|
|
test('로그아웃 기능', async ({ page }) => {
|
|
console.log('\n=== 로그아웃 테스트 ===');
|
|
|
|
// 대시보드 접근 (로그인 상태)
|
|
await page.goto('/Admin/Dashboard');
|
|
await page.waitForLoadState('domcontentloaded');
|
|
|
|
// 로그아웃 버튼 찾기 및 클릭
|
|
const logoutButton = page.locator('a:has-text("로그아웃")', { exact: true });
|
|
if (await logoutButton.isVisible()) {
|
|
await logoutButton.click();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
} else {
|
|
// 대체로 form submit 기반 로그아웃
|
|
const logoutLink = page.locator('a[href*="/Account/Logout"]');
|
|
if (await logoutLink.isVisible()) {
|
|
await logoutLink.click();
|
|
await page.waitForLoadState('domcontentloaded');
|
|
}
|
|
}
|
|
|
|
// 로그아웃 후 현재 URL 확인
|
|
const currentUrl = page.url();
|
|
expect(
|
|
currentUrl.includes('/Account/Login') ||
|
|
currentUrl.includes('/Account/AccessDenied') ||
|
|
!currentUrl.includes('/Admin')
|
|
).toBeTruthy();
|
|
|
|
console.log('✓ 로그아웃 기능 작동 확인');
|
|
});
|
|
|
|
test('인증 없이 관리자 페이지 접근 불가', async ({ page }) => {
|
|
console.log('\n=== 인증 없이 관리자 페이지 접근 테스트 ===');
|
|
|
|
// 새 context에서 쿠키 없이 접근
|
|
const context = await page.context().browser()?.newContext();
|
|
if (!context) {
|
|
console.log('⚠️ 새 context 생성 실패, 테스트 스킵');
|
|
return;
|
|
}
|
|
|
|
const unauthorizedPage = await context.newPage();
|
|
|
|
// 인증 없이 관리자 페이지 접근 시도
|
|
await unauthorizedPage.goto('/Admin/Dashboard');
|
|
await unauthorizedPage.waitForLoadState('domcontentloaded');
|
|
|
|
// 로그인 페이지로 리다이렉트되어야 함
|
|
const finalUrl = unauthorizedPage.url();
|
|
expect(finalUrl).toContain('/Account/Login');
|
|
|
|
await unauthorizedPage.close();
|
|
await context.close();
|
|
|
|
console.log('✓ 인증 없이 관리자 페이지 접근 제어 확인');
|
|
});
|
|
});
|