diff --git a/backend/pom.xml b/backend/pom.xml index 52dc476..43bb405 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -81,6 +81,20 @@ test + + net.datafaker + datafaker + 2.4.4 + test + + + + org.hamcrest + hamcrest-core + 3.0 + test + + com.fasterxml.jackson.core diff --git a/backend/src/main/java/com/vibing/backend/model/Location.java b/backend/src/main/java/com/vibing/backend/model/Location.java index c7b2b0c..046e1c8 100644 --- a/backend/src/main/java/com/vibing/backend/model/Location.java +++ b/backend/src/main/java/com/vibing/backend/model/Location.java @@ -4,6 +4,7 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.With; import lombok.AllArgsConstructor; import java.time.LocalDateTime; @@ -15,6 +16,7 @@ import java.time.LocalDateTime; @Data @NoArgsConstructor @AllArgsConstructor +@With public class Location { @Id diff --git a/backend/src/test/java/com/vibing/backend/ActivityRepositoryTest.java b/backend/src/test/java/com/vibing/backend/ActivityRepositoryTest.java new file mode 100644 index 0000000..31af90b --- /dev/null +++ b/backend/src/test/java/com/vibing/backend/ActivityRepositoryTest.java @@ -0,0 +1,277 @@ +package com.vibing.backend; + +import com.vibing.backend.model.Activity; +import com.vibing.backend.model.Location; +import com.vibing.backend.repository.ActivityRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for Activity entity using H2 database. + * Tests repository operations and database interactions. + */ +@DataJpaTest +@ActiveProfiles("test") +@TestPropertySource(locations = "classpath:application-test.yml") +public class ActivityRepositoryTest { + + @Autowired + private TestEntityManager entityManager; + + @Autowired + private ActivityRepository activityRepository; + + private Location testLocation; + private Activity testActivity; + + @BeforeEach + void setUp() { + // Create and persist test location + testLocation = new Location(); + testLocation.setName("Test Location"); + testLocation.setAddress("123 Test Street"); + testLocation.setCity("Test City"); + testLocation.setCountry("Test Country"); + testLocation.setPostalCode("12345"); + testLocation.setLatitude(40.7128); + testLocation.setLongitude(-74.0060); + testLocation = entityManager.persistAndFlush(testLocation); + + // Create test activity + testActivity = new Activity(); + testActivity.setName("Test Activity"); + testActivity.setDescription("A test activity for integration testing"); + testActivity.setLocation(testLocation); + testActivity.setPriceRange(3); + testActivity.setTags(Arrays.asList("test", "integration", "fun")); + } + + @Test + void shouldSaveAndRetrieveActivity() { + // Given - activity is created in setUp() + + // When + Activity savedActivity = activityRepository.save(testActivity); + entityManager.flush(); + entityManager.clear(); + + Optional retrievedActivity = activityRepository.findById(savedActivity.getId()); + + // Then + assertThat(retrievedActivity).isPresent(); + assertThat(retrievedActivity.get().getName()).isEqualTo("Test Activity"); + assertThat(retrievedActivity.get().getDescription()).isEqualTo("A test activity for integration testing"); + assertThat(retrievedActivity.get().getPriceRange()).isEqualTo(3); + assertThat(retrievedActivity.get().getLocation().getName()).isEqualTo("Test Location"); + assertThat(retrievedActivity.get().getTags()).containsExactlyInAnyOrder("test", "integration", "fun"); + assertThat(retrievedActivity.get().getCreatedAt()).isNotNull(); + assertThat(retrievedActivity.get().getUpdatedAt()).isNotNull(); + } + + @Test + void shouldFindActivitiesByNameContainingIgnoreCase() { + // Given + Activity activity1 = new Activity(); + activity1.setName("Beach Volleyball"); + activity1.setLocation(testLocation); + activity1.setPriceRange(2); + activity1.setTags(Arrays.asList("sport", "beach")); + + Activity activity2 = new Activity(); + activity2.setName("Mountain Hiking"); + activity2.setLocation(testLocation); + activity2.setPriceRange(1); + activity2.setTags(Arrays.asList("outdoor", "hiking")); + + activityRepository.saveAll(Arrays.asList(activity1, activity2)); + entityManager.flush(); + + // When + List beachActivities = activityRepository.findByNameContainingIgnoreCase("beach"); + List mountainActivities = activityRepository.findByNameContainingIgnoreCase("MOUNTAIN"); + + // Then + assertThat(beachActivities).hasSize(1); + assertThat(beachActivities.get(0).getName()).isEqualTo("Beach Volleyball"); + + assertThat(mountainActivities).hasSize(1); + assertThat(mountainActivities.get(0).getName()).isEqualTo("Mountain Hiking"); + } + + @Test + void shouldFindActivitiesByPriceRange() { + // Given + Activity cheapActivity = new Activity(); + cheapActivity.setName("Free Walking Tour"); + cheapActivity.setLocation(testLocation); + cheapActivity.setPriceRange(1); + cheapActivity.setTags(Arrays.asList("free", "walking")); + + Activity expensiveActivity = new Activity(); + expensiveActivity.setName("Luxury Spa Day"); + expensiveActivity.setLocation(testLocation); + expensiveActivity.setPriceRange(5); + expensiveActivity.setTags(Arrays.asList("luxury", "spa")); + + activityRepository.saveAll(Arrays.asList(cheapActivity, expensiveActivity)); + entityManager.flush(); + + // When + List cheapActivities = activityRepository.findByPriceRange(1); + List expensiveActivities = activityRepository.findByPriceRange(5); + + // Then + assertThat(cheapActivities).hasSize(1); + assertThat(cheapActivities.get(0).getName()).isEqualTo("Free Walking Tour"); + + assertThat(expensiveActivities).hasSize(1); + assertThat(expensiveActivities.get(0).getName()).isEqualTo("Luxury Spa Day"); + } + + @Test + void shouldFindActivitiesByLocationId() { + // Given + Location anotherLocation = new Location(); + anotherLocation.setName("Another Location"); + anotherLocation.setCity("Another City"); + anotherLocation = entityManager.persistAndFlush(anotherLocation); + + Activity activityAtTestLocation = new Activity(); + activityAtTestLocation.setName("Activity at Test Location"); + activityAtTestLocation.setLocation(testLocation); + activityAtTestLocation.setPriceRange(2); + + Activity activityAtAnotherLocation = new Activity(); + activityAtAnotherLocation.setName("Activity at Another Location"); + activityAtAnotherLocation.setLocation(anotherLocation); + activityAtAnotherLocation.setPriceRange(3); + + activityRepository.saveAll(Arrays.asList(activityAtTestLocation, activityAtAnotherLocation)); + entityManager.flush(); + + // When + List activitiesAtTestLocation = activityRepository.findByLocationId(testLocation.getId()); + List activitiesAtAnotherLocation = activityRepository.findByLocationId(anotherLocation.getId()); + + // Then + assertThat(activitiesAtTestLocation).hasSize(1); + assertThat(activitiesAtTestLocation.get(0).getName()).isEqualTo("Activity at Test Location"); + + assertThat(activitiesAtAnotherLocation).hasSize(1); + assertThat(activitiesAtAnotherLocation.get(0).getName()).isEqualTo("Activity at Another Location"); + } + + @Test + void shouldFindActivitiesByTagsContaining() { + // Given + Activity outdoorActivity = new Activity(); + outdoorActivity.setName("Rock Climbing"); + outdoorActivity.setLocation(testLocation); + outdoorActivity.setPriceRange(3); + outdoorActivity.setTags(Arrays.asList("outdoor", "adventure", "climbing")); + + Activity indoorActivity = new Activity(); + indoorActivity.setName("Museum Visit"); + indoorActivity.setLocation(testLocation); + indoorActivity.setPriceRange(2); + indoorActivity.setTags(Arrays.asList("indoor", "culture", "educational")); + + activityRepository.saveAll(Arrays.asList(outdoorActivity, indoorActivity)); + entityManager.flush(); + + // When + List outdoorActivities = activityRepository.findByTagsContaining("outdoor"); + List cultureActivities = activityRepository.findByTagsContaining("culture"); + + // Then + assertThat(outdoorActivities).hasSize(1); + assertThat(outdoorActivities.get(0).getName()).isEqualTo("Rock Climbing"); + + assertThat(cultureActivities).hasSize(1); + assertThat(cultureActivities.get(0).getName()).isEqualTo("Museum Visit"); + } + + @Test + void shouldFindActivitiesByPriceRangeLessThanEqual() { + // Given + Activity cheapActivity = new Activity(); + cheapActivity.setName("Budget Activity"); + cheapActivity.setLocation(testLocation); + cheapActivity.setPriceRange(1); + + Activity moderateActivity = new Activity(); + moderateActivity.setName("Moderate Activity"); + moderateActivity.setLocation(testLocation); + moderateActivity.setPriceRange(3); + + Activity expensiveActivity = new Activity(); + expensiveActivity.setName("Expensive Activity"); + expensiveActivity.setLocation(testLocation); + expensiveActivity.setPriceRange(5); + + activityRepository.saveAll(Arrays.asList(cheapActivity, moderateActivity, expensiveActivity)); + entityManager.flush(); + + // When + List budgetActivities = activityRepository.findByPriceRangeLessThanEqual(2); + List moderateBudgetActivities = activityRepository.findByPriceRangeLessThanEqual(3); + + // Then + assertThat(budgetActivities).hasSize(1); + assertThat(budgetActivities.get(0).getName()).isEqualTo("Budget Activity"); + + assertThat(moderateBudgetActivities).hasSize(2); + assertThat(moderateBudgetActivities) + .extracting(Activity::getName) + .containsExactlyInAnyOrder("Budget Activity", "Moderate Activity"); + } + + @Test + void shouldUpdateActivityTimestamps() { + // Given + Activity savedActivity = activityRepository.save(testActivity); + entityManager.flush(); + entityManager.clear(); + + // When + Optional retrievedActivity = activityRepository.findById(savedActivity.getId()); + assertThat(retrievedActivity).isPresent(); + + Activity activityToUpdate = retrievedActivity.get(); + activityToUpdate.setName("Updated Activity Name"); + Activity updatedActivity = activityRepository.save(activityToUpdate); + entityManager.flush(); + + // Then + assertThat(updatedActivity.getCreatedAt()).isNotNull(); + assertThat(updatedActivity.getUpdatedAt()).isNotNull(); + assertThat(updatedActivity.getUpdatedAt()).isAfterOrEqualTo(updatedActivity.getCreatedAt()); + } + + @Test + void shouldDeleteActivity() { + // Given + Activity savedActivity = activityRepository.save(testActivity); + entityManager.flush(); + Long activityId = savedActivity.getId(); + + // When + activityRepository.deleteById(activityId); + entityManager.flush(); + + // Then + Optional deletedActivity = activityRepository.findById(activityId); + assertThat(deletedActivity).isEmpty(); + } +} diff --git a/backend/src/test/java/com/vibing/backend/ActivityTestIt.java b/backend/src/test/java/com/vibing/backend/ActivityTestIt.java new file mode 100644 index 0000000..2ad9337 --- /dev/null +++ b/backend/src/test/java/com/vibing/backend/ActivityTestIt.java @@ -0,0 +1,46 @@ +package com.vibing.backend; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; + +import java.time.LocalDateTime; +import java.util.Random; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import com.vibing.backend.model.Activity; +import com.vibing.backend.model.Location; +import com.vibing.backend.repository.ActivityRepository; +import com.vibing.backend.service.ActivityService; + +import net.datafaker.Faker; + +@SpringBootTest +@ActiveProfiles("test") +@TestPropertySource(locations = "classpath:application-test.yml") +public class ActivityTestIt { + + private Faker faker = new Faker(new Random(123)); + + @Autowired + private ActivityService activityService; + + @Autowired + private ActivityRepository activityRepository; + + @Test + void shouldSaveActivity() { + Activity activity = new Activity(); + activity.setName(faker.location().publicSpace()); + activity.setPriceRange(faker.number().numberBetween(1, 5)); + activity.setTags(faker.lorem().words(3)); + activity.setLocation(new Location().withName(faker.location().publicSpace())); + activityRepository.save(activity); + + assertThat(activityService.findAll(), containsInAnyOrder(activity)); + } +} diff --git a/backend/src/test/resources/application-test.yml b/backend/src/test/resources/application-test.yml index b056451..324e46e 100644 --- a/backend/src/test/resources/application-test.yml +++ b/backend/src/test/resources/application-test.yml @@ -1,6 +1,6 @@ spring: datasource: - url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL + url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL;INIT=CREATE SCHEMA IF NOT EXISTS vibing username: sa password: driver-class-name: org.h2.Driver @@ -14,8 +14,12 @@ spring: ddl-auto: none # Let Flyway handle schema creation show-sql: true database-platform: org.hibernate.dialect.H2Dialect + properties: + hibernate: + default_schema: vibing flyway: enabled: true clean-disabled: false # Allow clean in tests locations: classpath:db/migration + default-schema: vibing