diff --git a/timeless-api/src/main/java/dev/matheuscruz/presentation/SignUpResource.java b/timeless-api/src/main/java/dev/matheuscruz/presentation/SignUpResource.java index 224eec6..202617f 100644 --- a/timeless-api/src/main/java/dev/matheuscruz/presentation/SignUpResource.java +++ b/timeless-api/src/main/java/dev/matheuscruz/presentation/SignUpResource.java @@ -7,7 +7,6 @@ import dev.matheuscruz.presentation.data.ProblemResponse; import io.quarkus.logging.Log; import io.quarkus.narayana.jta.QuarkusTransaction; -import jakarta.annotation.security.PermitAll; import jakarta.validation.Valid; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; @@ -17,7 +16,6 @@ import jakarta.ws.rs.core.Response; @Path("/api/sign-up") -@PermitAll public class SignUpResource { UserRepository userRepository; @@ -49,7 +47,7 @@ public Response signUp(@Valid SignUpRequest req) { .build(); } - public record SignUpRequest(@Email String email, @NotBlank @Size(min = 8, max = 32) String password, + public record SignUpRequest(@NotBlank @Email String email, @NotBlank @Size(min = 8, max = 32) String password, @NotBlank String firstName, @NotBlank String lastName, @NotBlank String phoneNumber) { } diff --git a/timeless-api/src/main/resources/application.properties b/timeless-api/src/main/resources/application.properties index f7cc93b..b4b2f97 100644 --- a/timeless-api/src/main/resources/application.properties +++ b/timeless-api/src/main/resources/application.properties @@ -33,4 +33,20 @@ quarkus.quinoa.build-dir=dist/timeless/browser # jwt mp.jwt.verify.publickey=${JWT_PUBLIC_KEY} mp.jwt.verify.issuer=https://timelessapp.platformoon.com/issuer -smallrye.jwt.sign.key=${JWT_PRIVATE_KEY} \ No newline at end of file +smallrye.jwt.sign.key=${JWT_PRIVATE_KEY} + +# Test Profile +%test.security.sensible.secret=YS0xNi1ieXRlLXNlY3JldA== +%test.whatsapp.incoming-message.queue-url=test-incoming-queue +%test.whatsapp.recognized-message.queue-url=test-processed-queue +%test.mp.jwt.verify.publickey=test-public-key + +%test.quarkus.sqs.aws.credentials.static-provider.access-key-id=test +%test.quarkus.sqs.aws.credentials.static-provider.secret-access-key=test +%test.quarkus.sqs.aws.credentials.type=STATIC +%test.quarkus.sqs.aws.region=us-east-1 +%test.quarkus.sqs.endpoint-override=http://127.0.0.1:4566 + +%quarkus.test.arg-line=--add-opens java.base/java.lang=ALL-UNNAMED + +%test.quarkus.scheduler.enabled=false diff --git a/timeless-api/src/test/java/dev/matheuscruz/presentation/SignUpResourceTest.java b/timeless-api/src/test/java/dev/matheuscruz/presentation/SignUpResourceTest.java new file mode 100644 index 0000000..f3fd450 --- /dev/null +++ b/timeless-api/src/test/java/dev/matheuscruz/presentation/SignUpResourceTest.java @@ -0,0 +1,184 @@ +package dev.matheuscruz.presentation; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +import dev.matheuscruz.domain.User; +import dev.matheuscruz.domain.UserRepository; +import dev.matheuscruz.presentation.data.ProblemResponse; +import io.quarkus.narayana.jta.QuarkusTransaction; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@QuarkusTest +public class SignUpResourceTest { + + @Inject + UserRepository userRepository; + + @BeforeEach + @Transactional + void setUp() { + userRepository.deleteAll(); + } + + @Test + @DisplayName("should return Created when valid SignUpRequest is provided") + void should_returnCreated_when_validSignUpRequestIsProvided() { + var request = new SignUpResource.SignUpRequest("test@email.com", "password-1234", "John", "Doe", "+1234567890"); + + var response = given().contentType("application/json").body(request).when().post("/api/sign-up").then().log() + .ifValidationFails().statusCode(201).extract().as(SignUpResource.SignUpResponse.class); + + assertThat(response.id()).isNotNull().isNotBlank(); + assertThat(response.email()).isEqualTo(request.email()); + } + + @Test + @DisplayName("should return Conflict when email already exists") + void should_returnConflict_when_emailAlreadyExists() { + var existingEmail = "existing.user@email.com"; + var existingUser = User.create(existingEmail, "some-hashed-password", "Existing", "User", "+0987654321"); + + QuarkusTransaction.requiringNew().run(() -> { + userRepository.persist(existingUser); + }); + + var request = new SignUpResource.SignUpRequest(existingEmail, "a-different-password", "New", "Person", + "+0987654321"); + + var response = given().contentType("application/json").body(request).when().post("/api/sign-up").then().log() + .ifValidationFails().statusCode(409).extract().as(ProblemResponse.class); + + assertThat(response.message()).isEqualTo("Este nome de usuário já foi usado. Tente outro."); + assertThat(userRepository.count()).isEqualTo(1); + assertThat(userRepository.existsByEmail(existingEmail)).isTrue(); + } + + @Test + @DisplayName("should return Bad Request when email is not valid") + void should_returnBadRequest_when_emailIsNotValid() { + var request = new SignUpResource.SignUpRequest("invalid-email.com", "password-1234", "John", "Doe", + "+1234567890"); + + var validationProblemResponse = given().contentType("application/json").body(request).when() + .post("/api/sign-up").then().log().ifValidationFails().statusCode(400).extract() + .as(ValidationProblemResponse.class); + + assertThat(validationProblemResponse.title()).isEqualTo("Constraint Violation"); + assertThat(validationProblemResponse.violations()).hasSize(1).extracting(Violation::field, Violation::message) + .containsExactly(tuple("signUp.req.email", "must be a well-formed email address")); + } + + @Test + @DisplayName("should return Bad Request when password is too short") + void should_returnBadRequest_when_passwordIsTooShort() { + var request = new SignUpResource.SignUpRequest("test@email.com", "1234", "John", "Doe", "+1234567890"); + + var validationProblemResponse = given().contentType("application/json").body(request).when() + .post("/api/sign-up").then().log().ifValidationFails().statusCode(400).extract() + .as(ValidationProblemResponse.class); + + assertThat(validationProblemResponse.title()).isEqualTo("Constraint Violation"); + assertThat(validationProblemResponse.violations()).hasSize(1).extracting(Violation::field, Violation::message) + .containsExactly(tuple("signUp.req.password", "size must be between 8 and 32")); + } + + @Test + @DisplayName("should return Bad Request when password is too long") + void should_returnBadRequest_when_passwordIsTooLong() { + var request = new SignUpResource.SignUpRequest("test@email.com", "12345678910111213141516171819202122", "John", + "Doe", "+1234567890"); + + var validationProblemResponse = given().contentType("application/json").body(request).when() + .post("/api/sign-up").then().log().ifValidationFails().statusCode(400).extract() + .as(ValidationProblemResponse.class); + + assertThat(validationProblemResponse.title()).isEqualTo("Constraint Violation"); + assertThat(validationProblemResponse.violations()).hasSize(1).extracting(Violation::field, Violation::message) + .containsExactly(tuple("signUp.req.password", "size must be between 8 and 32")); + } + + @Test + @DisplayName("should return Bad Request when password is blank") + void should_returnBadRequest_when_passwordIsBlank() { + var request = new SignUpResource.SignUpRequest("test@email.com", "", "John", "Doe", "+1234567890"); + + var validationProblemResponse = given().contentType("application/json").body(request).when() + .post("/api/sign-up").then().log().ifValidationFails().statusCode(400).extract() + .as(ValidationProblemResponse.class); + + assertThat(validationProblemResponse.title()).isEqualTo("Constraint Violation"); + assertThat(validationProblemResponse.violations()).hasSize(2).extracting(Violation::field, Violation::message) + .containsExactlyInAnyOrder(tuple("signUp.req.password", "size must be between 8 and 32"), + tuple("signUp.req.password", "must not be blank") + + ); + } + + @Test + @DisplayName("should return Bad Request when first name is blank") + void should_returnBadRequest_when_firstNameIsBlank() { + var request = new SignUpResource.SignUpRequest("test@email.com", "password-1234", "", "Doe", "+1234567890"); + + var validationProblemResponse = given().contentType("application/json").body(request).when() + .post("/api/sign-up").then().log().ifValidationFails().statusCode(400).extract() + .as(ValidationProblemResponse.class); + + assertThat(validationProblemResponse.title()).isEqualTo("Constraint Violation"); + assertThat(validationProblemResponse.violations()).hasSize(1).extracting(Violation::field, Violation::message) + .containsExactly(tuple("signUp.req.firstName", "must not be blank")); + } + + @Test + @DisplayName("should return Bad Request when last name is blank") + void should_returnBadRequest_when_lastNameIsBlank() { + var request = new SignUpResource.SignUpRequest("test@email.com", "password-1234", "John", "", "+1234567890"); + + var validationProblemResponse = given().contentType("application/json").body(request).when() + .post("/api/sign-up").then().log().ifValidationFails().statusCode(400).extract() + .as(ValidationProblemResponse.class); + + assertThat(validationProblemResponse.title()).isEqualTo("Constraint Violation"); + assertThat(validationProblemResponse.violations()).hasSize(1).extracting(Violation::field, Violation::message) + .containsExactly(tuple("signUp.req.lastName", "must not be blank")); + } + + @Test + @DisplayName("should return Bad Request when e-mail is blank") + void should_returnBadRequest_when_emailIsBlank() { + var request = new SignUpResource.SignUpRequest("", "password-1234", "John", "Doe", "+1234567890"); + + var ProblemResponse = given().contentType("application/json").body(request).when().post("/api/sign-up").then() + .log().ifValidationFails().statusCode(400).extract().as(ValidationProblemResponse.class); + + assertThat(ProblemResponse.title()).isEqualTo("Constraint Violation"); + assertThat(ProblemResponse.violations()).hasSize(1).extracting(Violation::field, Violation::message) + .containsExactlyInAnyOrder(tuple("signUp.req.email", "must not be blank")); + } + + @Test + @DisplayName("should return Bad Request when phone number is blank") + void should_returnBadRequest_when_phoneNumberIsBlank() { + var request = new SignUpResource.SignUpRequest("test@email.com", "password-1234", "John", "Doe", ""); + + var validationProblemResponse = given().contentType("application/json").body(request).when() + .post("/api/sign-up").then().log().ifValidationFails().statusCode(400).extract() + .as(ValidationProblemResponse.class); + + assertThat(validationProblemResponse.title()).isEqualTo("Constraint Violation"); + assertThat(validationProblemResponse.violations()).hasSize(1).extracting(Violation::field, Violation::message) + .containsExactly(tuple("signUp.req.phoneNumber", "must not be blank")); + } + + public record Violation(String field, String message) { + } + + public record ValidationProblemResponse(String title, int status, java.util.List violations) { + } +}