Compare commits
6 Commits
2e14ea06c9
...
activity-t
| Author | SHA1 | Date | |
|---|---|---|---|
| 684d46c5b2 | |||
| b4cd05e7b2 | |||
| 286699a1a8 | |||
| c3764176ac | |||
| 47ac6f4c74 | |||
| dc26a8c467 |
32
.clinerules/frontend-color-theme.md
Normal file
32
.clinerules/frontend-color-theme.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
## Brief overview
|
||||||
|
Project-specific frontend color theme guidelines for the vibing application. Defines a cohesive peach/coral/pink color palette with specific CSS custom properties, component styling patterns, and accessibility requirements.
|
||||||
|
|
||||||
|
## Color palette usage
|
||||||
|
- Always use the defined peach-coral color scheme: Primary Peach (#FFCDB2), Secondary Coral (#FFB4A2), Light Pink (#E5989B), Medium Mauve (#B5838D), Dark Purple (#6D6875)
|
||||||
|
- Implement CSS custom properties in src/index.css for all color definitions
|
||||||
|
- Use semantic color naming: --color-primary, --color-secondary, --color-bg-dark, --color-text-primary, etc.
|
||||||
|
- Support both dark and light modes using @media (prefers-color-scheme: light)
|
||||||
|
|
||||||
|
## Component styling patterns
|
||||||
|
- Buttons: Use gradient backgrounds, implement translateY(-2px) hover animations, apply consistent focus states with peach outline
|
||||||
|
- Cards: White backgrounds with subtle shadows, translateY(-8px) hover animations, 16px border radius, scale(1.05) image hover effects
|
||||||
|
- Links: Primary color #FFCDB2, hover colors #E5989B (dark mode) or #B5838D (light mode), no text decoration by default
|
||||||
|
- Use semantic CSS class names: .btn-primary, .btn-secondary, .btn-outline, .btn-ghost
|
||||||
|
|
||||||
|
## Interactive states and animations
|
||||||
|
- Apply subtle lift animations using transform: translateY(-2px) for hover effects
|
||||||
|
- Use rgba(255, 205, 178, 0.3) for focus rings and primary shadows
|
||||||
|
- Use rgba(229, 152, 155, 0.3) for secondary shadows
|
||||||
|
- Ensure hover states are distinct from focus states for accessibility
|
||||||
|
|
||||||
|
## File organization structure
|
||||||
|
- Place global styles and CSS custom properties in src/index.css
|
||||||
|
- Create individual .css files in src/components/ for component-specific styles
|
||||||
|
- Use src/App.css for utility classes
|
||||||
|
- Consider src/styles/ directory for additional theme files when needed
|
||||||
|
|
||||||
|
## Accessibility requirements
|
||||||
|
- Maintain minimum contrast ratios: 4.5:1 for normal text, 3:1 for large text
|
||||||
|
- Use --color-focus for focus indicators
|
||||||
|
- Test with color blindness simulators
|
||||||
|
- Ensure all interactive elements have proper focus states
|
||||||
38
.clinerules/git-workflow.md
Normal file
38
.clinerules/git-workflow.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
## Brief overview
|
||||||
|
Project-specific git workflow guidelines emphasizing feature branch development, conventional commit messages, and proper branch management practices. Never work directly on main/master branches.
|
||||||
|
|
||||||
|
## Branch management strategy
|
||||||
|
- Always create feature branches for all development work
|
||||||
|
- Use kebab-case naming: feature/user-authentication, bugfix/login-validation, hotfix/security-patch, refactor/api-endpoints
|
||||||
|
- Keep feature branches short-lived (1-3 days ideally)
|
||||||
|
- One feature per branch, don't mix different types of changes
|
||||||
|
- Delete branches after successful merge
|
||||||
|
|
||||||
|
## Commit message conventions
|
||||||
|
- Use conventional commit format: type(scope): description
|
||||||
|
- Use present tense ("add feature" not "added feature")
|
||||||
|
- Be specific and descriptive in commit messages
|
||||||
|
- Reference issue numbers when applicable: feat(auth): add OAuth login #123
|
||||||
|
- Make frequent, small commits with clear purposes
|
||||||
|
|
||||||
|
## Development workflow steps
|
||||||
|
- Always start by ensuring main branch is up to date: git checkout main && git pull origin main
|
||||||
|
- Create new feature branch: git checkout -b feature/your-feature-name
|
||||||
|
- Before pushing, rebase with main: git fetch origin && git rebase origin/main
|
||||||
|
- Push feature branch: git push origin feature/your-feature-name
|
||||||
|
- Create Pull Request from feature branch to main
|
||||||
|
- Only merge after code review approval
|
||||||
|
|
||||||
|
## Code review requirements
|
||||||
|
- Create PR for all changes, no direct commits to main
|
||||||
|
- Request reviews from team members
|
||||||
|
- Address all feedback before merging
|
||||||
|
- Ensure all tests pass before creating PR
|
||||||
|
- Self-review changes before requesting reviews
|
||||||
|
- Update documentation when needed
|
||||||
|
|
||||||
|
## Emergency procedures
|
||||||
|
- For critical issues, create hotfix branches from main
|
||||||
|
- Make minimal necessary changes in hotfixes
|
||||||
|
- Test thoroughly before creating emergency PR
|
||||||
|
- Follow same review process even for hotfixes
|
||||||
@@ -41,4 +41,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: backend-test-results
|
name: backend-test-results
|
||||||
path: backend/target/surefire-reports/
|
path: backend/target/surefire-reports/
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -5,10 +5,28 @@
|
|||||||
```mermaid
|
```mermaid
|
||||||
classDiagram
|
classDiagram
|
||||||
class Activity {
|
class Activity {
|
||||||
|
+Long id
|
||||||
+String name
|
+String name
|
||||||
+Location location
|
+Location location
|
||||||
+Int priceRange
|
+Integer priceRange
|
||||||
+List~String~ tags
|
+List~String~ tags
|
||||||
|
+String description
|
||||||
|
+LocalDateTime createdAt
|
||||||
|
+LocalDateTime updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Location {
|
||||||
|
+Long id
|
||||||
|
+String name
|
||||||
|
+String address
|
||||||
|
+String city
|
||||||
|
+String country
|
||||||
|
+String postalCode
|
||||||
|
+Double latitude
|
||||||
|
+Double longitude
|
||||||
|
+LocalDateTime createdAt
|
||||||
|
+LocalDateTime updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
Activity <-- Location : belongs to
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,26 @@
|
|||||||
<artifactId>spring-security-test</artifactId>
|
<artifactId>spring-security-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.datafaker</groupId>
|
||||||
|
<artifactId>datafaker</artifactId>
|
||||||
|
<version>2.4.4</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hamcrest</groupId>
|
||||||
|
<artifactId>hamcrest-core</artifactId>
|
||||||
|
<version>3.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- JSON Processing -->
|
<!-- JSON Processing -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -132,4 +152,4 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import jakarta.persistence.*;
|
|||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.With;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ import java.time.LocalDateTime;
|
|||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@With
|
||||||
public class Location {
|
public class Location {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
CREATE SCHEMA IF NOT EXISTS vibing;
|
CREATE SCHEMA IF NOT EXISTS vibing;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS vibing.locations (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
address TEXT,
|
||||||
|
city TEXT,
|
||||||
|
country TEXT,
|
||||||
|
postal_code TEXT,
|
||||||
|
latitude DOUBLE PRECISION,
|
||||||
|
longitude DOUBLE PRECISION,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS vibing.activities (
|
CREATE TABLE IF NOT EXISTS vibing.activities (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
@@ -20,17 +33,3 @@ CREATE TABLE IF NOT EXISTS vibing.activity_tags (
|
|||||||
FOREIGN KEY (activity_id) REFERENCES vibing.activities(id) ON DELETE CASCADE,
|
FOREIGN KEY (activity_id) REFERENCES vibing.activities(id) ON DELETE CASCADE,
|
||||||
PRIMARY KEY (activity_id, tag)
|
PRIMARY KEY (activity_id, tag)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS vibing.locations (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
address TEXT,
|
|
||||||
city TEXT,
|
|
||||||
country TEXT,
|
|
||||||
postal_code TEXT,
|
|
||||||
latitude DOUBLE PRECISION,
|
|
||||||
longitude DOUBLE PRECISION,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|||||||
@@ -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<Activity> 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<Activity> beachActivities = activityRepository.findByNameContainingIgnoreCase("beach");
|
||||||
|
List<Activity> 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<Activity> cheapActivities = activityRepository.findByPriceRange(1);
|
||||||
|
List<Activity> 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<Activity> activitiesAtTestLocation = activityRepository.findByLocationId(testLocation.getId());
|
||||||
|
List<Activity> 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<Activity> outdoorActivities = activityRepository.findByTagsContaining("outdoor");
|
||||||
|
List<Activity> 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<Activity> budgetActivities = activityRepository.findByPriceRangeLessThanEqual(2);
|
||||||
|
List<Activity> 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<Activity> 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<Activity> deletedActivity = activityRepository.findById(activityId);
|
||||||
|
assertThat(deletedActivity).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
46
backend/src/test/java/com/vibing/backend/ActivityTestIt.java
Normal file
46
backend/src/test/java/com/vibing/backend/ActivityTestIt.java
Normal file
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,15 +2,17 @@ package com.vibing.backend;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic test class for the Vibing Backend application.
|
* Basic test class for the Vibing Backend application.
|
||||||
*/
|
*/
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
|
@ActiveProfiles("test")
|
||||||
class VibingBackendApplicationTests {
|
class VibingBackendApplicationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void contextLoads() {
|
void contextLoads() {
|
||||||
// This test verifies that the Spring application context loads successfully
|
// This test verifies that the Spring application context loads successfully
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:postgresql://localhost:5432/your_test_database
|
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL;INIT=CREATE SCHEMA IF NOT EXISTS vibing
|
||||||
username: test_user
|
username: sa
|
||||||
password: test_password
|
password:
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
|
||||||
|
h2:
|
||||||
|
console:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: none # Let Flyway handle schema creation
|
||||||
|
show-sql: true
|
||||||
|
database-platform: org.hibernate.dialect.H2Dialect
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
default_schema: vibing
|
||||||
|
|
||||||
flyway:
|
flyway:
|
||||||
enabled: true
|
enabled: true
|
||||||
clean-disabled: false # Allow clean in tests
|
clean-disabled: false # Allow clean in tests
|
||||||
locations: classpath:db/migration
|
locations: classpath:db/migration
|
||||||
|
default-schema: vibing
|
||||||
|
|||||||
Reference in New Issue
Block a user