Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#12] 동네 인증 도메인, 유즈케이스 구현 및 테스트 코드 작성 #15

Merged
merged 7 commits into from
Sep 21, 2024
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
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// Lombok
compileOnly 'org.projectlombok:lombok:1.18.34'
annotationProcessor 'org.projectlombok:lombok:1.18.34'

testCompileOnly 'org.projectlombok:lombok:1.18.34'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.34'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.srltas.runtogether.adapter.out;

import com.srltas.runtogether.domain.model.location.DistanceCalculator;
import com.srltas.runtogether.domain.model.location.Location;

public class HaversineDistanceCalculator implements DistanceCalculator {

private static final double EARTH_RADIUS_KM = 6371;

@Override
public double calculateDistanceBetween(Location location1, Location location2) {
double latitude = Math.toRadians(location1.getLatitude() - location2.getLatitude());
double longitude = Math.toRadians(location1.getLongitude() - location2.getLongitude());

double a = Math.sin(latitude / 2) * Math.sin(latitude / 2)
+ Math.cos(Math.toRadians(location2.getLatitude())) * Math.cos(Math.toRadians(location1.getLatitude()))
* Math.sin(longitude / 2) * Math.sin(longitude / 2);

double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

return EARTH_RADIUS_KM * c;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.srltas.runtogether.application;

import com.srltas.runtogether.domain.exception.NeighborhoodNotFoundException;
import com.srltas.runtogether.domain.exception.OutOfNeighborhoodBoundaryException;
import com.srltas.runtogether.domain.model.location.Location;
import com.srltas.runtogether.domain.model.neighborhood.Neighborhood;
import com.srltas.runtogether.domain.model.neighborhood.NeighborhoodRepository;
import com.srltas.runtogether.domain.model.user.User;
import com.srltas.runtogether.domain.model.user.UserRepository;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class NeighborhoodVerificationService {

private final NeighborhoodRepository neighborhoodRepository;
private final UserRepository userRepository;

public void verifyAndRegisterNeighborhood(User user, Location currentLocation, String neighborhoodName) {
Neighborhood neighborhood = neighborhoodRepository.findByName(neighborhoodName)
.orElseThrow(() -> new NeighborhoodNotFoundException(neighborhoodName));

if (neighborhood.isWithinBoundary(currentLocation)) {
user.addVerifiedNeighborhood(neighborhood);
userRepository.save(user);
} else {
throw new OutOfNeighborhoodBoundaryException(neighborhoodName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.srltas.runtogether.domain.exception;

public class NeighborhoodNotFoundException extends RuntimeException {

public NeighborhoodNotFoundException(String neighborhoodName) {
super(String.format("Neighborhood not found: %s", neighborhoodName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.srltas.runtogether.domain.exception;

public class OutOfNeighborhoodBoundaryException extends RuntimeException {

public OutOfNeighborhoodBoundaryException(String neighborhoodName) {
super(String.format("User is outside of the boundary of neighborhood: %s", neighborhoodName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.srltas.runtogether.domain.model.location;

public interface DistanceCalculator {
double calculateDistanceBetween(Location location1, Location location2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.srltas.runtogether.domain.model.location;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@EqualsAndHashCode
@RequiredArgsConstructor
public class Location {

private final double latitude;
private final double longitude;

public double calculateDistance(Location otherLocation, DistanceCalculator distanceCalculator) {
return distanceCalculator.calculateDistanceBetween(otherLocation, this);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.srltas.runtogether.domain.model.neighborhood;

import com.srltas.runtogether.domain.model.location.DistanceCalculator;
import com.srltas.runtogether.domain.model.location.Location;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@EqualsAndHashCode
@RequiredArgsConstructor
public class Neighborhood {
f-lab-bradley marked this conversation as resolved.
Show resolved Hide resolved

@Getter
private final String name;

@Getter
private final Location location;

private final double boundaryRadius;

private final DistanceCalculator distanceCalculator;

public boolean isWithinBoundary(Location currentLocation) {
double distance = location.calculateDistance(currentLocation, distanceCalculator);
return distance <= boundaryRadius;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.srltas.runtogether.domain.model.neighborhood;

import java.util.Optional;

public interface NeighborhoodRepository {
Optional<Neighborhood> findByName(String name);
}
20 changes: 20 additions & 0 deletions src/main/java/com/srltas/runtogether/domain/model/user/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.srltas.runtogether.domain.model.user;

import java.util.ArrayList;
import java.util.List;

import com.srltas.runtogether.domain.model.neighborhood.Neighborhood;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class User {

private final Long id;
private final String name;
private List<VerifiedNeighborhood> verifiedNeighborhoods = new ArrayList<>();

public void addVerifiedNeighborhood(Neighborhood neighborhood) {
this.verifiedNeighborhoods.add(new VerifiedNeighborhood(neighborhood));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.srltas.runtogether.domain.model.user;

public interface UserRepository {
void save(User user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.srltas.runtogether.domain.model.user;

import java.time.LocalDateTime;

import com.srltas.runtogether.domain.model.neighborhood.Neighborhood;

public class VerifiedNeighborhood {

private final Neighborhood neighborhood;
private final LocalDateTime verifiedAt;

public VerifiedNeighborhood(Neighborhood neighborhood) {
this.neighborhood = neighborhood;
this.verifiedAt = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.srltas.runtogether.adapter.out;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

import com.srltas.runtogether.domain.model.location.Location;

class HaversineDistanceCalculatorTest {

private final HaversineDistanceCalculator haversineDistanceCalculator = new HaversineDistanceCalculator();

private final Location seoul = new Location(37.566535, 126.977969);
private final Location busan = new Location(35.179554, 129.075641);

@Test
public void testCalculateDistanceBetween_TwoLocations() {
double seoulBusanDistance = 325.0;

double distance = haversineDistanceCalculator.calculateDistanceBetween(seoul, busan);

assertEquals(seoulBusanDistance, distance, 1.0);
}

@Test
public void testCalculateDistanceBetween_SameLocation() {
double distance = haversineDistanceCalculator.calculateDistanceBetween(seoul, seoul);

assertEquals(0.0, distance);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.srltas.runtogether.application;

import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.*;

import java.util.Optional;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;

import com.srltas.runtogether.domain.exception.NeighborhoodNotFoundException;
import com.srltas.runtogether.domain.exception.OutOfNeighborhoodBoundaryException;
import com.srltas.runtogether.domain.model.location.DistanceCalculator;
import com.srltas.runtogether.domain.model.location.Location;
import com.srltas.runtogether.domain.model.neighborhood.Neighborhood;
import com.srltas.runtogether.domain.model.neighborhood.NeighborhoodRepository;
import com.srltas.runtogether.domain.model.user.User;
import com.srltas.runtogether.domain.model.user.UserRepository;

class NeighborhoodVerificationServiceTest {

@Mock
private NeighborhoodRepository neighborhoodRepository;

@Mock
private DistanceCalculator distanceCalculator;

@Mock
private UserRepository userRepository;

@InjectMocks
private NeighborhoodVerificationService neighborhoodVerificationService;

private User user = new User(1L, "testUser");
private String neighborhoodName = "Gangnam";
private Location currentLocation = new Location(37.505858, 127.058319);
private Neighborhood neighborhood;

@BeforeEach
public void setUp() {
openMocks(this);
neighborhood = new Neighborhood(neighborhoodName, new Location(37.517347, 127.047382), 7.0, distanceCalculator);
}

@Nested
class WhenNeighborhoodIsFound {

@BeforeEach
public void setUp() {
when(neighborhoodRepository.findByName(neighborhoodName)).thenReturn(Optional.of(neighborhood));
}

@Test
public void testVerifyAndRegisterNeighborhood_WithinBoundary() {
when(distanceCalculator.calculateDistanceBetween(currentLocation, neighborhood.getLocation()))
.thenReturn(5.0);

neighborhoodVerificationService.verifyAndRegisterNeighborhood(user, currentLocation, neighborhoodName);

verify(userRepository).save(user);
}

@Test
public void testVerifyAndRegisterNeighborhood_OutsideBoundary() {
when(distanceCalculator.calculateDistanceBetween(currentLocation, neighborhood.getLocation()))
.thenReturn(15.0);

OutOfNeighborhoodBoundaryException exception = assertThrows(OutOfNeighborhoodBoundaryException.class,
() -> {
neighborhoodVerificationService.verifyAndRegisterNeighborhood(user, currentLocation, neighborhoodName);
});

assertEquals(format("User is outside of the boundary of neighborhood: %s", neighborhoodName), exception.getMessage());
verify(userRepository, never()).save(user);
}
}

@Nested
class WhenNeighborhoodIsNotFound {
@Test
public void testVerifyAndRegisterNeighborhood_NeighborhoodNotFound() {
when(neighborhoodRepository.findByName("Homaesil")).thenReturn(Optional.empty());

NeighborhoodNotFoundException exception = assertThrows(NeighborhoodNotFoundException.class,
() -> {
neighborhoodVerificationService.verifyAndRegisterNeighborhood(user, currentLocation, neighborhoodName);
});

assertEquals(format("Neighborhood not found: %s", neighborhoodName), exception.getMessage());
verify(userRepository, never()).save(user);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.srltas.runtogether.domain.model.neighborhood;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.MockitoAnnotations.*;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;

import com.srltas.runtogether.domain.model.location.DistanceCalculator;
import com.srltas.runtogether.domain.model.location.Location;

class NeighborhoodTest {

@Mock
private DistanceCalculator distanceCalculator;

private Location currentLocation;
private Location neighborhoodLocation;
private Neighborhood neighborhood;

@BeforeEach
public void setUp() {
openMocks(this);

currentLocation = new Location(37.505858, 127.058319);
neighborhoodLocation = new Location(37.517347, 127.047382);
neighborhood = new Neighborhood("Gangnam", neighborhoodLocation, 7.0, distanceCalculator);
}

@Test
public void testIsWithinBoundary_WhenUserInSideBoundary() {
when(distanceCalculator.calculateDistanceBetween(currentLocation, neighborhoodLocation)).thenReturn(5.0);

boolean isInSide = neighborhood.isWithinBoundary(currentLocation);

assertTrue(isInSide);
verify(distanceCalculator).calculateDistanceBetween(currentLocation, neighborhoodLocation);
}

@Test
public void testIsWithinBoundary_WhenUserOutsideBoundary() {
when(distanceCalculator.calculateDistanceBetween(currentLocation, neighborhoodLocation)).thenReturn(10.0);

boolean isInSide = neighborhood.isWithinBoundary(currentLocation);

assertFalse(isInSide);
verify(distanceCalculator).calculateDistanceBetween(currentLocation, neighborhoodLocation);
}
}
Loading