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
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ enum class ErrorCode(
FARMING_RECORD_DETAIL_REQUIRED("FARMING_002", "error.farming_record_detail_required", 400),
FARMING_RECORD_INVALID_DETAIL("FARMING_003", "error.farming_record_invalid_detail", 400),
FARMING_RECORD_FORBIDDEN("FARMING_004", "error.farming_record_forbidden", 403),
FARMING_RECORD_CROP_NOT_IN_FARM("FARMING_006", "error.farming_record_crop_not_in_farm", 400),
WEATHER_LOCATION_REQUIRED("WEATHER_001", "error.weather_location_required", 400),
WEATHER_PROVIDER_UNAVAILABLE("WEATHER_002", "error.weather_provider_unavailable", 503),
WEATHER_DATE_IN_FUTURE("WEATHER_003", "error.weather_date_in_future", 400),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.chamchamcham.application.report.ReportScope
import com.chamchamcham.domain.crop.Crop
import com.chamchamcham.domain.crop.CropRepository
import com.chamchamcham.domain.crop.CropUsePartCategory
import com.chamchamcham.domain.crop.MemberCropRepository
import com.chamchamcham.domain.farm.Farm
import com.chamchamcham.domain.farm.FarmRepository
import com.chamchamcham.domain.farming.FarmingRecord
Expand Down Expand Up @@ -49,6 +50,7 @@ class FarmingRecordService(
private val memberRepository: MemberRepository,
private val farmRepository: FarmRepository,
private val cropRepository: CropRepository,
private val memberCropRepository: MemberCropRepository,
private val farmingRecordRepository: FarmingRecordRepository,
private val farmingRecordMediaRepository: FarmingRecordMediaRepository,
private val farmingRecordQueryRepository: FarmingRecordQueryRepository,
Expand All @@ -72,6 +74,7 @@ class FarmingRecordService(
val member = findMember(command.memberId)
val farm = findFarm(command.farmId, command.memberId)
val crop = findCrop(command.cropId)
assertCropRegisteredToFarm(command.memberId, command.farmId, command.cropId)
val media = validateMedia(command.memberId, command.mediaIds)

val record = farmingRecordRepository.save(
Expand Down Expand Up @@ -162,6 +165,7 @@ class FarmingRecordService(

val farm = findFarm(command.farmId, command.memberId)
val crop = findCrop(command.cropId)
assertCropRegisteredToFarm(command.memberId, command.farmId, command.cropId)
val existingRecordMedia = farmingRecordMediaRepository.findByRecord_Id(command.recordId)
val media = validateUpdatedMedia(record, command.memberId, command.mediaIds)

Expand Down Expand Up @@ -575,6 +579,14 @@ class FarmingRecordService(
BusinessException(ErrorCode.CROP_NOT_FOUND)
}

// 회원이 해당 농장에 등록한(MemberCrop) 작물만 기록에 쓸 수 있다.
// farm 소유는 findFarm에서, 전역 작물 존재는 findCrop에서 확인하고, 여기서 (농장,작물) 등록 조합을 확인한다.
private fun assertCropRegisteredToFarm(memberId: UUID, farmId: UUID, cropId: UUID) {
if (!memberCropRepository.existsByMemberIdAndFarmIdAndCropId(memberId, farmId, cropId)) {
throw BusinessException(ErrorCode.FARMING_RECORD_CROP_NOT_IN_FARM)
}
}

private fun findPesticide(pesticideId: UUID): Pesticide =
pesticideRepository.findById(pesticideId).orElseThrow {
BusinessException(ErrorCode.PESTICIDE_NOT_FOUND)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.chamchamcham.domain.common.BaseTimeEntity
import com.chamchamcham.domain.crop.Crop
import com.chamchamcham.domain.crop.CropRepository
import com.chamchamcham.domain.crop.CropUsePartCategory
import com.chamchamcham.domain.crop.MemberCropRepository
import com.chamchamcham.domain.farm.Farm
import com.chamchamcham.domain.farm.FarmRepository
import com.chamchamcham.domain.farming.FarmingRecord
Expand Down Expand Up @@ -57,6 +58,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.lenient
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoInteractions
Expand All @@ -82,6 +84,7 @@ class FarmingRecordServiceTest {
@Mock private lateinit var memberRepository: MemberRepository
@Mock private lateinit var farmRepository: FarmRepository
@Mock private lateinit var cropRepository: CropRepository
@Mock private lateinit var memberCropRepository: MemberCropRepository
@Mock private lateinit var farmingRecordRepository: FarmingRecordRepository
@Mock private lateinit var farmingRecordMediaRepository: FarmingRecordMediaRepository
@Mock private lateinit var farmingRecordQueryRepository: FarmingRecordQueryRepository
Expand Down Expand Up @@ -112,6 +115,7 @@ class FarmingRecordServiceTest {
memberRepository = memberRepository,
farmRepository = farmRepository,
cropRepository = cropRepository,
memberCropRepository = memberCropRepository,
farmingRecordRepository = farmingRecordRepository,
farmingRecordMediaRepository = farmingRecordMediaRepository,
farmingRecordQueryRepository = farmingRecordQueryRepository,
Expand All @@ -129,6 +133,9 @@ class FarmingRecordServiceTest {
reportProjectionService = reportProjectionService,
recordFeedbackLifecycleService = recordFeedbackLifecycleService,
)
// 기존 create/update 해피패스는 등록된 (농장,작물) 조합을 전제하므로 기본 true로 둔다.
// 미등록 거부 케이스는 별도 테스트에서 이 조합만 false로 덮어쓴다.
lenient().`when`(memberCropRepository.existsByMemberIdAndFarmIdAndCropId(memberId, farmId, cropId)).thenReturn(true)
member = Member(id = memberId, email = "$memberId@example.com", passwordHash = null)
otherMember = Member(id = otherMemberId, email = "$otherMemberId@example.com", passwordHash = null)
farm = Farm(id = farmId, owner = member, name = "약초농장", roadAddress = "서울시 강남구")
Expand Down Expand Up @@ -708,6 +715,36 @@ class FarmingRecordServiceTest {
assertEquals(ErrorCode.FARMING_RECORD_FORBIDDEN, exception.errorCode)
}

@Test
fun `create throws when crop is not registered to the farm`() {
`when`(memberRepository.findById(memberId)).thenReturn(Optional.of(member))
`when`(farmRepository.findByIdAndOwnerId(farmId, memberId)).thenReturn(farm)
`when`(cropRepository.findById(cropId)).thenReturn(Optional.of(crop))
`when`(memberCropRepository.existsByMemberIdAndFarmIdAndCropId(memberId, farmId, cropId)).thenReturn(false)

val exception = assertThrows(BusinessException::class.java) {
service.create(baseCommand(workType = WorkType.WATERING))
}

assertEquals(ErrorCode.FARMING_RECORD_CROP_NOT_IN_FARM, exception.errorCode)
verify(farmingRecordRepository, never()).save(any(FarmingRecord::class.java))
}

@Test
fun `update throws when crop is not registered to the farm`() {
`when`(farmingRecordRepository.findByIdAndIsDeletedFalse(recordId))
.thenReturn(existingRecord(workType = WorkType.PRUNING))
`when`(farmRepository.findByIdAndOwnerId(farmId, memberId)).thenReturn(farm)
`when`(cropRepository.findById(cropId)).thenReturn(Optional.of(crop))
`when`(memberCropRepository.existsByMemberIdAndFarmIdAndCropId(memberId, farmId, cropId)).thenReturn(false)

val exception = assertThrows(BusinessException::class.java) {
service.update(updateCommand(workType = WorkType.WATERING))
}

assertEquals(ErrorCode.FARMING_RECORD_CROP_NOT_IN_FARM, exception.errorCode)
}

@Test
fun `delete sets isDeleted without removing the row`() {
val record = existingRecord(workType = WorkType.PRUNING)
Expand Down
Loading