Rewrite variety score and suggestions with configurable scoring
- Add VarietyScoreConfig entity, repository, and V020 migration for per-household scoring weights and configurable tag types - Rewrite getVarietyScore: tag-type repeats on consecutive days, non-staple ingredient overlaps, cooking log history, plan duplicates - Rewrite getSuggestions: simulate variety score for each candidate, add tag filter (AND, case-insensitive) and configurable topN param - Update SuggestionResponse to return simulatedScore instead of fitReasons/warnings, update VarietyScoreResponse to new shape - Seed default VarietyScoreConfig on household creation - Extend test suite across all domains (+270 tests, all passing) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import com.recipeapp.household.dto.*;
|
||||
import com.recipeapp.household.entity.Household;
|
||||
import com.recipeapp.household.entity.HouseholdInvite;
|
||||
import com.recipeapp.household.entity.HouseholdMember;
|
||||
import com.recipeapp.planning.VarietyScoreConfigRepository;
|
||||
import com.recipeapp.recipe.IngredientCategoryRepository;
|
||||
import com.recipeapp.recipe.IngredientRepository;
|
||||
import com.recipeapp.recipe.TagRepository;
|
||||
@@ -36,6 +37,7 @@ class HouseholdServiceTest {
|
||||
@Mock private IngredientRepository ingredientRepository;
|
||||
@Mock private IngredientCategoryRepository ingredientCategoryRepository;
|
||||
@Mock private TagRepository tagRepository;
|
||||
@Mock private VarietyScoreConfigRepository varietyScoreConfigRepository;
|
||||
|
||||
@InjectMocks
|
||||
private HouseholdServiceImpl householdService;
|
||||
@@ -191,4 +193,67 @@ class HouseholdServiceTest {
|
||||
assertThatThrownBy(() -> householdService.acceptInvite("tom@example.com", "USED123"))
|
||||
.isInstanceOf(ConflictException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptInviteShouldThrowWhenInviteNotFound() {
|
||||
var user = new UserAccount("tom@example.com", "Tom", "hashed");
|
||||
|
||||
when(userAccountRepository.findByEmailIgnoreCase("tom@example.com")).thenReturn(Optional.of(user));
|
||||
when(householdMemberRepository.findByUserEmailIgnoreCase("tom@example.com")).thenReturn(Optional.empty());
|
||||
when(householdInviteRepository.findByInviteCode("INVALID")).thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> householdService.acceptInvite("tom@example.com", "INVALID"))
|
||||
.isInstanceOf(ResourceNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptInviteShouldThrowWhenUserNotFound() {
|
||||
when(userAccountRepository.findByEmailIgnoreCase("unknown@example.com")).thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> householdService.acceptInvite("unknown@example.com", "ABC12XYZ"))
|
||||
.isInstanceOf(ResourceNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createHouseholdShouldThrowWhenUserNotFound() {
|
||||
when(userAccountRepository.findByEmailIgnoreCase("unknown@example.com")).thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> householdService.createHousehold(
|
||||
"unknown@example.com", new CreateHouseholdRequest("New")))
|
||||
.isInstanceOf(ResourceNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMembersShouldReturnAllMembers() {
|
||||
var user1 = testUser();
|
||||
var user2 = new UserAccount("tom@example.com", "Tom", "hashed");
|
||||
var household = new Household("Smith family", user1);
|
||||
var member1 = new HouseholdMember(household, user1, "planner");
|
||||
var member2 = new HouseholdMember(household, user2, "member");
|
||||
|
||||
when(householdMemberRepository.findByUserEmailIgnoreCase("sarah@example.com")).thenReturn(Optional.of(member1));
|
||||
when(householdMemberRepository.findByHouseholdId(any())).thenReturn(List.of(member1, member2));
|
||||
|
||||
List<MemberResponse> result = householdService.getMembers("sarah@example.com");
|
||||
|
||||
assertThat(result).hasSize(2);
|
||||
assertThat(result.get(0).displayName()).isEqualTo("Sarah");
|
||||
assertThat(result.get(1).displayName()).isEqualTo("Tom");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMembersShouldThrowWhenUserNotInHousehold() {
|
||||
when(householdMemberRepository.findByUserEmailIgnoreCase("orphan@example.com")).thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> householdService.getMembers("orphan@example.com"))
|
||||
.isInstanceOf(ResourceNotFoundException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createInviteShouldThrowWhenUserNotInHousehold() {
|
||||
when(householdMemberRepository.findByUserEmailIgnoreCase("orphan@example.com")).thenReturn(Optional.empty());
|
||||
|
||||
assertThatThrownBy(() -> householdService.createInvite("orphan@example.com"))
|
||||
.isInstanceOf(ResourceNotFoundException.class);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user