feat(ui): integrate login with shared auth shell
This commit is contained in:
@@ -8,15 +8,15 @@
|
|||||||
|
|
||||||
## Protected behavior
|
## Protected behavior
|
||||||
|
|
||||||
The authenticated account-creation page consumes the shared role-aware desktop shell and posts to the real account endpoint. The public activation page consumes the local themed authentication shell while preserving its single-use raw-token form contract. The Admin dashboard and navigation link to the implemented `/admin/accounts/new` route.
|
The authenticated account-creation page consumes the shared role-aware desktop shell and posts to the real account endpoint. The public activation and login pages consume the local themed authentication shell while preserving their raw-token and Spring Security form contracts. The Admin dashboard and navigation link to the implemented `/admin/accounts/new` route, and logout remains a CSRF-protected POST in the shared shell.
|
||||||
|
|
||||||
## Test method
|
## Test method
|
||||||
|
|
||||||
A focused MockMvc slice renders both production account templates through a test-only controller. It asserts the authenticated and public shell markers, local pre-paint theme and CSS assets, real form actions, activation token retention, and the real account-creation URL. The existing PostgreSQL Account web flow then exercises account creation, activation, authentication, authorization, and logout through the production controller and services.
|
A focused MockMvc slice renders the production account-creation, activation, and login templates through a test-only controller. It asserts the authenticated and public shell markers, local pre-paint theme and CSS assets, real form actions, accessible error status, activation token retention, and the real account-creation URL. The existing PostgreSQL Account and Authentication web flows then exercise account creation, activation, normalized login, failed login, authorization, and logout through the production controllers and services.
|
||||||
|
|
||||||
## Hand-derived expected result
|
## Hand-derived expected result
|
||||||
|
|
||||||
The account-creation response contains `app-shell`, posts to `/admin/accounts`, and exposes `/admin/accounts/new` as the account navigation target. The activation response contains `auth-shell`, posts to `/activate`, retains `raw-token`, and loads `/assets/theme.js` before `/assets/app.css`. Existing account lifecycle and Admin dashboard requests remain successful on PostgreSQL.
|
The account-creation response contains `app-shell`, posts to `/admin/accounts`, and exposes `/admin/accounts/new` as the account navigation target. The activation response contains `auth-shell`, posts to `/activate`, retains `raw-token`, and loads `/assets/theme.js` before `/assets/app.css`. Login contains `auth-shell`, posts the expected `username` and `password` fields to `/login`, and exposes a live error announcement. Existing account lifecycle and Admin dashboard requests remain successful on PostgreSQL.
|
||||||
|
|
||||||
## RED
|
## RED
|
||||||
|
|
||||||
@@ -38,7 +38,16 @@ BUILD FAILURE
|
|||||||
Total time: 4.763 s
|
Total time: 4.763 s
|
||||||
```
|
```
|
||||||
|
|
||||||
Both production templates were standalone documents and did not consume either shared layout.
|
The account-creation and activation templates were standalone documents and did not consume either shared layout.
|
||||||
|
|
||||||
|
After the custom login page landed, its focused pre-change contract also failed as expected:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
|
||||||
|
AccountTemplateIntegrationTest.loginUsesPublicAuthShellAndPreservesAuthenticationContract expected class="auth-shell"
|
||||||
|
BUILD FAILURE
|
||||||
|
Total time: 5.343 s
|
||||||
|
```
|
||||||
|
|
||||||
## GREEN
|
## GREEN
|
||||||
|
|
||||||
@@ -66,12 +75,12 @@ Total time: 3.710 s
|
|||||||
export JAVA_HOME=/opt/homebrew/opt/openjdk@25
|
export JAVA_HOME=/opt/homebrew/opt/openjdk@25
|
||||||
export PATH="/opt/homebrew/opt/node@24/bin:$JAVA_HOME/bin:$PATH"
|
export PATH="/opt/homebrew/opt/node@24/bin:$JAVA_HOME/bin:$PATH"
|
||||||
export DOCKER_HOST=unix:///Users/sechmachine/.orbstack/run/docker.sock
|
export DOCKER_HOST=unix:///Users/sechmachine/.orbstack/run/docker.sock
|
||||||
./mvnw -Dtest=AccountWebIntegrationTest,AdminDashboardWebTest test
|
./mvnw -Dtest=AccountTemplateIntegrationTest,AuthenticationWebIntegrationTest,AccountWebIntegrationTest test
|
||||||
|
|
||||||
PostgreSQL 18.4
|
PostgreSQL 18.4
|
||||||
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
|
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
|
||||||
BUILD SUCCESS
|
BUILD SUCCESS
|
||||||
Total time: 12.219 s
|
Total time: 18.361 s
|
||||||
```
|
```
|
||||||
|
|
||||||
## External-test boundaries
|
## External-test boundaries
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,18 +1,19 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
<html lang="en" xmlns:th="http://www.thymeleaf.org"
|
||||||
<head><meta charset="utf-8"><title>Sign in · Lab Timesheet</title></head>
|
th:replace="~{fragments/auth-layout :: shell(
|
||||||
|
pageTitle='Sign in',
|
||||||
|
eyebrow='Lab Timesheet',
|
||||||
|
content=~{::main})}">
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<h1>Sign in</h1>
|
<p class="page-description">Use the email address and password associated with your active account.</p>
|
||||||
<p th:if="${param.error}">Invalid email or password</p>
|
<p class="alert alert-error" th:if="${param.error}" role="alert">Invalid email or password</p>
|
||||||
<p th:if="${param.logout}">You have signed out</p>
|
<p class="alert" th:if="${param.logout}" role="status">You have signed out</p>
|
||||||
<p th:if="${param.activated}">Your account is active. Sign in to continue.</p>
|
<p class="alert" th:if="${param.activated}" role="status">Your account is active. Sign in to continue.</p>
|
||||||
<form method="post" th:action="@{/login}">
|
<form class="form-grid auth-form" method="post" th:action="@{/login}">
|
||||||
<label for="username">Email</label>
|
<div class="field"><label class="field-label" for="username">Email</label><input class="control" id="username" name="username" type="email" autocomplete="username" required autofocus></div>
|
||||||
<input id="username" name="username" type="email" autocomplete="username" required autofocus>
|
<div class="field"><label class="field-label" for="password">Password</label><input class="control" id="password" name="password" type="password" autocomplete="current-password" required></div>
|
||||||
<label for="password">Password</label>
|
<button class="button button-primary" type="submit">Sign in</button>
|
||||||
<input id="password" name="password" type="password" autocomplete="current-password" required>
|
|
||||||
<button type="submit">Sign in</button>
|
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
+19
@@ -49,6 +49,20 @@ class AccountTemplateIntegrationTest {
|
|||||||
.andExpect(content().string(containsString("href=\"/assets/app.css\"")));
|
.andExpect(content().string(containsString("href=\"/assets/app.css\"")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void loginUsesPublicAuthShellAndPreservesAuthenticationContract() throws Exception {
|
||||||
|
mvc.perform(get("/template-contract/accounts/login")
|
||||||
|
.param("error", "")
|
||||||
|
.with(user("anonymous-template-viewer")))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().string(containsString("class=\"auth-shell\"")))
|
||||||
|
.andExpect(content().string(containsString("action=\"/login\"")))
|
||||||
|
.andExpect(content().string(containsString("name=\"username\"")))
|
||||||
|
.andExpect(content().string(containsString("autocomplete=\"current-password\"")))
|
||||||
|
.andExpect(content().string(containsString("role=\"alert\"")))
|
||||||
|
.andExpect(content().string(containsString("src=\"/assets/theme.js\"")));
|
||||||
|
}
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public static class TemplateController {
|
public static class TemplateController {
|
||||||
|
|
||||||
@@ -62,5 +76,10 @@ class AccountTemplateIntegrationTest {
|
|||||||
model.addAttribute("token", "raw-token");
|
model.addAttribute("token", "raw-token");
|
||||||
return "accounts/activate";
|
return "accounts/activate";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/template-contract/accounts/login")
|
||||||
|
String login() {
|
||||||
|
return "accounts/login";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user