v1.12.31 불필요 파일 정리 및 업로드·배포 안정성 보강
This commit is contained in:
@@ -464,6 +464,30 @@ test('긴 접속 시간은 실시간에 표시하고 평균 체류 집계 상한
|
||||
}
|
||||
})
|
||||
|
||||
test('공개 업로드는 내부 가져오기 파일의 정규화된 우회 경로도 차단한다', async ({ request: anonymousApi }) => {
|
||||
const uploadRoot = process.env.SORI_TEST_UPLOAD_ROOT
|
||||
const privateDirectory = join(uploadRoot, '_import-jobs', 'path-access-test')
|
||||
const publicFile = join(uploadRoot, 'path-access-public.txt')
|
||||
await mkdir(privateDirectory, { recursive: true })
|
||||
await writeFile(join(privateDirectory, 'private.txt'), 'private-test-content')
|
||||
await writeFile(publicFile, 'public-test-content')
|
||||
try {
|
||||
const publicResponse = await anonymousApi.get('/uploads/path-access-public.txt')
|
||||
expect(publicResponse.status()).toBe(200)
|
||||
expect(await publicResponse.text()).toBe('public-test-content')
|
||||
for (const path of [
|
||||
'/uploads/_import-jobs/path-access-test/private.txt',
|
||||
'/uploads/folder%2F..%2F_import-jobs/path-access-test/private.txt',
|
||||
'/uploads/%2F_import-jobs/path-access-test/private.txt'
|
||||
]) {
|
||||
expect((await anonymousApi.get(path)).status(), path).toBe(404)
|
||||
}
|
||||
} finally {
|
||||
await rm(privateDirectory, { recursive: true, force: true })
|
||||
await rm(publicFile, { force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('미디어 카탈로그 reconcile과 DB 목록 계약 및 fallback을 유지한다', async () => {
|
||||
const uploadRoot = process.env.SORI_TEST_UPLOAD_ROOT
|
||||
const mediaDirectory = join(uploadRoot, 'posts', 'catalog-api-test')
|
||||
@@ -556,9 +580,11 @@ test('미디어 카탈로그 reconcile과 DB 목록 계약 및 fallback을 유
|
||||
const avatarResult = await avatarResponse.json()
|
||||
expect(avatarResult.items[0].avatarOwner.id).toBe(adminUser.userId)
|
||||
expect(avatarResult.items[0].avatarOwner.username).toBeTruthy()
|
||||
const userUsage = avatarResult.items[0].usage.find((usage) => usage.type === 'user')
|
||||
expect(userUsage.typeLabel).toBe('회원')
|
||||
expect(userUsage.adminUrl).toBe(`/admin/members/${adminUser.userId}`)
|
||||
expect(avatarResult.items[0].usage.some((usage) => usage.type === 'user')).toBe(false)
|
||||
const unusedAvatars = await adminApi.get('/admin/api/media?paged=1&tab=all&unused=1&search=catalog-owner')
|
||||
expect((await unusedAvatars.json()).pagination.total).toBe(0)
|
||||
const deleteAvatarResponse = await adminApi.delete('/admin/api/media', { data: { url: avatarUrl } })
|
||||
expect(deleteAvatarResponse.status()).toBe(409)
|
||||
|
||||
const navigationResponse = await adminApi.get('/admin/api/media?paged=1&search=catalog-02&pageSize=50')
|
||||
const navigationResult = await navigationResponse.json()
|
||||
|
||||
@@ -63,3 +63,36 @@ test('운영 이미지 빌드는 읽을 수 없는 환경 파일을 거부한다
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test('운영 마이그레이션은 SQL과 이력을 같은 트랜잭션으로 실행하고 실패하면 중단한다', () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'sori-migration-test-'))
|
||||
try {
|
||||
const logPath = join(directory, 'docker.log')
|
||||
const envPath = join(directory, 'production.env')
|
||||
writeFileSync(envPath, 'COMPOSE_PROJECT_NAME=sori-studio\n')
|
||||
writeFileSync(join(directory, 'docker'), `#!/bin/sh
|
||||
printf "<%s>" "$@" >> "$SORI_TEST_DOCKER_LOG"
|
||||
printf "\\n" >> "$SORI_TEST_DOCKER_LOG"
|
||||
for argument in "$@"; do
|
||||
case "$argument" in
|
||||
*"to_regclass('public.schema_migrations')"*) printf 't\\n' ;;
|
||||
--single-transaction) exit 19 ;;
|
||||
esac
|
||||
done
|
||||
`, { mode: 0o755 })
|
||||
const result = spawnSync('sh', ['scripts/migrate-production-db.sh', 'migrate'], {
|
||||
cwd: root, encoding: 'utf8',
|
||||
env: { ...process.env, PATH: `${directory}:${process.env.PATH}`, ENV_FILE: envPath, SORI_TEST_DOCKER_LOG: logPath }
|
||||
})
|
||||
assert.equal(result.status, 19, result.stderr)
|
||||
const calls = readFileSync(logPath, 'utf8').trim().split('\n')
|
||||
const transactions = calls.filter(call => call.includes('<--single-transaction>'))
|
||||
assert.equal(transactions.length, 1)
|
||||
assert.ok(transactions[0].startsWith(`<compose><-f><${join(root, 'docker-compose.yaml')}><--env-file><${envPath}>`))
|
||||
assert.match(transactions[0], /<-f><\/docker-entrypoint-initdb.d\/001_[^>]+\.sql>/)
|
||||
assert.match(transactions[0], /<-c><INSERT INTO schema_migrations \(file_name\) VALUES \('001_[^']+\.sql'\)/)
|
||||
assert.doesNotMatch(result.stdout, /마이그레이션 실행 완료/)
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user