Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions apps/backend/apps/client/src/submission/submission.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1125,11 +1125,15 @@ export class SubmissionService {
where: {
id,
problemId,
contestId,
assignmentId
// contestId/assignmentId가 null인 경우 해당 필드를 필터에서 제외한다.
// null로 그대로 넘기면 Prisma가 WHERE contest_id IS NULL 조건을 생성하여
// 대회 종료 후 공개된 문제에서 과거 대회 제출 기록을 조회할 수 없게 되는 버그가 발생한다.
contestId: contestId ?? undefined,
assignmentId: assignmentId ?? undefined
Comment thread
zero1177 marked this conversation as resolved.
Outdated
},
select: {
userId: true,
contestId: true,
Comment thread
Choi-Jung-Hyeon marked this conversation as resolved.
Outdated
user: {
select: {
username: true
Expand All @@ -1153,6 +1157,34 @@ export class SubmissionService {
throw new EntityNotExistException('Submission')
}

// contestId가 요청에 포함되지 않았지만 실제 submission에는 contestId가 있는 경우
// (대회 종료 후 공개된 문제에서 과거 대회 제출 기록을 조회하는 케이스)
// 해당 submission이 속한 대회 정보를 로드하여 진행 중인 대회의 타인 제출 열람을 차단한다.
if (!contestId && submission.contestId) {
const contestRecord = await this.prisma.contestRecord.findUnique({
where: {
// eslint-disable-next-line @typescript-eslint/naming-convention
contestId_userId: {
Comment thread
Choi-Jung-Hyeon marked this conversation as resolved.
Outdated
contestId: submission.contestId,
userId
}
},
select: {
contest: {
select: {
startTime: true,
endTime: true,
isJudgeResultVisible: true
}
}
}
})
if (contestRecord) {
contest = contestRecord.contest
isJudgeResultVisible = contestRecord.contest.isJudgeResultVisible
}
}
Comment thread
Choi-Jung-Hyeon marked this conversation as resolved.
Outdated

// 본인이나 관리자가 아닐 경우
if (
submission.userId !== userId &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,87 @@ describe('SubmissionService', () => {
})
).to.be.rejectedWith(ForbiddenAccessException)
})

it('should return own past-contest submission when accessed from public problem page (contestId=null)', async () => {
// 대회 종료 후 공개된 문제에서 본인이 대회 당시 제출한 submission을 조회하는 케이스
// submission의 contestId가 CONTEST_ID이지만 요청에서 contestId=null로 호출됨
const endedContest = {
...mockContest,
startTime: new Date(Date.now() - 20000),
endTime: new Date(Date.now() - 10000), // 이미 종료된 대회
isJudgeResultVisible: true
}
const testcaseResult = submissionResults.map((result) => {
return {
...result,
cpuTime:
result.cpuTime || result.cpuTime === BigInt(0)
? result.cpuTime.toString()
: null
}
})

db.problem.findFirst.resolves(problems[0])
db.submission.findFirst.resolves({
...submissions[0],
contestId: CONTEST_ID, // 대회 당시 제출이므로 contestId가 채워져 있음
user: { username: 'username' },
submissionResult: submissionResults
})
db.contestRecord.findUnique.resolves({
contest: endedContest
})

expect(
await service.getSubmission({
id: submissions[0].id,
problemId: problems[0].id,
userId: submissions[0].userId, // 본인 조회
userRole: Role.User,
contestId: null, // 공개 문제 페이지에서 contestId 없이 호출
assignmentId: null
})
).to.deep.equal({
problemId: problems[0].id,
username: 'username',
code: submissions[0].code.map((snippet) => snippet.text).join('\n'),
language: submissions[0].language,
createTime: submissions[0].createTime,
result: submissions[0].result,
testcaseResult
})
})

it('should block viewing other users past-contest submission via public page when contest is still ongoing', async () => {
// contestId=null로 공개 엔드포인트를 호출하더라도
// submission의 실제 contestId가 진행 중인 대회이면 타인 제출 열람을 차단해야 함
const ongoingContest = {
...mockContest,
startTime: new Date(Date.now() - 10000),
endTime: new Date(Date.now() + 10000)
}

db.problem.findFirst.resolves(problems[0])
db.submission.findFirst.resolves({
...submissions[0],
contestId: CONTEST_ID, // 진행 중인 대회의 submission
userId: 2 // 타인의 submission
})
db.contestRecord.findUnique.resolves({
contest: ongoingContest
})
Comment thread
Choi-Jung-Hyeon marked this conversation as resolved.
Outdated

await expect(
service.getSubmission({
id: submissions[0].id,
problemId: problems[0].id,
userId: 1, // 다른 유저가 조회 시도
userRole: Role.User,
contestId: null, // contestId 없이 공개 엔드포인트 호출
assignmentId: null
})
).to.be.rejectedWith(ForbiddenAccessException)
})
})

describe('getContestSubmissions', () => {
Expand Down
Loading