문제와 원인
대표와 동료가 모두 접속해 준비를 마쳐도 오프라인 구성원 1명 때문에 그룹 시험을 시작할 수 없었다. 개별 ready는 오프라인일 때 false인데, 최종 allReady가 전체 구성원의 준비 상태를 검사해 접속자들의 준비 완료까지 막았다.
수정 전 코드
GroupExamReadinessRepository.fromSnapshotRows
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
const publicMembers = roster.map(m => ({
membership_id: m.public_id,
public_name: m.public_name,
is_owner: m.is_owner,
is_self: m.is_self,
membership_epoch: m.membership_epoch,
online: m.online,
presence_state: m.online ? "online" : "offline",
ready: Boolean(
m.ready && m.context_digest === contextVersion &&
m.online && m.prepared_run_id === null
),
}));
allReady: publicMembers.length > 0 && publicMembers.every(m => m.ready)
GroupExamReadinessRepository.startGuard
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
async startGuard(group:Configuration,members:MemberRow[],now:Date) {
const context=await readinessContext(group,members);const config=readinessConfigurationGuard(group,members);
return {sql:`${config.sql} AND NOT EXISTS(SELECT 1 FROM study_group_members m
WHERE m.group_id=? AND m.status='active' AND NOT EXISTS(SELECT 1 FROM study_group_readiness r
WHERE r.group_id=m.group_id AND r.user_key=m.user_key AND r.membership_epoch=m.membership_epoch AND r.ready=1 AND r.prepared_run_id IS NULL AND r.context_digest=?))
AND NOT EXISTS(SELECT 1 FROM study_group_members m WHERE m.group_id=? AND m.status='active'
AND NOT EXISTS(SELECT 1 FROM study_group_presence_sessions p WHERE p.group_id=m.group_id AND p.user_key=m.user_key AND p.visible=1 AND p.last_seen_at>=?))`,
values:[...config.values,group.id,context,group.id,new Date(now.getTime()-60_000).toISOString()]};
}
수정 후 코드
GroupExamReadinessRepository.fromSnapshotRows
onlineRoster와 onlineMembers를 분리하고, 접속자 1명 이상·대표 접속·접속자 전원 준비를 요구하도록 바꿨다. 개별 준비의 맥락 일치와 prepared_run_id 조건은 유지한다.
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
const onlineRoster = roster.filter(m => m.online);
const onlineMembers = publicMembers.filter(m => m.online);
allReady: onlineMembers.length > 0 &&
onlineMembers.some(m => m.is_owner) &&
onlineMembers.every(m => m.ready)
GroupExamReadinessRepository.startGuard
시작 직전 startGuard에는 참여자의 사용자 키·가입 세대를 고정한 목록을 전달한다. DB에서 대표 포함 여부, 접속자 수, 준비 상태, 가입 세대와 60,000ms(60초) 접속 기준을 다시 검사한다.
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
const expected = JSON.stringify(participants.map(m => ({
key: m.user_key,
epoch: m.membership_epoch,
})));
const cutoff = new Date(now.getTime() - 60_000).toISOString();
선택 중 구성원의 접속·이탈로 DB 참여 조건이 달라지면 시험 시작을 다시 시도하도록 처리한다.
검증 결과
Swagger UI에서 수정 전후 원본 fromSnapshotRows를 다시 실행했다. DB 조회 행 형태의 합성 입력 5개를 그대로 사용했다.
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
| 입력 조건 | 수정 전 allReady | 수정 후 allReady |
|---|---|---|
| 대표 준비·동료 오프라인 | false | true |
| 접속 2명 준비·1명 오프라인 | false | true |
| 접속 동료 미준비 | false | false |
| 대표 오프라인 | false | false |
| 접속자 없음 | false | false |
오프라인 구성원이 접속자 준비 완료를 막던 조건은 2/2개에서 0/2개로 줄었다. 미준비 접속자·대표 오프라인·접속자 없음의 시작 제한은 3/3개 유지됐다.