test(frontend): 대용량 파일을 다 올린 뒤 보낸다 — 조각 SHA-256 이 스레드 풀이라 부하에서 늦다
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Failing after 10m31s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m18s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 14m21s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m30s

쓰기 화면의 두 테스트는 대용량 파일을 고른 뒤 양보(flushPromises) 두 번 만에 다 올렸다고
보았다. 조각의 SHA-256 은 jsdom 이 아니라 Node 의 Web Crypto(libuv 스레드 풀)라, CI 부하에서
그 안에 끝나지 않았다. 다 올리기 전에 누른 [보내기]는 막혀 보내기 요청이 끝내 나가지 않아
payload 가 undefined 였다(2026-11-03 CI — ComposePage.spec.ts 첨부 합계 테스트).

- 두 테스트(첨부 합계 · 링크로 보내기)는 대용량 줄이 ready 가 될 때까지 vi.waitFor 로 본다.
  첨부 합계 테스트는 보내기 전에 막는 까닭이 없음도 단언한다.
- test-utils/timeouts.ts 에 WAIT_FOR_TIMEOUT_MS(15초 — 테스트 한도 20초보다 짧게)를 둔다.
- digest 를 늦춘 실험으로 고치기 전에는 깨지고 고친 뒤에는 통과함을 확인했다(실험은 되돌림).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-03 23:26:28 +09:00
parent 2cd2764da8
commit 349c312989
3 changed files with 31 additions and 3 deletions
@@ -21,6 +21,7 @@ import {
} from '@/test-utils/mock-api'
import { installProseMirrorDom } from '@/test-utils/prosemirror-dom'
import { createTestRouter, routeViewProvide } from '@/test-utils/router'
import { WAIT_FOR_TIMEOUT_MS } from '@/test-utils/timeouts'
import ComposePage from './ComposePage.vue'
// 쓰기 화면의 대용량 첨부 (11단계 — 디자인 원본 ComposeLargeFiles · 계약 4절 "발송 · 임시 저장과 함께" · "인용 속 링크의 상태")
@@ -367,7 +368,14 @@ describe('ComposePage — 대용량 첨부', () => {
expect(wrapper.get('#compose-send-why').text()).toBe(SEND_BLOCK_TEXT.uploading('1'))
releaseChunk()
await flushPromises()
// 조각을 받으면 끝내기까지 가 ready 가 된다 — 조각을 보내기 전의 SHA-256 은 Node 의 Web Crypto(스레드 풀)라
// 부하가 크면 양보 두 번으로는 끝나지 않는다 (ComposePage.spec.ts 의 첨부 합계 테스트와 같은 까닭) — ready 가 될 때까지 본다
await vi.waitFor(
() => {
if (phases(wrapper)[0] !== 'ready') throw new Error('대용량 파일을 올리는 중')
},
{ timeout: WAIT_FOR_TIMEOUT_MS },
)
await flushPromises()
expect(phases(wrapper)).toEqual(['ready'])
+14 -2
View File
@@ -36,7 +36,7 @@ import {
} from '@/test-utils/mock-api'
import { editorOf, fileDragEvent, installProseMirrorDom, pasteEvent } from '@/test-utils/prosemirror-dom'
import { createTestRouter, routeViewProvide } from '@/test-utils/router'
import { LONG_DRAFT_TIMEOUT_MS } from '@/test-utils/timeouts'
import { LONG_DRAFT_TIMEOUT_MS, WAIT_FOR_TIMEOUT_MS } from '@/test-utils/timeouts'
import ComposePage from './ComposePage.vue'
const USER = { email: 'ara.kim@arasystem.it.kr', name: '김아라' }
@@ -599,7 +599,17 @@ describe('ComposePage', () => {
configurable: true,
})
await picker.trigger('change')
await flushPromises()
// 도면은 다 올려야(끝내기 응답 뒤 ready) [보내기]가 풀린다 — 그 전에 누른 보내기는 막혀 보내기 요청이 끝내 나가지 않는다.
// 조각의 SHA-256 은 Node 의 Web Crypto(스레드 풀)라 부하가 크면 양보 두 번으로는 끝나지 않았다 (2026-11-03 CI) — ready 가 될 때까지 본다
await vi.waitFor(
() => {
const row = wrapper.find('[data-large-upload]')
if (!row.exists() || row.attributes('data-phase') !== 'ready') {
throw new Error('대용량 파일을 올리는 중')
}
},
{ timeout: WAIT_FOR_TIMEOUT_MS },
)
await flushPromises()
expect(wrapper.findAll('.upload-item').map((item) => item.classes())).toEqual([
@@ -612,6 +622,8 @@ describe('ComposePage', () => {
)
expect(requests.filter((request) => request.url === '/mail-attachments')).toHaveLength(1)
expect(useToastStore().toasts).toEqual([])
// 다 올렸다 — [보내기]를 막는 까닭이 없다 (아래 보내기는 마이크로태스크로만 이어져 양보 한 번이면 요청이 나간다)
expect(wrapper.find('#compose-send-why').exists()).toBe(false)
const input = wrapper.get('#compose-to')
await input.setValue('junho.park@arasystem.it.kr')
+8
View File
@@ -9,3 +9,11 @@
* 전체 한도에 가까워 이 테스트들에만 30초를 준다.
*/
export const LONG_DRAFT_TIMEOUT_MS = 30_000
/**
* 조건이 될 때까지 다시 보는 기다림(vi.waitFor)의 기한 — 몇 번 양보(flushPromises)해서는 끝난다고 믿을 수 없는 일을 기다린다.
* 예: 대용량 파일 조각의 SHA-256 — jsdom 이 아니라 Node 의 Web Crypto 라 스레드 풀에서 돌아, 부하가 크면 양보 두 번 안에 끝나지 않았다
* (2026-11-03 CI — 다 올리기 전에 누른 [보내기]는 막혀 보내기 요청이 끝내 나가지 않는다).
* 테스트 한도(vitest.config.ts, 20초)보다 짧게 둬, 끝내 되지 않으면 테스트 한도보다 이 기다림이 먼저 까닭(던진 글)을 알린다.
*/
export const WAIT_FOR_TIMEOUT_MS = 15_000