v1.12.31 불필요 파일 정리 및 업로드·배포 안정성 보강

This commit is contained in:
2026-10-21 19:10:01 +09:00
parent 7b6fc45bf3
commit 0cd888a427
17 changed files with 170 additions and 59 deletions

View File

@@ -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()

View File

@@ -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 })
}
})