v1.10.88 품질·보안 개선과 미디어 카탈로그 database 전환을 반영한다.
세션·로그인 제한, ZIP/업로드 스트리밍, CSP 최소 강제와 Report-Only, Playwright 격리 검증을 추가하고 미디어 목록을 reconcile 후 database 모드로 고정한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
100
tests/playwright/e2e/admin-guard-and-settings.spec.js
Normal file
100
tests/playwright/e2e/admin-guard-and-settings.spec.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
const adminEmail = 'playwright-admin@example.com'
|
||||
const adminPassword = 'playwright-admin-password'
|
||||
|
||||
/**
|
||||
* 관리자 화면에서 실제 로그인 API를 거쳐 로그인한다.
|
||||
* @param {import('@playwright/test').Page} page - 브라우저 페이지
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const loginAdmin = async (page) => {
|
||||
await page.goto('/admin/login')
|
||||
await page.getByLabel('이메일').fill(adminEmail)
|
||||
await page.locator('input[type="password"]').fill(adminPassword)
|
||||
|
||||
await Promise.all([
|
||||
page.waitForResponse((response) =>
|
||||
response.url().endsWith('/admin/api/auth/login') && response.status() === 200
|
||||
),
|
||||
page.getByRole('button', { name: '로그인' }).click()
|
||||
])
|
||||
await page.waitForURL(/\/admin(?:\/)?$/)
|
||||
}
|
||||
|
||||
test('설정 카드 하나만 저장하면 다른 카드의 미저장 입력을 보존한다', async ({ page }) => {
|
||||
const savedTitle = 'Playwright 독립 저장 사이트'
|
||||
const unsavedCopyright = 'Playwright 미저장 저작권'
|
||||
|
||||
await loginAdmin(page)
|
||||
await page.goto('/admin/settings')
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
const titleCard = page.getByTestId('title-and-description')
|
||||
const siteInfoCard = page.locator('#admin-settings-section-misc')
|
||||
|
||||
await titleCard.getByRole('button', { name: '편집' }).click()
|
||||
await titleCard.getByLabel('사이트 이름').fill(savedTitle)
|
||||
|
||||
await siteInfoCard.getByRole('button', { name: '편집' }).click()
|
||||
const copyrightInput = siteInfoCard.getByLabel('저작권 문구')
|
||||
const originalCopyright = await copyrightInput.inputValue()
|
||||
await copyrightInput.fill(unsavedCopyright)
|
||||
|
||||
const saveResponsePromise = page.waitForResponse((response) =>
|
||||
response.url().endsWith('/admin/api/settings')
|
||||
&& response.request().method() === 'PATCH'
|
||||
&& response.status() === 200
|
||||
)
|
||||
await titleCard.getByRole('button', { name: '저장', exact: true }).click()
|
||||
const saveResponse = await saveResponsePromise
|
||||
|
||||
expect(saveResponse.request().postDataJSON()).toEqual({
|
||||
title: savedTitle,
|
||||
description: expect.any(String)
|
||||
})
|
||||
await expect(copyrightInput).toHaveValue(unsavedCopyright)
|
||||
|
||||
await page.reload()
|
||||
await page.waitForLoadState('networkidle')
|
||||
await expect(titleCard).toContainText(savedTitle)
|
||||
|
||||
await siteInfoCard.getByRole('button', { name: '편집' }).click()
|
||||
await expect(siteInfoCard.getByLabel('저작권 문구')).toHaveValue(originalCopyright)
|
||||
})
|
||||
|
||||
test('발행 글의 미저장 변경에서 이탈을 취소하면 입력과 경로를 유지한다', async ({ page }) => {
|
||||
const originalTitle = 'Playwright Unsaved Guard'
|
||||
const changedTitle = 'Playwright Unsaved Guard Changed'
|
||||
|
||||
await loginAdmin(page)
|
||||
const createResponse = await page.request.post('/admin/api/posts', {
|
||||
data: {
|
||||
title: originalTitle,
|
||||
slug: 'playwright-unsaved-guard',
|
||||
content: '미저장 이탈 보호 E2E 본문',
|
||||
excerpt: '',
|
||||
status: 'published',
|
||||
publishedAt: new Date().toISOString(),
|
||||
tags: [],
|
||||
tagNames: {}
|
||||
}
|
||||
})
|
||||
expect(createResponse.status()).toBe(200)
|
||||
const post = await createResponse.json()
|
||||
|
||||
await page.goto(`/admin/posts/${post.id}`)
|
||||
await page.waitForLoadState('networkidle')
|
||||
const titleInput = page.getByPlaceholder('제목')
|
||||
await titleInput.fill(changedTitle)
|
||||
await expect(titleInput).toHaveValue(changedTitle)
|
||||
|
||||
await page.getByRole('link', { name: 'Posts', exact: true }).click()
|
||||
const dialog = page.getByRole('dialog', { name: '이 페이지를 떠날까요?' })
|
||||
await expect(dialog).toBeVisible()
|
||||
await dialog.getByRole('button', { name: '머무르기' }).click()
|
||||
|
||||
await expect(dialog).toBeHidden()
|
||||
await expect(page).toHaveURL(new RegExp(`/admin/posts/${post.id}$`))
|
||||
await expect(titleInput).toHaveValue(changedTitle)
|
||||
})
|
||||
38
tests/playwright/e2e/admin-publish.spec.js
Normal file
38
tests/playwright/e2e/admin-publish.spec.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
test('관리자가 로그인해 게시물을 발행하고 공개 화면에서 확인한다', async ({ page }) => {
|
||||
const title = 'Playwright E2E Post'
|
||||
const slug = 'playwright-e2e-post'
|
||||
|
||||
await page.goto('/admin/login')
|
||||
await page.getByLabel('이메일').fill('playwright-admin@example.com')
|
||||
await page.locator('input[type="password"]').fill('playwright-admin-password')
|
||||
|
||||
await Promise.all([
|
||||
page.waitForResponse((response) =>
|
||||
response.url().endsWith('/admin/api/auth/login') && response.status() === 200
|
||||
),
|
||||
page.getByRole('button', { name: '로그인' }).click()
|
||||
])
|
||||
await page.waitForURL(/\/admin(?:\/)?$/)
|
||||
|
||||
await page.goto('/admin/posts/new')
|
||||
await page.waitForLoadState('networkidle')
|
||||
await page.getByPlaceholder('제목').fill(title)
|
||||
await expect(page.getByPlaceholder('제목')).toHaveValue(title)
|
||||
|
||||
const publishResponsePromise = page.waitForResponse((response) =>
|
||||
response.url().endsWith('/admin/api/posts')
|
||||
&& response.request().method() === 'POST'
|
||||
&& response.status() === 200
|
||||
)
|
||||
await expect(page.getByRole('button', { name: 'Publish', exact: true })).toBeEnabled()
|
||||
await page.getByRole('button', { name: 'Publish', exact: true }).click()
|
||||
await expect(page.getByRole('heading', { name: '발행' })).toBeVisible()
|
||||
await page.getByRole('button', { name: '최종 확인하고 저장 →' }).click()
|
||||
await publishResponsePromise
|
||||
await page.waitForURL(/\/admin\/posts\/[0-9a-f-]+$/)
|
||||
|
||||
await page.goto(`/post/${slug}`)
|
||||
await expect(page.getByRole('heading', { name: title, exact: true })).toBeVisible()
|
||||
})
|
||||
Reference in New Issue
Block a user