implement testing

This commit is contained in:
2026-07-09 10:35:11 +02:00
parent 6f1b111037
commit df1ccbdb56
7 changed files with 124 additions and 2 deletions
+25
View File
@@ -28,6 +28,14 @@ $env:ADMIN_PASSWORD = "secret"
$env:DB_URL = "jdbc:postgresql://localhost:5432/linux_distro_roguelite_promotion"
$env:DB_USERNAME = "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
```
@@ -67,6 +75,23 @@ Die PostgreSQL-Verbindung wird ueber Umgebungsvariablen konfiguriert:
- `DB_USERNAME`, 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:
- `users`: `id`, `username`, `email`, `newsletter_optin`, `created_at`
+4
View File
@@ -57,6 +57,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mailer</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit</artifactId>
@@ -24,10 +24,12 @@ import java.util.Map;
@Produces(MediaType.APPLICATION_JSON)
public class PromotionResource {
private final DatabaseService database;
private final RegistrationEmailService registrationEmailService;
@Inject
public PromotionResource(DatabaseService database) {
public PromotionResource(DatabaseService database, RegistrationEmailService registrationEmailService) {
this.database = database;
this.registrationEmailService = registrationEmailService;
}
@GET
@@ -52,6 +54,14 @@ public class PromotionResource {
} catch (RuntimeException exception) {
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();
}
@@ -95,7 +95,7 @@ registerForm.addEventListener("submit", async (event) => {
);
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();
} catch (error) {
setMessage(error.message, true);
+10
View File
@@ -7,6 +7,16 @@ quarkus.datasource.jdbc.url=${DB_URL:jdbc:postgresql://localhost:5432/linux_dist
quarkus.datasource.jdbc.max-size=8
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.password=${ADMIN_PASSWORD:admin}
@@ -1,7 +1,10 @@
package dev.frederik.promotion;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.MockMailbox;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
@@ -21,6 +24,14 @@ class PromotionIntegrationTest {
@Inject
DataSource dataSource;
@Inject
MockMailbox mailbox;
@BeforeEach
void clearMailbox() {
mailbox.clear();
}
@Test
void platformsEndpointReturnsCurrentLaunchTargetsFromIsolatedDatabase() throws Exception {
// 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);
assertEquals(email, row.get("email"));
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 {
@@ -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.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.password=secret