diff --git a/server/.env.example b/server/.env.example index 11f1357..ad716ef 100644 --- a/server/.env.example +++ b/server/.env.example @@ -4,8 +4,12 @@ OAUTH_GITHUB_CLIENT_SECRET= OAUTH_GOOGLE_CLIENT_ID= OAUTH_GOOGLE_CLIENT_SECRET= +JWT_SECRET_KEY= + DB_URL= DB_USERNAME= DB_PASSWORD= -JWT_SECRET_KEY= \ No newline at end of file +MAIL_HOST= +MAIL_USERNAME= +MAIL_PASSWORD= \ No newline at end of file diff --git a/server/build.gradle b/server/build.gradle index 23003fe..9ae4605 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -38,6 +38,10 @@ dependencies { runtimeOnly 'com.h2database:h2' runtimeOnly 'org.postgresql:postgresql' + // redis +// implementation 'org.springframework.boot:spring-boot-starter-data-redis' +// implementation 'io.lettuce.core:lettuce-core' + // lombok compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' diff --git a/server/src/main/java/com/fluffy/submission/application/SubmissionService.java b/server/src/main/java/com/fluffy/submission/application/SubmissionService.java index ad95691..3efa3d5 100644 --- a/server/src/main/java/com/fluffy/submission/application/SubmissionService.java +++ b/server/src/main/java/com/fluffy/submission/application/SubmissionService.java @@ -30,6 +30,8 @@ public void submit(SubmissionAppRequest request) { throw new BadRequestException("시험이 공개되지 않았습니다."); } + //TODO: 존재하지 않는 것에 대한 동시성 문제 aka 따닥 -> Redis 분산락 고려 ? + //TODO: unique 제약조건 examId, memberId ? if (submissionRepository.existsByExamIdAndMemberId(exam.getId(), member.getId())) { throw new BadRequestException("이미 제출한 시험입니다."); } diff --git a/server/src/main/java/com/fluffy/submission/domain/SubmissionRepository.java b/server/src/main/java/com/fluffy/submission/domain/SubmissionRepository.java index b305e71..57c47f8 100644 --- a/server/src/main/java/com/fluffy/submission/domain/SubmissionRepository.java +++ b/server/src/main/java/com/fluffy/submission/domain/SubmissionRepository.java @@ -1,11 +1,14 @@ package com.fluffy.submission.domain; import com.fluffy.global.exception.NotFoundException; +import java.util.List; import java.util.Optional; import org.springframework.data.repository.Repository; public interface SubmissionRepository extends Repository, SubmissionRepositoryCustom { + List findAll(); + void save(Submission submission); boolean existsByExamIdAndMemberId(Long examId, Long memberId); diff --git a/server/src/test/java/com/fluffy/integration/submission/SubmissionServiceIntegrationTest.java b/server/src/test/java/com/fluffy/integration/submission/SubmissionServiceIntegrationTest.java new file mode 100644 index 0000000..4484b4c --- /dev/null +++ b/server/src/test/java/com/fluffy/integration/submission/SubmissionServiceIntegrationTest.java @@ -0,0 +1,76 @@ +package com.fluffy.integration.submission; + +import static com.fluffy.auth.domain.OAuth2Provider.GOOGLE; +import static java.util.concurrent.Executors.newFixedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; + +import com.fluffy.auth.domain.Member; +import com.fluffy.auth.domain.MemberRepository; +import com.fluffy.exam.domain.Exam; +import com.fluffy.exam.domain.ExamRepository; +import com.fluffy.exam.domain.Question; +import com.fluffy.global.web.Accessor; +import com.fluffy.integration.AbstractIntegrationTest; +import com.fluffy.submission.application.SubmissionService; +import com.fluffy.submission.application.dto.QuestionResponseAppRequest; +import com.fluffy.submission.application.dto.SubmissionAppRequest; +import com.fluffy.submission.domain.Submission; +import com.fluffy.submission.domain.SubmissionRepository; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class SubmissionServiceIntegrationTest extends AbstractIntegrationTest { + + @Autowired + private SubmissionService submissionService; + + @Autowired + private ExamRepository examRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private SubmissionRepository submissionRepository; + + @Test + @Disabled("동시성 문제 해결 방법 미정으로 테스트 비활성화합니다.") + @DisplayName("시험을 제출할 수 있다.") + void submit() throws InterruptedException { + // given + Member member1 = memberRepository.save(new Member("ex1@gmail.com", GOOGLE, "123", "ex1", "https://ex1.com")); + Exam exam = Exam.create("시험 제목", member1.getId()); + exam.updateQuestions(List.of(Question.shortAnswer("단답형1", "답1"))); + exam.publish(null, null); + examRepository.save(exam); + + // when + ExecutorService executorService = newFixedThreadPool(2); + SubmissionAppRequest request = new SubmissionAppRequest( + exam.getId(), + List.of(new QuestionResponseAppRequest(List.of("답1"))), + new Accessor(member1.getId()) + ); + for (int i = 0; i < 2; i++) { + executorService.execute(() -> { + try { + submissionService.submit(request); + } catch (RuntimeException e) { + // ignore + } + }); + } + + executorService.shutdown(); + executorService.awaitTermination(30, TimeUnit.SECONDS); + + // then + List submissions = submissionRepository.findAll(); + assertThat(submissions).hasSize(1); + } +} diff --git a/server/src/test/resources/application-test.yml b/server/src/test/resources/application-test.yml index fcc91b6..78bac08 100644 --- a/server/src/test/resources/application-test.yml +++ b/server/src/test/resources/application-test.yml @@ -6,6 +6,20 @@ spring: hibernate: format_sql: true highlight_sql: true + mail: + host: smtp.gmail.com + port: 587 + username: username + password: password + properties: + mail: + smtp: + connectiontimeout: 5000 + timeout: 3000 + writetimeout: 5000 + auth: true + starttls: + enable: true api-host: http://localhost:8080 client-host: http://localhost:5173 @@ -27,9 +41,3 @@ auth: client-secret: client-secret-client-secret-client-secret-client-secret redirect-uri: ${api-host}/api/v1/auth/oauth2/callback/github client-uri: ${client-host} - -logging: - level: - org.springframework.orm.jpa: DEBUG - org.springframework.orm.transaction: DEBUG - org.hibernate.orm.jdbc.bind: trace