Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @Gyuhyeok99 @wibaek @whqtker @lsy1307 @Hexeong @JAEHEE25 @sukangpunch
* @Gyuhyeok99 @wibaek @whqtker @lsy1307 @Hexeong @sukangpunch
59 changes: 59 additions & 0 deletions .github/workflows/full-test-repeat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Full Test Suite Repeat

on:
workflow_dispatch:
inputs:
repeat_count:
description: '전체 테스트를 반복할 횟수'
required: true
default: '10'

jobs:
repeat-test:
runs-on: ubuntu-latest
permissions:
contents: read
checks: write

steps:
- name: Checkout the code
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Make Gradle wrapper executable
run: chmod +x ./gradlew

- name: Run full test suite N times
run: |
set +e
REPEAT_COUNT="${{ github.event.inputs.repeat_count }}"
FAIL_COUNT=0
for i in $(seq 1 "$REPEAT_COUNT"); do

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject non-positive or non-integer repeat counts

When a dispatcher supplies 0, a negative value, a fractional value, or malformed text, this loop may execute fewer tests than requested—or none at all—while the preceding set +e ignores seq errors and leaves FAIL_COUNT at zero, producing a successful workflow without the intended test coverage. GNU seq --help confirms that arguments are interpreted as floating-point values and that a default positive sequence below its first value emits nothing; validate REPEAT_COUNT as a positive integer before entering the loop.

Useful? React with 👍 / 👎.

echo "=== Run $i/$REPEAT_COUNT ==="
./gradlew test --rerun-tasks
STATUS=$?
mkdir -p "build/repeat-test-results/run-$i"
cp -r build/test-results/test/. "build/repeat-test-results/run-$i/" 2>/dev/null || true
if [ $STATUS -ne 0 ]; then
FAIL_COUNT=$((FAIL_COUNT + 1))
echo "Run $i FAILED"
fi
done
echo "Total failures: $FAIL_COUNT / $REPEAT_COUNT"
if [ "$FAIL_COUNT" -ne 0 ]; then
exit 1
fi

- name: Publish Test Report
uses: mikepenz/action-junit-report@v5
if: success() || failure()
with:
report_paths: 'build/repeat-test-results/**/TEST-*.xml'
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ public ResponseEntity<UnivApplyInfoImportResponse> importUnivApplyInfos(
return ResponseEntity.ok(adminUnivApplyInfoService.importUnivApplyInfos(request));
}

@GetMapping("/{id}")
public ResponseEntity<AdminUnivApplyInfoResponse> getUnivApplyInfo(
@PathVariable long id
) {
return ResponseEntity.ok(adminUnivApplyInfoService.getUnivApplyInfo(id));
}

@PostMapping
public ResponseEntity<AdminUnivApplyInfoResponse> createUnivApplyInfo(
@Valid @RequestBody AdminUnivApplyInfoCreateRequest request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ private HostUniversity findHostUniversity(Long hostUniversityId) {
.orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND));
}

@Transactional(readOnly = true)
public AdminUnivApplyInfoResponse getUnivApplyInfo(long id) {
UnivApplyInfo univApplyInfo = univApplyInfoRepository.findById(id)
.orElseThrow(() -> new CustomException(UNIV_APPLY_INFO_NOT_FOUND));
return AdminUnivApplyInfoResponse.from(univApplyInfo);
}

@Transactional
@DefaultCacheOut(
key = {"univApplyInfoTextSearch", "university:recommend:general"},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.solidconnection.application.controller;

import com.example.solidconnection.application.dto.ApplicationPreviewResponse;
import com.example.solidconnection.application.dto.ApplicationSubmissionResponse;
import com.example.solidconnection.application.dto.ApplicationsResponse;
import com.example.solidconnection.application.dto.ApplyRequest;
Expand Down Expand Up @@ -37,6 +38,14 @@ public ResponseEntity<ApplicationSubmissionResponse> apply(
.body(applicationSubmissionResponse);
}

@GetMapping("/preview")
public ResponseEntity<ApplicationPreviewResponse> getApplicationPreview(
@AuthorizedUser long siteUserId
) {
ApplicationPreviewResponse result = applicationQueryService.getApplicantUniversityPreviews(siteUserId);
return ResponseEntity.ok(result);
}

// @RequireRoleAccess(roles = {Role.ADMIN}) // todo : 추후 어드민 페이지에서 권한 변경 기능 추가 필요
@GetMapping
public ResponseEntity<ApplicationsResponse> getApplicants(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.solidconnection.application.dto;

import java.util.List;

public record ApplicationPreviewResponse(
long totalUniversityCount,
List<ApplicationUniversityPreviewResponse> universities) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.example.solidconnection.application.dto;

import com.example.solidconnection.university.domain.UnivApplyInfo;

public record ApplicationUniversityPreviewResponse(
long id,
String koreanName,
Integer studentCapacity,
String region,
String country,
String logoImageUrl,
String backgroundImageUrl) {

public static ApplicationUniversityPreviewResponse from(UnivApplyInfo univApplyInfo) {
return new ApplicationUniversityPreviewResponse(
univApplyInfo.getId(),
univApplyInfo.getKoreanName(),
univApplyInfo.getStudentCapacity(),
univApplyInfo.getUniversity().getRegion().getKoreanName(),
univApplyInfo.getUniversity().getCountry().getKoreanName(),
univApplyInfo.getUniversity().getLogoImageUrl(),
univApplyInfo.getUniversity().getBackgroundImageUrl()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_FOUND;

import com.example.solidconnection.application.domain.Application;
import com.example.solidconnection.application.dto.ApplicationUniversityPreviewResponse;
import com.example.solidconnection.common.VerifyStatus;
import com.example.solidconnection.common.exception.CustomException;
import java.util.List;
Expand All @@ -29,6 +30,38 @@ List<Application> findAllByUnivApplyInfoIds(
@Param("status") VerifyStatus status,
@Param("termId") long termId);

@Query("""
SELECT new com.example.solidconnection.application.dto.ApplicationUniversityPreviewResponse(
uai.id,
uai.koreanName,
uai.studentCapacity,
region.koreanName,
country.koreanName,
university.logoImageUrl,
university.backgroundImageUrl
)
FROM UnivApplyInfo uai
JOIN uai.university university
JOIN university.region region
JOIN university.country country
WHERE uai.termId = :termId
AND uai.homeUniversity.id = :homeUniversityId
AND EXISTS (
SELECT application.id
FROM Application application
JOIN application.choices choice
WHERE choice.univApplyInfoId = uai.id
AND application.verifyStatus = :status
AND application.termId = :termId
AND application.isDelete = false
)
ORDER BY uai.koreanName
""")
List<ApplicationUniversityPreviewResponse> findApplicantUniversityPreviews(
@Param("status") VerifyStatus status,
@Param("termId") long termId,
@Param("homeUniversityId") long homeUniversityId);

Optional<Application> findTopBySiteUserIdAndTermIdAndIsDeleteFalseOrderByIdDesc(long siteUserId, long termId);

default Application getApplicationBySiteUserIdAndTermId(long siteUserId, long termId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED;
import static com.example.solidconnection.common.exception.ErrorCode.CURRENT_TERM_NOT_FOUND;
import static com.example.solidconnection.common.exception.ErrorCode.SCHOOL_EMAIL_NOT_VERIFIED;
import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND;

import com.example.solidconnection.application.domain.Application;
import com.example.solidconnection.application.domain.ApplicationChoice;
import com.example.solidconnection.application.dto.ApplicantsResponse;
import com.example.solidconnection.application.dto.ApplicationPreviewResponse;
import com.example.solidconnection.application.dto.ApplicationsResponse;
import com.example.solidconnection.application.repository.ApplicationRepository;
import com.example.solidconnection.common.VerifyStatus;
Expand Down Expand Up @@ -41,6 +43,27 @@ public class ApplicationQueryService {
private final TermRepository termRepository;
private final HomeUniversityRepository homeUniversityRepository;

@Transactional(readOnly = true)
public ApplicationPreviewResponse getApplicantUniversityPreviews(long siteUserId) {
SiteUser siteUser = siteUserRepository.findById(siteUserId)
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));

if (siteUser.getHomeUniversityId() == null) {
throw new CustomException(SCHOOL_EMAIL_NOT_VERIFIED);
}

Term term = termRepository.findByIsCurrentTrue()
.orElseThrow(() -> new CustomException(CURRENT_TERM_NOT_FOUND));

return new ApplicationPreviewResponse(
univApplyInfoRepository.countByTermIdAndHomeUniversityId(term.getId(), siteUser.getHomeUniversityId()),
applicationRepository.findApplicantUniversityPreviews(
VerifyStatus.APPROVED,
term.getId(),
siteUser.getHomeUniversityId())
);
}

@Transactional(readOnly = true)
public ApplicationsResponse getApplicants(long siteUserId, String regionCode, String keyword) {
SiteUser siteUser = siteUserRepository.findById(siteUserId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ default UnivApplyInfo getUnivApplyInfoById(Long id) {

boolean existsByHomeUniversityId(Long homeUniversityId);

long countByTermIdAndHomeUniversityId(long termId, long homeUniversityId);

@Query("""
SELECT uai.id
FROM UnivApplyInfo uai
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,43 @@ class 지원_정보_생성 {
}
}

@Nested
class 지원_정보_단건_조회 {

@Test
void 존재하는_id로_조회하면_상세_정보를_반환한다() {
// given
UnivApplyInfo univApplyInfo = univApplyInfoFixtureBuilder.univApplyInfo()
.termId(term.getId()).koreanName("괌대학(A형)")
.university(hostUniversity).homeUniversity(homeUniversity).create();

// when
AdminUnivApplyInfoResponse response = adminUnivApplyInfoService.getUnivApplyInfo(univApplyInfo.getId());

// then
assertAll(
() -> assertThat(response.id()).isEqualTo(univApplyInfo.getId()),
() -> assertThat(response.termId()).isEqualTo(term.getId()),
() -> assertThat(response.homeUniversityId()).isEqualTo(homeUniversity.getId()),
() -> assertThat(response.hostUniversityId()).isEqualTo(hostUniversity.getId()),
() -> assertThat(response.studentCapacity()).isEqualTo(univApplyInfo.getStudentCapacity()),
() -> assertThat(response.semesterRequirement()).isEqualTo(univApplyInfo.getSemesterRequirement()),
() -> assertThat(response.detailsForLanguage()).isEqualTo(univApplyInfo.getDetailsForLanguage()),
() -> assertThat(response.gpaRequirement()).isEqualTo(univApplyInfo.getGpaRequirement()),
() -> assertThat(response.gpaRequirementCriteria()).isEqualTo(univApplyInfo.getGpaRequirementCriteria()),
() -> assertThat(response.detailsForAccommodation()).isEqualTo(univApplyInfo.getDetailsForAccommodation())
);
}

@Test
void 존재하지_않는_id로_조회하면_예외가_발생한다() {
// when & then
assertThatCode(() -> adminUnivApplyInfoService.getUnivApplyInfo(invalidId))
.isInstanceOf(CustomException.class)
.hasMessage(ErrorCode.UNIV_APPLY_INFO_NOT_FOUND.getMessage());
}
}

@Nested
class 지원_정보_수정 {

Expand Down
Loading
Loading