diff --git a/src/main/java/dev/cloudeko/zenei/application/web/model/response/EmailAddressResponse.java b/src/main/java/dev/cloudeko/zenei/application/web/model/response/EmailAddressResponse.java new file mode 100644 index 0000000..ecea6ce --- /dev/null +++ b/src/main/java/dev/cloudeko/zenei/application/web/model/response/EmailAddressResponse.java @@ -0,0 +1,43 @@ +package dev.cloudeko.zenei.application.web.model.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import dev.cloudeko.zenei.domain.model.email.EmailAddress; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.eclipse.microprofile.openapi.annotations.media.Schema; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@RegisterForReflection +@Schema(name = "EmailAddress", description = "Represents an email address") +public class EmailAddressResponse { + + @JsonProperty("email") + @Schema(description = "Email address") + private String email; + + @JsonProperty("email_verified") + @Schema(description = "Whether the email address has been verified") + private Boolean emailVerified; + + @JsonProperty("created_at") + @Schema(description = "Timestamp when the email address was created") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + private LocalDateTime createdAt; + + @JsonProperty("updated_at") + @Schema(description = "Timestamp when the email address was last updated") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + private LocalDateTime updatedAt; + + public EmailAddressResponse(EmailAddress emailAddress) { + this.email = emailAddress.getEmail(); + this.emailVerified = emailAddress.getEmailVerified(); + this.createdAt = emailAddress.getCreatedAt(); + this.updatedAt = emailAddress.getUpdatedAt(); + } +} diff --git a/src/main/java/dev/cloudeko/zenei/application/web/model/response/PrivateUserResponse.java b/src/main/java/dev/cloudeko/zenei/application/web/model/response/PrivateUserResponse.java index 782254b..a5ad018 100644 --- a/src/main/java/dev/cloudeko/zenei/application/web/model/response/PrivateUserResponse.java +++ b/src/main/java/dev/cloudeko/zenei/application/web/model/response/PrivateUserResponse.java @@ -9,6 +9,7 @@ import org.eclipse.microprofile.openapi.annotations.media.Schema; import java.time.LocalDateTime; +import java.util.List; @Data @NoArgsConstructor @@ -36,6 +37,10 @@ public class PrivateUserResponse { @Schema(description = "Primary email of the user") private String primaryEmailAddress; + @JsonProperty("email_addresses") + @Schema(description = "Email addresses of the user") + private List emailAddresses; + @JsonProperty("image") @Schema(description = "URL of the user's profile picture") private String image; @@ -64,6 +69,7 @@ public PrivateUserResponse(User user) { this.firstName = user.getFirstName(); this.lastName = user.getLastName(); this.primaryEmailAddress = user.getPrimaryEmailAddress().getEmail(); + this.emailAddresses = user.getEmailAddresses().stream().map(EmailAddressResponse::new).toList(); this.image = user.getImage(); this.admin = user.isAdmin(); this.passwordEnabled = user.isPasswordEnabled(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2489dd4..0b2e440 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -17,7 +17,7 @@ quarkus.hibernate-orm.database.generation=update # Mail configuration # Use a mock mailer for development, not sending actual emails. -quarkus.mailer.mock=true +%dev.quarkus.mailer.mock=true # In 'prod' profile, disable the mock mailer and send real emails. %prod.quarkus.mailer.mock=false diff --git a/src/test/java/dev/cloudeko/zenei/auth/AuthenticationFlowWithDisabledMailingTest.java b/src/test/java/dev/cloudeko/zenei/auth/AuthenticationFlowWithDisabledMailingTest.java new file mode 100644 index 0000000..f5c48f9 --- /dev/null +++ b/src/test/java/dev/cloudeko/zenei/auth/AuthenticationFlowWithDisabledMailingTest.java @@ -0,0 +1,86 @@ +package dev.cloudeko.zenei.auth; + +import dev.cloudeko.zenei.application.web.model.response.TokenResponse; +import dev.cloudeko.zenei.profile.MailingDisabledProfile; +import io.quarkus.mailer.MockMailbox; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import jakarta.inject.Inject; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.*; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@QuarkusTest +@TestProfile(MailingDisabledProfile.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class AuthenticationFlowWithDisabledMailingTest { + + @ConfigProperty(name = "quarkus.http.test-port") + int quarkusPort; + + @Inject + MockMailbox mailbox; + + @BeforeAll + static void setup() { + RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); + } + + @Test + @Order(1) + @DisplayName("Create user via email and password (POST /user) should return (200 OK)") + void testCreateUser() { + given() + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .formParam("username", "test-user2") + .formParam("email", "test@test.com") + .formParam("password", "test-password") + .formParam("strategy", "PASSWORD") + .post("/user") + .then() + .statusCode(Response.Status.OK.getStatusCode()) + .body( + "id", notNullValue(), + "username", notNullValue(), + "primary_email_address", notNullValue() + ); + + assertEquals(0, mailbox.getTotalMessagesSent()); + } + + @Test + @Order(2) + @DisplayName("Retrieve user information (GET /user) should return (200 OK)") + void testGetUserInfo() { + final var token = given() + .contentType(MediaType.APPLICATION_JSON) + .queryParam("grant_type", "password") + .queryParam("username", "test@test.com") + .queryParam("password", "test-password") + .post("/user/token") + .then() + .statusCode(Response.Status.OK.getStatusCode()) + .extract().as(TokenResponse.class); + + given() + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer " + token.getAccessToken()) + .get("/user") + .then() + .statusCode(Response.Status.OK.getStatusCode()) + .body( + "id", notNullValue(), + "username", notNullValue(), + "primary_email_address", notNullValue(), + "email_addresses", notNullValue(), + "email_addresses.size()", greaterThanOrEqualTo(1), + "email_addresses.getFirst().email_verified", equalTo(true) + ); + } +} diff --git a/src/test/java/dev/cloudeko/zenei/auth/AuthenticationFlowTest.java b/src/test/java/dev/cloudeko/zenei/auth/AuthenticationFlowWithEnabledMailingTest.java similarity index 94% rename from src/test/java/dev/cloudeko/zenei/auth/AuthenticationFlowTest.java rename to src/test/java/dev/cloudeko/zenei/auth/AuthenticationFlowWithEnabledMailingTest.java index 014efa7..6c6be7a 100644 --- a/src/test/java/dev/cloudeko/zenei/auth/AuthenticationFlowTest.java +++ b/src/test/java/dev/cloudeko/zenei/auth/AuthenticationFlowWithEnabledMailingTest.java @@ -1,9 +1,10 @@ package dev.cloudeko.zenei.auth; import dev.cloudeko.zenei.application.web.model.response.TokenResponse; -import dev.cloudeko.zenei.domain.model.Token; +import dev.cloudeko.zenei.profile.MailingEnabledProfile; import io.quarkus.mailer.MockMailbox; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; import io.restassured.RestAssured; import jakarta.inject.Inject; import jakarta.ws.rs.core.MediaType; @@ -12,13 +13,14 @@ import org.junit.jupiter.api.*; import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @QuarkusTest +@TestProfile(MailingEnabledProfile.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class AuthenticationFlowTest { +public class AuthenticationFlowWithEnabledMailingTest { @ConfigProperty(name = "quarkus.http.test-port") int quarkusPort; @@ -211,7 +213,10 @@ void testGetUserInfo() { .body( "id", notNullValue(), "username", notNullValue(), - "primary_email_address", notNullValue() + "primary_email_address", notNullValue(), + "email_addresses", notNullValue(), + "email_addresses.size()", greaterThanOrEqualTo(1), + "email_addresses.getFirst().email_verified", equalTo(true) ); } diff --git a/src/test/java/dev/cloudeko/zenei/profile/MailingDisabledProfile.java b/src/test/java/dev/cloudeko/zenei/profile/MailingDisabledProfile.java new file mode 100644 index 0000000..9ac2665 --- /dev/null +++ b/src/test/java/dev/cloudeko/zenei/profile/MailingDisabledProfile.java @@ -0,0 +1,13 @@ +package dev.cloudeko.zenei.profile; + +import io.quarkus.test.junit.QuarkusTestProfile; + +import java.util.Map; + +public class MailingDisabledProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.mailer.mock", "false", "zenei.mailer.auto-confirm", "true"); + } +} diff --git a/src/test/java/dev/cloudeko/zenei/profile/MailingEnabledProfile.java b/src/test/java/dev/cloudeko/zenei/profile/MailingEnabledProfile.java new file mode 100644 index 0000000..4229cfc --- /dev/null +++ b/src/test/java/dev/cloudeko/zenei/profile/MailingEnabledProfile.java @@ -0,0 +1,14 @@ +package dev.cloudeko.zenei.profile; + +import io.quarkus.test.junit.QuarkusTestProfile; + +import java.util.Collections; +import java.util.Map; + +public class MailingEnabledProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Collections.singletonMap("quarkus.mailer.mock", "true"); + } +}