package com.dog.dognation.scheduler; import com.dog.dognation.domain.matching.DailyMatchQuota; import com.dog.dognation.domain.matching.DailyMatchQuotaRepository; import com.dog.dognation.domain.matching.MatchQuotaService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.JdbcTemplate; import static org.assertj.core.api.Assertions.assertThat; /** * 자정 매칭 쿼터 초기화 스케줄러 검증. * 실제 스케줄러 빈 -> 서비스 -> 리포지토리(JPQL 벌크 UPDATE) 경로를 그대로 실행한다. */ @SpringBootTest class MatchQuotaSchedulerTest { @Autowired MatchQuotaScheduler scheduler; @Autowired MatchQuotaService quotaService; @Autowired DailyMatchQuotaRepository quotaRepository; @Autowired JdbcTemplate jdbc; private long freeUserId; private long adFreeUserId; private long premiumUserId; @BeforeEach void setUp() { jdbc.update("DELETE FROM daily_match_quota"); jdbc.update("DELETE FROM users"); // 잔여 0으로 소진된 상태를 만들어 초기화가 실제로 일어나는지 관찰 freeUserId = seedUser("free@dog.com", "FREE", 0); adFreeUserId = seedUser("adfree@dog.com", "AD_FREE", 0); premiumUserId = seedUser("premium@dog.com", "PREMIUM", 0); } @Test @DisplayName("자정 배치: FREE/AD_FREE 는 3으로 초기화, PREMIUM 은 손대지 않는다") void reset_resetsNonPremiumOnly() { scheduler.resetDailyMatchQuota(); assertThat(remaining(freeUserId)).isEqualTo(3); assertThat(remaining(adFreeUserId)).isEqualTo(3); assertThat(remaining(premiumUserId)).isEqualTo(0); // PREMIUM 은 배치 대상 아님(무제한) } @Test @DisplayName("초기화 대상(비 PREMIUM) 행 수를 정확히 반환한다") void reset_returnsAffectedRowCount() { int updated = quotaService.resetAll(); assertThat(updated).isEqualTo(2); // FREE + AD_FREE } @Test @DisplayName("소진 로직: 3회까지 차감되고 이후 신청은 차단된다") void consume_blocksAfterLimit() { // 쿼터 행이 3으로 초기화된 상태에서 시작 scheduler.resetDailyMatchQuota(); assertThat(quotaService.consumeOne(freeUserId)).isTrue(); // 3 -> 2 assertThat(quotaService.consumeOne(freeUserId)).isTrue(); // 2 -> 1 assertThat(quotaService.consumeOne(freeUserId)).isTrue(); // 1 -> 0 assertThat(quotaService.consumeOne(freeUserId)).isFalse(); // 0 -> 차단 assertThat(remaining(freeUserId)).isEqualTo(0); } // --- helpers --- private long seedUser(String email, String tier, int remaining) { jdbc.update(""" INSERT INTO users(email, password_hash, nickname, subscription_tier, status, created_at, updated_at) VALUES (?, 'x', ?, ?, 'ACTIVE', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) """, email, email, tier); Long id = jdbc.queryForObject("SELECT id FROM users WHERE email = ?", Long.class, email); quotaRepository.save(new DailyMatchQuota(id, remaining, java.time.LocalDate.now())); return id; } private int remaining(long userId) { Integer r = jdbc.queryForObject( "SELECT remaining_count FROM daily_match_quota WHERE user_id = ?", Integer.class, userId); return r == null ? -1 : r; } }