Implement household domain with outside-in TDD (15 tests)

Controller (5 tests): create household, get mine, get members,
create invite, accept invite.

Service (10 tests): household creation with planner role + seed
data (categories, tags, staple ingredients), conflict when already
in household, invite code generation with 48h expiry, accept invite
with expired/used/conflict validation.

Also includes:
- Household, HouseholdMember, HouseholdInvite JPA entities
- HouseholdInvite repository with findByInviteCode
- Ingredient, IngredientCategory, Tag entities + repositories
  (created early for seed data, will be extended in recipe domain)
- Fixed BackendApplicationTests to use AbstractIntegrationTest

Total: 38 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 21:31:00 +02:00
parent 3253dcfec2
commit 4f457303d8
22 changed files with 870 additions and 3 deletions

View File

@@ -0,0 +1,56 @@
package com.recipeapp.household;
import com.recipeapp.common.ApiResponse;
import com.recipeapp.household.dto.*;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.security.Principal;
import java.util.List;
@RestController
@RequestMapping("/v1")
public class HouseholdController {
private final HouseholdService householdService;
public HouseholdController(HouseholdService householdService) {
this.householdService = householdService;
}
@PostMapping("/households")
public ResponseEntity<ApiResponse<HouseholdResponse>> createHousehold(
Principal principal,
@Valid @RequestBody CreateHouseholdRequest request) {
HouseholdResponse response = householdService.createHousehold(principal.getName(), request);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(response));
}
@GetMapping("/households/mine")
public ResponseEntity<ApiResponse<HouseholdResponse>> getMyHousehold(Principal principal) {
HouseholdResponse response = householdService.getMyHousehold(principal.getName());
return ResponseEntity.ok(ApiResponse.success(response));
}
@GetMapping("/households/mine/members")
public ResponseEntity<List<MemberResponse>> getMembers(Principal principal) {
List<MemberResponse> members = householdService.getMembers(principal.getName());
return ResponseEntity.ok(members);
}
@PostMapping("/households/mine/invites")
public ResponseEntity<ApiResponse<InviteResponse>> createInvite(Principal principal) {
InviteResponse response = householdService.createInvite(principal.getName());
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(response));
}
@PostMapping("/invites/{code}/accept")
public ResponseEntity<ApiResponse<AcceptInviteResponse>> acceptInvite(
Principal principal,
@PathVariable String code) {
AcceptInviteResponse response = householdService.acceptInvite(principal.getName(), code);
return ResponseEntity.ok(ApiResponse.success(response));
}
}