v1.10.88 품질·보안 개선과 미디어 카탈로그 database 전환을 반영한다.
세션·로그인 제한, ZIP/업로드 스트리밍, CSP 최소 강제와 Report-Only, Playwright 격리 검증을 추가하고 미디어 목록을 reconcile 후 database 모드로 고정한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
98
tests/media-catalog.test.js
Normal file
98
tests/media-catalog.test.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import {
|
||||
detectMediaKind,
|
||||
detectMediaRole,
|
||||
escapePostgresLikeLiteral,
|
||||
extractMediaReferences,
|
||||
normalizeMediaCatalogUrl
|
||||
} from '../lib/media-catalog.js'
|
||||
|
||||
test('SQL LIKE wildcard 검색 문자를 리터럴로 이스케이프한다', () => {
|
||||
assert.equal(escapePostgresLikeLiteral('100%_done!'), '100!%!_done!!')
|
||||
})
|
||||
|
||||
test('미디어 URL을 저장 키로 정규화하고 쿼리를 제거한다', () => {
|
||||
assert.deepEqual(
|
||||
normalizeMediaCatalogUrl('/uploads/posts/2026/07/%ED%95%9C%EA%B8%80.png?v=1'),
|
||||
{
|
||||
url: '/uploads/posts/2026/07/한글.png',
|
||||
storageKey: 'posts/2026/07/한글.png'
|
||||
}
|
||||
)
|
||||
assert.deepEqual(
|
||||
normalizeMediaCatalogUrl('/uploads/posts/literal-100%.png'),
|
||||
{
|
||||
url: '/uploads/posts/literal-100%.png',
|
||||
storageKey: 'posts/literal-100%.png'
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('업로드 루트 이탈과 인코딩된 traversal을 차단한다', () => {
|
||||
assert.throws(() => normalizeMediaCatalogUrl('/uploads/../secret.txt'), /MEDIA_URL_TRAVERSAL/)
|
||||
assert.throws(() => normalizeMediaCatalogUrl('/uploads/%2e%2e/secret.txt'), /MEDIA_URL_TRAVERSAL/)
|
||||
assert.throws(() => normalizeMediaCatalogUrl('/etc/passwd'), /MEDIA_URL_OUTSIDE_UPLOADS/)
|
||||
})
|
||||
|
||||
test('확장자 종류와 시스템 역할 및 파생 원본 후보를 판별한다', () => {
|
||||
assert.equal(detectMediaKind('photo.webp'), 'image')
|
||||
assert.equal(detectMediaKind('clip.mp4'), 'video')
|
||||
assert.equal(detectMediaKind('voice.m4a'), 'audio')
|
||||
assert.equal(detectMediaKind('document.pdf'), 'file')
|
||||
assert.deepEqual(detectMediaRole('exports/posts.zip'), {
|
||||
role: 'export',
|
||||
parentStorageKey: null
|
||||
})
|
||||
assert.deepEqual(detectMediaRole('posts/2026/07/thumbs/photo-card.webp'), {
|
||||
role: 'card',
|
||||
parentStorageKey: 'posts/2026/07/photo.jpg'
|
||||
})
|
||||
assert.equal(detectMediaRole('members/avatars/2026/07/profile.webp').role, 'avatar')
|
||||
})
|
||||
|
||||
test('본문 720·1600 파생 URL을 레거시 body와 구분한다', async () => {
|
||||
const {
|
||||
isPostBody1600ThumbnailUrl,
|
||||
isPostBody720ThumbnailUrl,
|
||||
isPostBodyThumbnailUrl
|
||||
} = await import('../lib/post-card-thumbnail-url.js')
|
||||
|
||||
assert.equal(
|
||||
isPostBodyThumbnailUrl('/uploads/posts/2026/07/thumbs/photo-body.webp'),
|
||||
true
|
||||
)
|
||||
assert.equal(
|
||||
isPostBody1600ThumbnailUrl('/uploads/posts/2026/07/thumbs/photo-body.webp'),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
isPostBody720ThumbnailUrl('/uploads/posts/2026/07/thumbs/photo-body.webp'),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
isPostBody1600ThumbnailUrl('/uploads/posts/2026/07/thumbs/photo-body-1600.webp'),
|
||||
true
|
||||
)
|
||||
assert.equal(
|
||||
isPostBody720ThumbnailUrl('/uploads/posts/2026/07/thumbs/photo-body-720.webp'),
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
test('필드별 미디어 참조를 추출하고 같은 필드 중복을 제거한다', () => {
|
||||
assert.deepEqual(extractMediaReferences({
|
||||
content: ' 다시 /uploads/posts/a.png',
|
||||
featuredImage: '/uploads/posts/a.png',
|
||||
external: 'https://example.com/image.png'
|
||||
}), [
|
||||
{
|
||||
mediaUrl: '/uploads/posts/a.png',
|
||||
referenceField: 'content'
|
||||
},
|
||||
{
|
||||
mediaUrl: '/uploads/posts/a.png',
|
||||
referenceField: 'featuredImage'
|
||||
}
|
||||
])
|
||||
})
|
||||
Reference in New Issue
Block a user