diff --git a/backend/src/main/java/fr/nessar/lmnporganizer/rental/adapter/in/web/landlord/LandlordProfileApi.java b/backend/src/main/java/fr/nessar/lmnporganizer/rental/adapter/in/web/landlord/LandlordProfileApi.java index 96c9dd6..5109bcb 100644 --- a/backend/src/main/java/fr/nessar/lmnporganizer/rental/adapter/in/web/landlord/LandlordProfileApi.java +++ b/backend/src/main/java/fr/nessar/lmnporganizer/rental/adapter/in/web/landlord/LandlordProfileApi.java @@ -1,6 +1,8 @@ package fr.nessar.lmnporganizer.rental.adapter.in.web.landlord; import fr.nessar.lmnporganizer.rental.domain.landlord.Landlord; +import fr.nessar.lmnporganizer.rental.domain.shared.PhoneNumber; +import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; @@ -12,7 +14,17 @@ record Request( @NotBlank String lastName, @NotBlank @Email String email, @NotBlank String phone, - @NotBlank String address) {} + @NotBlank String address) { + @AssertTrue + boolean isPhoneValid() { + try { + new PhoneNumber(phone); + return true; + } catch (IllegalArgumentException exception) { + return false; + } + } +} record Response( String firstName, diff --git a/backend/src/test/java/fr/nessar/lmnporganizer/rental/adapter/in/web/landlord/LandlordProfileWebTest.java b/backend/src/test/java/fr/nessar/lmnporganizer/rental/adapter/in/web/landlord/LandlordProfileWebTest.java new file mode 100644 index 0000000..2207372 --- /dev/null +++ b/backend/src/test/java/fr/nessar/lmnporganizer/rental/adapter/in/web/landlord/LandlordProfileWebTest.java @@ -0,0 +1,56 @@ +package fr.nessar.lmnporganizer.rental.adapter.in.web.landlord; + +import static org.mockito.Mockito.verifyNoInteractions; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import fr.nessar.lmnporganizer.LmnpOrganizerApplication; +import fr.nessar.lmnporganizer.identity.adapter.in.security.SecurityConfiguration; +import fr.nessar.lmnporganizer.rental.adapter.in.web.common.AuthenticatedLandlord; +import fr.nessar.lmnporganizer.rental.adapter.in.web.common.RentalApiExceptionHandler; +import fr.nessar.lmnporganizer.rental.application.landlord.LandlordAccountService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(controllers = LandlordProfileController.class) +@ContextConfiguration(classes = LmnpOrganizerApplication.class) +@Import({AuthenticatedLandlord.class, RentalApiExceptionHandler.class, SecurityConfiguration.class}) +class LandlordProfileWebTest { +@Autowired MockMvc mockMvc; + +@MockitoBean LandlordAccountService accounts; + +@Test +void exposesStableValidationPathsAndCodesForProfileFields() throws Exception { + mockMvc + .perform( + put("/api/landlord-profile") + .with(oidcLogin()) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content( + """ + {"firstName":" ","lastName":" ","email":"not-an-email","phone":"1234","address":" "} + """)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value("validation_failed")) + .andExpect( + jsonPath("$.errors[?(@.field == 'firstName' && @.code == 'not_blank')]").exists()) + .andExpect(jsonPath("$.errors[?(@.field == 'lastName' && @.code == 'not_blank')]").exists()) + .andExpect(jsonPath("$.errors[?(@.field == 'email' && @.code == 'email')]").exists()) + .andExpect( + jsonPath("$.errors[?(@.field == 'phoneValid' && @.code == 'assert_true')]").exists()) + .andExpect(jsonPath("$.errors[?(@.field == 'address' && @.code == 'not_blank')]").exists()); + + verifyNoInteractions(accounts); +} +} diff --git a/frontend/src/app/components/landlord-profile-completion-dialog/landlord-profile-completion-dialog.html b/frontend/src/app/components/landlord-profile-completion-dialog/landlord-profile-completion-dialog.html index ec10649..8bf6587 100644 --- a/frontend/src/app/components/landlord-profile-completion-dialog/landlord-profile-completion-dialog.html +++ b/frontend/src/app/components/landlord-profile-completion-dialog/landlord-profile-completion-dialog.html @@ -17,6 +17,8 @@ [profile]="profile()" [saving]="saving()" [saveError]="error()" + [saveFieldErrors]="saveFieldErrors()" + (fieldChanged)="clearSaveFieldError($event)" (saveRequested)="save($event)" >