# 통합 테스트

## 실행 명령

빠른 기본 검증은 Core의 canonical snapshot, comparator, log/message projection과
logical clock 계약만 실행합니다. Ref runtime이나 DB가 없어도 되며 skip을 성공으로
세지 않습니다.

```sh
pnpm test:integration
```

실제 경계는 목적별 lane으로 분리합니다.

```sh
# 고유 PostgreSQL schema와 Redis namespace
pnpm test:integration:conditional

# 필요할 때 실행하는 Ref 호환성 감사
pnpm test:integration:reference

# public/che schema와 Redis를 초기화하는 전용 stack 전용 lifecycle
INTEGRATION_LIFECYCLE_DISPOSABLE=1 pnpm test:integration:lifecycle
```

`tools/integration-tests/test-lanes.tsv`는 모든 `test/**/*.test.ts`를 `core`,
`conditional`, `reference`, `lifecycle` 중 정확히 하나에 배정합니다. 기본 runner는
실행 전에 실제 파일 집합과 registry를 대조하므로 새 파일의 미등록이나 중복 등록을
허용하지 않습니다. Ref 차등과 lifecycle을 기본 명령에서 분리한 것은 test 삭제나
silent skip이 아니라 실행 비용·외부 상태·판정 권위를 분리한 것입니다.

Ref는 매 변경의 제품 실행 의존성이 아닙니다. 계승 계약을 새로 이관하거나 전투·RNG·
명령 의미가 바뀌는 변경, 정기 호환성 감사에서는 `test:integration:reference`를
실행합니다. 일반 Core 변경은 Core 자체의 versioned fixture, canonical projection과
불변조건을 기본 acceptance로 사용합니다. 확인된 의도적 제품 차이는 Core 기대값을
갱신하고 문서화하며, Ref 결과에 자동으로 되맞추지 않습니다.

조건부 runner는 환경 변수 존재 여부만으로 안전성을 보장하지 않습니다.
대상 host, port, database와 Redis prefix가 해당 worktree 전용인지 확인해
주세요. Runner는 실행 ID가 포함된 game schema를 생성하고 성공, 테스트 실패,
`HUP`, `INT`, `TERM` 종료에서 자신이 생성한 schema만 삭제합니다. 사용자가
schema 환경 변수를 지정한 경우에도 이미 존재하는 schema는 거부합니다.
HTTP transport fixture의 Redis key도 실행 ID를 profile namespace에 포함하고,
runner 종료 시 그 실행 ID에 속한 key만 삭제합니다. 공유 Redis 전체에
`FLUSHDB`나 `FLUSHALL`을 실행하지 않습니다.
Battle simulator fixture도 실행 ID가 포함된 queue/result/notify namespace를
사용합니다. 실행 중단 시 테스트의 `finally`가 실행되지 않아도 runner가 같은
실행 ID의 key만 찾아 삭제합니다.
Runner는 test process group에 종료 신호를 전달하고 기본 10초 안에 종료되지
않으면 `SIGKILL`로 전환한 뒤 schema와 Redis cleanup을 계속합니다. 이 유예
시간은 중단 경계 검증에서만 `CONDITIONAL_INTEGRATION_TERM_GRACE_SECONDS`
(1~60초)로 줄일 수 있습니다.
`SIGKILL`은 cleanup trap을 실행할 수 없으므로 자동 정리를 보장하지 않습니다.

## 준비

상위 작업공간의 개발 stack을 사용할 수 있습니다.

```sh
cd ../docker_compose_files/development
./scripts/prepare-instance.sh docs-check 15443 16389 ../../core2026
./scripts/compose.sh docs-check up -d --wait

cd ../../core2026
pnpm install --frozen-lockfile
pnpm --filter @sammo-ts/infra prisma:generate
pnpm --filter @sammo-ts/common build
pnpm --filter @sammo-ts/logic build
pnpm test:integration:conditional
```

Instance 이름과 port는 다른 worktree와 겹치지 않게 정합니다. 생성된 `.env`,
`.env.ci`, log와 DB volume은 Git에 추가하지 않습니다.

HTTP lifecycle fixture는 disposable profile에
`localAccountGeneralCreationGraceDays`를 명시하고 실제 profile 정책을
통과합니다. PM2 orchestrator fixture는 전용 `PM2_HOME`을 사용하며, 다섯
runtime role을 삭제하고 PID와 명령행 및 daemon 종료를 확인한 뒤에만 임시
디렉터리를 정리합니다. 운영자의 전역 PM2 daemon과 경로를 공유하지 않습니다.

## 범위

통합 suite는 다음 경계를 포함합니다.

- gateway/game Prisma 연결과 schema
- Redis session, queue와 pub/sub
- scenario initialization과 profile schema
- `InputEvent` 원자성, 재시도와 중복 request
- turn daemon lease, heartbeat, fencing과 takeover
- 예약 턴 revision/CAS와 API/daemon 경합
- auth header, role, sanction과 owner별 HTTP transport
- ref/core command snapshot, RNG trace와 persistence
- auction, tournament와 worker transaction
- 관리자 시간 가속·연기의 durable action, checkpoint, Redis 부분 재시도와
  경매 timer race

실제 포함 suite는 `tools/integration-tests/test-lanes.tsv`,
`tools/run-conditional-integration.sh`, 각 package의 `package.json`,
`*.integration.test.ts`를 기준으로 확인합니다. DB 조건부
환경 변수는 `tools/conditional-integration-registry.tsv`에서 명시적으로
관리합니다. 새 `*_DATABASE_URL` gate가 registry에 없거나 registry 항목이
더 이상 테스트에 존재하지 않으면 runner가 테스트 실행 전에 실패합니다.
Registry는 marker 존재 여부뿐 아니라 중복, 형식과 지원 execution mode도
검사합니다. 지원하지 않는 mode로 인해 test가 실행 group에서 빠지는 경우에도
runner는 test 시작 전에 실패합니다. 지원 mode에 marker가 하나도 없거나
실행 group의 marker 정규식이 비어도 전체 파일로 선택 범위를 넓히지 않고
실패합니다.

DB marker가 없는 Ref 조건부 suite와 저장 trace 비교 suite는
`tools/conditional-integration-file-registry.tsv`에서 파일별 환경 요구를 관리합니다.
Runner는 이 registry를 실제 `describe.skipIf()` gate와 exact-set으로 대조합니다.
`TURN_DIFFERENTIAL_REFERENCE=1`일 때만 Ref runtime 그룹을 실행하고, 저장 trace 비교는
`TURN_REFERENCE_TRACE`와 `TURN_CORE_TRACE`가 모두 있을 때만 실행합니다. 한 trace만
주입한 경우에는 suite를 조용히 skip하지 않고 설정 오류로 실패합니다. DB/Redis marker가
있는 파일을 이 registry에 중복 등록할 수도 없습니다.

조건부 실행 결과는 Vitest의 file status만 세지 않습니다. 한 파일 안의 guard test가
통과해 file status가 `passed`여도 실제 조건부 assertion 하나가 `skipped`, `pending`,
`todo` 또는 `disabled`라면 runner가 실패합니다. 따라서 실행 대상으로 등록한 기능 test가
일부라도 조용히 생략된 mixed file을 0-skip 결과로 보고할 수 없습니다.

`external_fixture` mode는 격리 빈 schema로 만들 수 없는 명시적 예외입니다.
현재 `CURRENT_SEASON_FIXTURE_DATABASE_URL`은 별도 Ref 현 시즌 importer가 만든
read-only snapshot을 요구하므로 조건부 runner의 pass/skip 집계에 포함하지
않습니다. 이 mode는 registry 누락을 숨기기 위한 일반 제외 수단이 아니며,
해당 fixture 검증은 실제 imported snapshot URL을 주입해 별도로 실행하고 결과를
외부 fixture 범위로 보고해야 합니다.

관리자 시간 조정의 PostgreSQL 경계는
`runtimeClockShiftPersistence.integration.test.ts`, gateway action
`PARTIAL → APPLIED` 경계는 `gatewayRuntimeAction.integration.test.ts`입니다.
Runner는 후자에 `GATEWAY_RUNTIME_ACTION_DATABASE_URL`, 전자에
`INPUT_EVENT_DATABASE_URL`을 같은 격리 schema URL로 주입합니다. 장수 생성,
선택 pool, 즉시 장수 action처럼 별도 marker를 사용하는 테스트도 registry를
통해 포함합니다. DB와 Redis가 함께 필요한 파일은 DB group에서 한 번만
실행하고 Redis-only 파일만 별도 group에서 실행합니다.

## 안전 경계

- Test는 game schema table을 truncate할 수 있습니다.
- Redis key 또는 선택한 DB index를 정리할 수 있습니다.
- 운영·공유 DB URL을 사용하지 않습니다.
- Volume 삭제는 사용자가 데이터 폐기를 명시한 경우에만 수행합니다.
- Secret과 펼쳐진 `docker compose config` 출력은 report나 artifact에 넣지
  않습니다.

## 결과 해석

환경이 없어 skip된 test는 실행되지 않은 것입니다. Mock connector test는 실제
PostgreSQL·Redis 경계를 증명하지 않습니다. Full suite 실패는 변경 worktree와
변경 없는 `main`에서 각각 재현해 회귀와 baseline을 구분합니다.

조건부 runner는 마지막 줄에 통과, skip, 실패한 test file 수를 출력하며,
하나라도 file 전체가 skip되면 성공으로 처리하지 않습니다. 실행이 중단된 경우도
이미 완료된 group의 집계와 schema cleanup 결과를 확인해 주세요.

Ref 호환성 판정은 [차등 검증](architecture/turn-state-differential-testing.md),
UI는 [프론트엔드 호환 검증](frontend-legacy-parity.md)을 함께 사용합니다.

Ref 감사 lane의 전투 아이템 전체 차등 검증은 공격자·방어자 각각 145개 fixture를
모두 Core와 Ref에서 실행하고 event, RNG, outcome, 전체 log bucket을 항목별로
비교합니다. Ref harness는 fixture마다 새 PHP process를 띄우지 않고 `--jsonl`
batch를 한 process에서 처리합니다. `ITEM_PARITY_FILTER`는 특정 항목 진단에만
사용하며 전체 감사의 fixture 수나 assertion을 줄이는 최적화로 사용하지 않습니다.
