implement testing
This commit is contained in:
@@ -28,6 +28,14 @@ $env:ADMIN_PASSWORD = "secret"
|
|||||||
$env:DB_URL = "jdbc:postgresql://localhost:5432/linux_distro_roguelite_promotion"
|
$env:DB_URL = "jdbc:postgresql://localhost:5432/linux_distro_roguelite_promotion"
|
||||||
$env:DB_USERNAME = "postgres"
|
$env:DB_USERNAME = "postgres"
|
||||||
$env:DB_PASSWORD = "postgres"
|
$env:DB_PASSWORD = "postgres"
|
||||||
|
$env:TOKEN_SECRET = "<registration-token-secret>"
|
||||||
|
$env:SMTP_HOST = "mail.fretux.ch"
|
||||||
|
$env:SMTP_PORT = "587"
|
||||||
|
$env:SMTP_SECURE = "0"
|
||||||
|
$env:SMTP_USER = "no-reply@fretux.ch"
|
||||||
|
$env:SMTP_PASS = "<smtp-password>"
|
||||||
|
$env:MAIL_FROM = "Fretux <no-reply@fretux.ch>"
|
||||||
|
$env:BASE_URL = "localhost:3000"
|
||||||
mvn quarkus:dev
|
mvn quarkus:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -67,6 +75,23 @@ Die PostgreSQL-Verbindung wird ueber Umgebungsvariablen konfiguriert:
|
|||||||
- `DB_USERNAME`, Standard: `postgres`
|
- `DB_USERNAME`, Standard: `postgres`
|
||||||
- `DB_PASSWORD`, Standard: `postgres`
|
- `DB_PASSWORD`, Standard: `postgres`
|
||||||
|
|
||||||
|
## Mailversand
|
||||||
|
|
||||||
|
`POST /users` verschickt nach erfolgreicher Registrierung eine Bestaetigungsmail. Die E-Mail-Adresse ist Pflicht und kann wegen des eindeutigen Datenbank-Constraints nur einmal verwendet werden. Wenn der Mailversand fehlschlaegt, wird der neu angelegte Nutzer wieder entfernt und die API antwortet mit `502`.
|
||||||
|
|
||||||
|
Der SMTP-Zugang wird ueber Umgebungsvariablen konfiguriert:
|
||||||
|
|
||||||
|
- `TOKEN_SECRET`: Secret fuer den signierten Registrierungslink
|
||||||
|
- `SMTP_HOST`, Standard: `mail.fretux.ch`
|
||||||
|
- `SMTP_PORT`, Standard: `587`
|
||||||
|
- `SMTP_SECURE`, Standard: `false`; fuer Port 587 kann `0` verwendet werden
|
||||||
|
- `SMTP_USER`, Standard: `no-reply@fretux.ch`
|
||||||
|
- `SMTP_PASS`: SMTP-Passwort
|
||||||
|
- `MAIL_FROM`, Standard: `Fretux <no-reply@fretux.ch>`
|
||||||
|
- `BASE_URL`, Standard: `http://localhost:3000`
|
||||||
|
|
||||||
|
In Tests ist `quarkus.mailer.mock=true` gesetzt, damit keine echte E-Mail verschickt wird.
|
||||||
|
|
||||||
Das Schema entspricht dem Klassendiagramm:
|
Das Schema entspricht dem Klassendiagramm:
|
||||||
|
|
||||||
- `users`: `id`, `username`, `email`, `newsletter_optin`, `created_at`
|
- `users`: `id`, `username`, `email`, `newsletter_optin`, `created_at`
|
||||||
|
|||||||
@@ -57,6 +57,10 @@
|
|||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-smallrye-openapi</artifactId>
|
<artifactId>quarkus-smallrye-openapi</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.quarkus</groupId>
|
||||||
|
<artifactId>quarkus-mailer</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.quarkus</groupId>
|
<groupId>io.quarkus</groupId>
|
||||||
<artifactId>quarkus-junit</artifactId>
|
<artifactId>quarkus-junit</artifactId>
|
||||||
|
|||||||
@@ -24,10 +24,12 @@ import java.util.Map;
|
|||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public class PromotionResource {
|
public class PromotionResource {
|
||||||
private final DatabaseService database;
|
private final DatabaseService database;
|
||||||
|
private final RegistrationEmailService registrationEmailService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PromotionResource(DatabaseService database) {
|
public PromotionResource(DatabaseService database, RegistrationEmailService registrationEmailService) {
|
||||||
this.database = database;
|
this.database = database;
|
||||||
|
this.registrationEmailService = registrationEmailService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -52,6 +54,14 @@ public class PromotionResource {
|
|||||||
} catch (RuntimeException exception) {
|
} catch (RuntimeException exception) {
|
||||||
throw mapConflict(exception, "username or email already exists.");
|
throw mapConflict(exception, "username or email already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
registrationEmailService.sendRegistrationMail(id, username, email);
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
database.execute("DELETE FROM users WHERE id = ?", id);
|
||||||
|
throw new ApiException(502, "Registration email could not be sent. Please try again later.");
|
||||||
|
}
|
||||||
|
|
||||||
return Response.status(Response.Status.CREATED).entity(userById(id)).build();
|
return Response.status(Response.Status.CREATED).entity(userById(id)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ registerForm.addEventListener("submit", async (event) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
registerForm.reset();
|
registerForm.reset();
|
||||||
setMessage("Pre-registration complete. Use Auth to unlock private game intel.");
|
setMessage("Pre-registration complete. Check your inbox, then use Auth to unlock private game intel.");
|
||||||
await refreshStats();
|
await refreshStats();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setMessage(error.message, true);
|
setMessage(error.message, true);
|
||||||
|
|||||||
@@ -7,6 +7,16 @@ quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/linux_dist
|
|||||||
quarkus.datasource.jdbc.max-size=8
|
quarkus.datasource.jdbc.max-size=8
|
||||||
quarkus.log.category."dev.frederik.promotion".level=INFO
|
quarkus.log.category."dev.frederik.promotion".level=INFO
|
||||||
|
|
||||||
|
quarkus.mailer.host=${SMTP_HOST:mail.fretux.ch}
|
||||||
|
quarkus.mailer.port=${SMTP_PORT:587}
|
||||||
|
quarkus.mailer.username=${SMTP_USER:no-reply@fretux.ch}
|
||||||
|
quarkus.mailer.password=${SMTP_PASS:}
|
||||||
|
quarkus.mailer.from=${MAIL_FROM:Fretux <no-reply@fretux.ch>}
|
||||||
|
quarkus.mailer.tls=${SMTP_SECURE:false}
|
||||||
|
quarkus.mailer.start-tls=REQUIRED
|
||||||
|
registration.base-url=${BASE_URL:http://localhost:3000}
|
||||||
|
registration.token-secret=${TOKEN_SECRET:}
|
||||||
|
|
||||||
auth.admin.username=${ADMIN_USERNAME:admin}
|
auth.admin.username=${ADMIN_USERNAME:admin}
|
||||||
auth.admin.password=${ADMIN_PASSWORD:admin}
|
auth.admin.password=${ADMIN_PASSWORD:admin}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package dev.frederik.promotion;
|
package dev.frederik.promotion;
|
||||||
|
|
||||||
|
import io.quarkus.mailer.Mail;
|
||||||
|
import io.quarkus.mailer.MockMailbox;
|
||||||
import io.quarkus.test.junit.QuarkusTest;
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
@@ -21,6 +24,14 @@ class PromotionIntegrationTest {
|
|||||||
@Inject
|
@Inject
|
||||||
DataSource dataSource;
|
DataSource dataSource;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MockMailbox mailbox;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void clearMailbox() {
|
||||||
|
mailbox.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void platformsEndpointReturnsCurrentLaunchTargetsFromIsolatedDatabase() throws Exception {
|
void platformsEndpointReturnsCurrentLaunchTargetsFromIsolatedDatabase() throws Exception {
|
||||||
// Given: the Quarkus app started with the isolated test database and seeded launch platforms.
|
// Given: the Quarkus app started with the isolated test database and seeded launch platforms.
|
||||||
@@ -67,6 +78,48 @@ class PromotionIntegrationTest {
|
|||||||
Map<String, Object> row = findUserInDatabase(username);
|
Map<String, Object> row = findUserInDatabase(username);
|
||||||
assertEquals(email, row.get("email"));
|
assertEquals(email, row.get("email"));
|
||||||
assertEquals(Boolean.TRUE, row.get("newsletter_optin"));
|
assertEquals(Boolean.TRUE, row.get("newsletter_optin"));
|
||||||
|
|
||||||
|
List<Mail> sent = mailbox.getMailsSentTo(email);
|
||||||
|
assertEquals(1, sent.size());
|
||||||
|
assertEquals("Kernel Panic pre-registration", sent.get(0).getSubject());
|
||||||
|
assertTrue(sent.get(0).getText().contains("http://localhost:3000/auth.html?registration_token="));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void creatingSecondUserWithSameEmailIsRejected() throws Exception {
|
||||||
|
// Given: an email address already belongs to a pre-registered user.
|
||||||
|
String suffix = Long.toString(System.nanoTime());
|
||||||
|
String email = "unique_" + suffix + "@example.com";
|
||||||
|
Map<String, Object> firstPayload = Map.of(
|
||||||
|
"username", "first_" + suffix,
|
||||||
|
"email", email,
|
||||||
|
"newsletter_optin", false);
|
||||||
|
|
||||||
|
given()
|
||||||
|
.contentType("application/json")
|
||||||
|
.body(firstPayload)
|
||||||
|
.when()
|
||||||
|
.post("/users")
|
||||||
|
.then()
|
||||||
|
.statusCode(201);
|
||||||
|
|
||||||
|
// When: another username tries to reuse the same email address.
|
||||||
|
Map<String, Object> secondPayload = Map.of(
|
||||||
|
"username", "second_" + suffix,
|
||||||
|
"email", email,
|
||||||
|
"newsletter_optin", false);
|
||||||
|
|
||||||
|
given()
|
||||||
|
.contentType("application/json")
|
||||||
|
.body(secondPayload)
|
||||||
|
.when()
|
||||||
|
.post("/users")
|
||||||
|
.then()
|
||||||
|
.statusCode(409);
|
||||||
|
|
||||||
|
// Then: the email address is still stored only once and no second mail is sent.
|
||||||
|
assertEquals(1, countUsersByEmail(email));
|
||||||
|
assertEquals(1, mailbox.getMailsSentTo(email).size());
|
||||||
}
|
}
|
||||||
|
|
||||||
private int countCurrentPlatformsInDatabase() throws Exception {
|
private int countCurrentPlatformsInDatabase() throws Exception {
|
||||||
@@ -98,4 +151,19 @@ class PromotionIntegrationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int countUsersByEmail(String email) throws Exception {
|
||||||
|
try (Connection connection = dataSource.getConnection();
|
||||||
|
PreparedStatement statement = connection.prepareStatement("""
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM users
|
||||||
|
WHERE email = ?
|
||||||
|
""")) {
|
||||||
|
statement.setString(1, email);
|
||||||
|
try (ResultSet resultSet = statement.executeQuery()) {
|
||||||
|
resultSet.next();
|
||||||
|
return resultSet.getInt(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ quarkus.datasource.password=sa
|
|||||||
quarkus.datasource.jdbc.url=jdbc:h2:mem:kernel_panic_test;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH;DB_CLOSE_DELAY=-1
|
quarkus.datasource.jdbc.url=jdbc:h2:mem:kernel_panic_test;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH;DB_CLOSE_DELAY=-1
|
||||||
quarkus.datasource.jdbc.max-size=4
|
quarkus.datasource.jdbc.max-size=4
|
||||||
|
|
||||||
|
quarkus.mailer.mock=true
|
||||||
|
quarkus.mailer.from=Fretux <no-reply@fretux.ch>
|
||||||
|
registration.base-url=http://localhost:3000
|
||||||
|
registration.token-secret=test-token-secret
|
||||||
|
|
||||||
auth.admin.username=admin
|
auth.admin.username=admin
|
||||||
auth.admin.password=secret
|
auth.admin.password=secret
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user