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