Skip to content

Commit 24c8c4c

Browse files
authored
Merge pull request #487 from LO-CO-CO/test/486
[TEST] #487 캠페인, 크리에이터 도메인 관련 테스트 코드 작성
2 parents 7eaa309 + 55dc9f5 commit 24c8c4c

7 files changed

Lines changed: 705 additions & 0 deletions

File tree

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package com.lokoko.domain.campaign.domain.entity;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatCode;
5+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
6+
7+
import com.lokoko.domain.campaign.domain.entity.enums.CampaignLanguage;
8+
import com.lokoko.domain.campaign.domain.entity.enums.CampaignProductType;
9+
import com.lokoko.domain.campaign.domain.entity.enums.CampaignStatus;
10+
import com.lokoko.domain.campaign.domain.entity.enums.CampaignType;
11+
import com.lokoko.domain.campaign.exception.AdminCampaignNotModifiableException;
12+
import com.lokoko.domain.campaign.exception.CampaignCapacityExceedException;
13+
import com.lokoko.domain.campaign.exception.CampaignExpiredException;
14+
import com.lokoko.domain.campaign.exception.DraftNotFilledException;
15+
import com.lokoko.domain.campaign.exception.CampaignNotEditableException;
16+
import com.lokoko.domain.creatorCampaign.exception.CampaignNotRecruitingException;
17+
import com.lokoko.domain.creatorCampaign.exception.CampaignNotStartedException;
18+
import com.lokoko.domain.creatorCampaign.exception.CampaignRecruitmentFullException;
19+
import com.lokoko.domain.media.socialclip.domain.entity.enums.ContentType;
20+
import com.lokoko.domain.user.exception.CampaignApprovalNotAllowedException;
21+
import java.time.Instant;
22+
import java.util.List;
23+
import org.junit.jupiter.api.DisplayName;
24+
import org.junit.jupiter.api.Test;
25+
26+
@DisplayName("캠페인 도메인 검증")
27+
class CampaignTest {
28+
29+
@Test
30+
@DisplayName("isDraft() : 발행 필수값이 모두 있으면 brand 연관관계가 없어도 draft가 아니다")
31+
void isDraft_ignoresMissingBrandRelation() {
32+
Campaign campaign = completeCampaignBuilder()
33+
.brand(null)
34+
.brandName(null)
35+
.build();
36+
37+
assertThat(campaign.isDraft()).isFalse();
38+
}
39+
40+
@Test
41+
@DisplayName("validatePublishable() : 필수 필드가 비어 있으면 예외를 던진다")
42+
void validatePublishable_throwsWhenRequiredFieldMissing() {
43+
Campaign campaign = completeCampaignBuilder()
44+
.firstContentPlatform(null)
45+
.build();
46+
47+
assertThatThrownBy(campaign::validatePublishable)
48+
.isInstanceOf(DraftNotFilledException.class);
49+
}
50+
51+
@Test
52+
@DisplayName("validatePublishableForAdmin() : brandName 이 있으면 brand 연관관계 없이도 통과한다")
53+
void validatePublishableForAdmin_allowsBrandNameWithoutBrandRelation() {
54+
Campaign campaign = completeCampaignBuilder()
55+
.brand(null)
56+
.brandName("Lokoko")
57+
.build();
58+
59+
assertThatCode(campaign::validatePublishableForAdmin)
60+
.doesNotThrowAnyException();
61+
}
62+
63+
@Test
64+
@DisplayName("validatePublishableForAdmin() : 어드민 캠페인은 brandName 이 필수다")
65+
void validatePublishableForAdmin_requiresBrandName() {
66+
Campaign campaign = completeCampaignBuilder()
67+
.brand(null)
68+
.brandName(" ")
69+
.build();
70+
71+
assertThatThrownBy(campaign::validatePublishableForAdmin)
72+
.isInstanceOf(DraftNotFilledException.class);
73+
}
74+
75+
@Test
76+
@DisplayName("publish() : 캠페인을 발행 상태와 승인 대기 상태로 변경한다")
77+
void publish_marksCampaignAsWaitingApproval() {
78+
Campaign campaign = completeCampaignBuilder()
79+
.campaignStatus(CampaignStatus.DRAFT)
80+
.build();
81+
82+
campaign.publish();
83+
84+
assertThat(campaign.isPublished()).isTrue();
85+
assertThat(campaign.getCampaignStatus()).isEqualTo(CampaignStatus.WAITING_APPROVAL);
86+
assertThat(campaign.getPublishedAt()).isNotNull();
87+
}
88+
89+
@Test
90+
@DisplayName("validateParticipatableAt() : 신청 시작 전이면 예외를 던진다")
91+
void validateParticipatableAt_rejectsBeforeStart() {
92+
Instant now = Instant.parse("2026-03-11T00:00:00Z");
93+
Campaign campaign = completeCampaignBuilder()
94+
.campaignStatus(CampaignStatus.RECRUITING)
95+
.applyStartDate(now.plusSeconds(60))
96+
.build();
97+
98+
assertThatThrownBy(() -> campaign.validateParticipatableAt(now))
99+
.isInstanceOf(CampaignNotStartedException.class);
100+
}
101+
102+
@Test
103+
@DisplayName("validateParticipatableAt() : 신청 마감 후면 예외를 던진다")
104+
void validateParticipatableAt_rejectsAfterDeadline() {
105+
Instant now = Instant.parse("2026-03-11T00:00:00Z");
106+
Campaign campaign = completeCampaignBuilder()
107+
.campaignStatus(CampaignStatus.RECRUITING)
108+
.applyStartDate(now.minusSeconds(60))
109+
.applyDeadline(now.minusSeconds(1))
110+
.build();
111+
112+
assertThatThrownBy(() -> campaign.validateParticipatableAt(now))
113+
.isInstanceOf(CampaignExpiredException.class);
114+
}
115+
116+
@Test
117+
@DisplayName("validateParticipatableAt() : 모집 중 상태가 아니면 예외를 던진다")
118+
void validateParticipatableAt_requiresRecruitingStatus() {
119+
Instant now = Instant.parse("2026-03-11T00:00:00Z");
120+
Campaign campaign = completeCampaignBuilder()
121+
.applyStartDate(now.minusSeconds(60))
122+
.applyDeadline(now.plusSeconds(3600))
123+
.campaignStatus(CampaignStatus.OPEN_RESERVED)
124+
.build();
125+
126+
assertThatThrownBy(() -> campaign.validateParticipatableAt(now))
127+
.isInstanceOf(CampaignNotRecruitingException.class);
128+
}
129+
130+
@Test
131+
@DisplayName("validateParticipatableAt() : 모집 인원이 가득 찼으면 예외를 던진다")
132+
void validateParticipatableAt_rejectsFullRecruitment() {
133+
Instant now = Instant.parse("2026-03-11T00:00:00Z");
134+
Campaign campaign = completeCampaignBuilder()
135+
.applyStartDate(now.minusSeconds(60))
136+
.applyDeadline(now.plusSeconds(3600))
137+
.campaignStatus(CampaignStatus.RECRUITING)
138+
.approvedNumber(5)
139+
.recruitmentNumber(5)
140+
.build();
141+
142+
assertThatThrownBy(() -> campaign.validateParticipatableAt(now))
143+
.isInstanceOf(CampaignRecruitmentFullException.class);
144+
}
145+
146+
@Test
147+
@DisplayName("approveByAdmin() : 시작일이 미래면 OPEN_RESERVED 로 변경한다")
148+
void approveByAdmin_setsOpenReservedForFutureCampaign() {
149+
Instant now = Instant.parse("2026-03-11T00:00:00Z");
150+
Campaign campaign = completeCampaignBuilder()
151+
.campaignStatus(CampaignStatus.WAITING_APPROVAL)
152+
.applyStartDate(now.plusSeconds(60))
153+
.build();
154+
155+
campaign.approveByAdmin(now);
156+
157+
assertThat(campaign.getCampaignStatus()).isEqualTo(CampaignStatus.OPEN_RESERVED);
158+
}
159+
160+
@Test
161+
@DisplayName("approveByAdmin() : 이미 시작된 캠페인은 RECRUITING 으로 변경한다")
162+
void approveByAdmin_setsRecruitingForStartedCampaign() {
163+
Instant now = Instant.parse("2026-03-11T00:00:00Z");
164+
Campaign campaign = completeCampaignBuilder()
165+
.campaignStatus(CampaignStatus.WAITING_APPROVAL)
166+
.applyStartDate(now.minusSeconds(60))
167+
.build();
168+
169+
campaign.approveByAdmin(now);
170+
171+
assertThat(campaign.getCampaignStatus()).isEqualTo(CampaignStatus.RECRUITING);
172+
}
173+
174+
@Test
175+
@DisplayName("approveByAdmin() : WAITING_APPROVAL 이 아니면 예외를 던진다")
176+
void approveByAdmin_rejectsUnexpectedStatus() {
177+
Campaign campaign = completeCampaignBuilder()
178+
.campaignStatus(CampaignStatus.RECRUITING)
179+
.build();
180+
181+
assertThatThrownBy(() -> campaign.approveByAdmin(Instant.parse("2026-03-11T00:00:00Z")))
182+
.isInstanceOf(CampaignApprovalNotAllowedException.class);
183+
}
184+
185+
@Test
186+
@DisplayName("validateAdminModifiable() : WAITING_APPROVAL 상태에서만 수정 가능하다")
187+
void validateAdminModifiable_onlyAllowsWaitingApproval() {
188+
Campaign campaign = completeCampaignBuilder()
189+
.campaignStatus(CampaignStatus.RECRUITING)
190+
.build();
191+
192+
assertThatThrownBy(campaign::validateAdminModifiable)
193+
.isInstanceOf(AdminCampaignNotModifiableException.class);
194+
}
195+
196+
@Test
197+
@DisplayName("validateEditable() : 이미 발행된 캠페인이면 예외를 던진다")
198+
void validateEditable_rejectsPublishedCampaign() {
199+
Campaign campaign = completeCampaignBuilder()
200+
.isPublished(true)
201+
.build();
202+
203+
assertThatThrownBy(campaign::validateEditable)
204+
.isInstanceOf(CampaignNotEditableException.class);
205+
}
206+
207+
@Test
208+
@DisplayName("validateCapacityForApproval() : 승인 요청 수가 모집 인원을 넘으면 예외를 던진다")
209+
void validateCapacityForApproval_rejectsExceedingRecruitmentNumber() {
210+
Campaign campaign = completeCampaignBuilder()
211+
.approvedNumber(4)
212+
.recruitmentNumber(5)
213+
.build();
214+
215+
assertThatThrownBy(() -> campaign.validateCapacityForApproval(2))
216+
.isInstanceOf(CampaignCapacityExceedException.class);
217+
}
218+
219+
private Campaign.CampaignBuilder<?, ?> completeCampaignBuilder() {
220+
Instant now = Instant.parse("2026-03-11T00:00:00Z");
221+
222+
return Campaign.builder()
223+
.brandName("Lokoko")
224+
.title("Spring Campaign")
225+
.language(CampaignLanguage.EN)
226+
.campaignType(CampaignType.CONTENTS)
227+
.campaignStatus(CampaignStatus.DRAFT)
228+
.campaignProductType(CampaignProductType.SKINCARE)
229+
.applyStartDate(now.plusSeconds(60))
230+
.applyDeadline(now.plusSeconds(3600))
231+
.creatorAnnouncementDate(now.plusSeconds(7200))
232+
.reviewSubmissionDeadline(now.plusSeconds(10800))
233+
.recruitmentNumber(5)
234+
.participationRewards(List.of("Reward"))
235+
.deliverableRequirements(List.of("Deliverable"))
236+
.eligibilityRequirements(List.of("Eligibility"))
237+
.firstContentPlatform(ContentType.INSTA_REELS);
238+
}
239+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.lokoko.domain.campaignReview.application.service;
2+
3+
import static org.assertj.core.api.Assertions.*;
4+
import static org.mockito.BDDMockito.*;
5+
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.api.extension.ExtendWith;
9+
import org.mockito.InjectMocks;
10+
import org.mockito.Mock;
11+
import org.mockito.junit.jupiter.MockitoExtension;
12+
13+
import com.lokoko.domain.brand.api.dto.request.BrandNoteRevisionRequest;
14+
import com.lokoko.domain.brand.domain.entity.Brand;
15+
import com.lokoko.domain.campaign.domain.entity.Campaign;
16+
import com.lokoko.domain.campaign.domain.entity.enums.CampaignStatus;
17+
import com.lokoko.domain.campaign.exception.NotCampaignOwnershipException;
18+
import com.lokoko.domain.campaignReview.domain.entity.CampaignReview;
19+
import com.lokoko.domain.campaignReview.domain.entity.enums.BrandNoteStatus;
20+
import com.lokoko.domain.campaignReview.domain.entity.enums.ReviewStatus;
21+
import com.lokoko.domain.campaignReview.domain.entity.enums.RevisionAction;
22+
import com.lokoko.domain.campaignReview.exception.RevisionRequestNotAllowedException;
23+
import com.lokoko.domain.creatorCampaign.domain.entity.CreatorCampaign;
24+
import com.lokoko.domain.creatorCampaign.domain.enums.ParticipationStatus;
25+
import com.lokoko.domain.media.application.service.S3Service;
26+
27+
@ExtendWith(MockitoExtension.class)
28+
@DisplayName("캠페인 리뷰 수정 요청 검증")
29+
class CampaignReviewUpdateServiceTest {
30+
31+
@Mock
32+
private S3Service s3Service;
33+
34+
@Mock
35+
private CampaignReviewGetService campaignReviewGetService;
36+
37+
@InjectMocks
38+
private CampaignReviewUpdateService campaignReviewUpdateService;
39+
40+
@Test
41+
@DisplayName("requestReviewRevision() : 소유한 브랜드의 제출된 리뷰에 대해서만 수정 요청을 반영한다")
42+
void requestReviewRevision_submitsForSubmittedReviewOwnedByBrand() {
43+
CampaignReview review = submittedReview(7L);
44+
BrandNoteRevisionRequest request = new BrandNoteRevisionRequest("Please tighten the caption.");
45+
46+
given(campaignReviewGetService.findById(11L)).willReturn(review);
47+
48+
var response = campaignReviewUpdateService.requestReviewRevision(
49+
RevisionAction.SUBMIT, 7L, 11L, request);
50+
51+
assertThat(response.brandNote()).isEqualTo("Please tighten the caption.");
52+
assertThat(response.status()).isEqualTo(BrandNoteStatus.PUBLISHED);
53+
assertThat(response.revisionRequestedAt()).isNotNull();
54+
assertThat(review.getStatus()).isEqualTo(ReviewStatus.REVISION_REQUESTED);
55+
assertThat(review.getCreatorCampaign().getStatus()).isEqualTo(ParticipationStatus.ACTIVE);
56+
}
57+
58+
@Test
59+
@DisplayName("requestReviewRevision() : SUBMITTED 상태가 아니면 수정 요청을 거부한다")
60+
void requestReviewRevision_rejectsNonSubmittedReviews() {
61+
CampaignReview review = submittedReview(7L);
62+
review.requestSecondReview("updated caption", "https://example.com/post");
63+
64+
given(campaignReviewGetService.findById(11L)).willReturn(review);
65+
66+
assertThatThrownBy(() -> campaignReviewUpdateService.requestReviewRevision(
67+
RevisionAction.SUBMIT, 7L, 11L, new BrandNoteRevisionRequest("Please revise")))
68+
.isInstanceOf(RevisionRequestNotAllowedException.class);
69+
}
70+
71+
@Test
72+
@DisplayName("requestReviewRevision() : 소유하지 않은 브랜드면 예외를 던진다")
73+
void requestReviewRevision_rejectsOtherBrands() {
74+
CampaignReview review = submittedReview(7L);
75+
76+
given(campaignReviewGetService.findById(11L)).willReturn(review);
77+
78+
assertThatThrownBy(() -> campaignReviewUpdateService.requestReviewRevision(
79+
RevisionAction.SUBMIT, 99L, 11L, new BrandNoteRevisionRequest("Please revise")))
80+
.isInstanceOf(NotCampaignOwnershipException.class);
81+
}
82+
83+
private CampaignReview submittedReview(Long brandId) {
84+
Brand brand = Brand.builder()
85+
.id(brandId)
86+
.brandName("Lokoko")
87+
.build();
88+
89+
Campaign campaign = Campaign.builder()
90+
.brand(brand)
91+
.brandName(brand.getBrandName())
92+
.campaignStatus(CampaignStatus.IN_REVIEW)
93+
.build();
94+
95+
CreatorCampaign creatorCampaign = CreatorCampaign.builder()
96+
.campaign(campaign)
97+
.status(ParticipationStatus.COMPLETED)
98+
.build();
99+
100+
CampaignReview review = new CampaignReview();
101+
review.bindToCreatorCampaign(creatorCampaign);
102+
review.requestFirstReview("initial caption");
103+
return review;
104+
}
105+
}

0 commit comments

Comments
 (0)