Binde Auftragshistorie an und korrigiere Kundenanzeige

Ergänzt die lesende Historien-API mit Verlaufsanzeige und ordnet Auftragskunden dem effektiven Zahler zu. Enthält VS-Code-Debugkonfiguration, Dokumentation und Regressionstests.

Validiert mit Frontend-Build, 76 Backend-Tests und 25 Browsertests. Live-Datenbankprüfung wegen fehlender Erreichbarkeit offen.
This commit is contained in:
Sven committed 2026-10-07 20:40:58 +02:00
1 parent ac5ea374b9
commit 75e2bf2bec
24 files changed
+1069 -60

No files matched your search

@@ -17,6 +17,7 @@ import org.springframework.test.web.servlet.MvcResult;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.*;
@@ -35,6 +36,7 @@ class ApiSecurityTest {
record Csrf(MockHttpSession session, String token, String header) {}
@BeforeEach void configure() {
when(database.readTransaction(any())).thenAnswer(call -> ((Supplier<?>)call.getArgument(0)).get());
when(database.checkConnection()).thenReturn(new DatabaseService.ConnectionCheck(true,"CONNECTED","Verbunden"));
when(database.queryForList(eq(LegacyAuthenticationProvider.AUTH_SQL),eq("existing-user"),eq("existing-password"),eq("existing-password")))
.thenReturn(List.of(Map.of("usr_id",11,"emp_id",12,"hq_id",7,"usr_account","existing-user","emp_rights","111111111111")));
@@ -70,6 +72,8 @@ class ApiSecurityTest {
MvcResult unauthorized = mvc.perform(get("/api/orders")).andExpect(status().isUnauthorized())
.andExpect(header().doesNotExist("WWW-Authenticate")).andReturn();
assertThat(unauthorized.getRequest().getSession(false)).isNull();
mvc.perform(get("/api/orders/42/history")).andExpect(status().isUnauthorized())
.andExpect(header().doesNotExist("WWW-Authenticate"));
Csrf csrf = csrf(null);
mvc.perform(post("/api/orders").session(csrf.session()).header(csrf.header(),csrf.token())
.contentType("application/json").content("{}"))
@@ -180,6 +184,52 @@ class ApiSecurityTest {
mvc.perform(get("/api/orders").session(session)).andExpect(status().isForbidden());
mvc.perform(get("/api/customers").session(session)).andExpect(status().isForbidden());
mvc.perform(get("/api/orders/export.csv").session(session)).andExpect(status().isForbidden());
mvc.perform(get("/api/orders/42/history").session(session)).andExpect(status().isForbidden());
verify(database,never()).queryForList(contains("phoenix_log"),any(Object[].class));
}
@Test void historyOfMissingOrForeignOrderReturns404WithoutReadingLog() throws Exception {
MockHttpSession session = authenticated("existing-user","existing-password");
mvc.perform(get("/api/orders/42/history").session(session)).andExpect(status().isNotFound());
verify(database).queryForList("SELECT jb.jb_id FROM job jb WHERE jb.jb_id=? AND jb.hq_id=?",42L,7L);
verify(database,never()).queryForList(contains("phoenix_log"),any(Object[].class));
}
@Test void accessibleOrderWithNoLogReturnsEmptyHistory() throws Exception {
MockHttpSession session = authenticated("existing-user","existing-password");
when(database.queryForList("SELECT jb.jb_id FROM job jb WHERE jb.jb_id=? AND jb.hq_id=?",42L,7L))
.thenReturn(List.of(Map.of("jb_id",42L)));
mvc.perform(get("/api/orders/42/history").session(session))
.andExpect(status().isOk()).andExpect(content().json("[]"));
verify(database).queryForList(contains("FROM `phoenix_log`.log l"),eq(42L),eq(7L));
}
@Test void historyReturnsExistingEvents() throws Exception {
MockHttpSession session = authenticated("existing-user","existing-password");
when(database.queryForList("SELECT jb.jb_id FROM job jb WHERE jb.jb_id=? AND jb.hq_id=?",42L,7L))
.thenReturn(List.of(Map.of("jb_id",42L)));
when(database.queryForList(contains("FROM `phoenix_log`.log l"),eq(42L),eq(7L)))
.thenReturn(List.of(Map.of("log_id",81L,"logo_id",1,"log_createtime","2026-10-07 08:15:00",
"logo_op_name","Auftrag angelegt","logo_description","Testhistorie","usr_account","disponent")));
mvc.perform(get("/api/orders/42/history").session(session)).andExpect(status().isOk())
.andExpect(jsonPath("$.length()").value(1)).andExpect(jsonPath("$[0].id").value(81))
.andExpect(jsonPath("$[0].at").value("2026-10-07T08:15:00"))
.andExpect(jsonPath("$[0].action").isString()).andExpect(jsonPath("$[0].actor").isString());
}
@Test void logFailureReturns503AndDoesNotDiscloseDatabaseDetails() throws Exception {
MockHttpSession session = authenticated("existing-user","existing-password");
when(database.queryForList("SELECT jb.jb_id FROM job jb WHERE jb.jb_id=? AND jb.hq_id=?",42L,7L))
.thenReturn(List.of(Map.of("jb_id",42L)));
when(database.queryForList(contains("FROM `phoenix_log`.log l"),eq(42L),eq(7L)))
.thenThrow(new DataAccessResourceFailureException("private database credentials"));
var response=mvc.perform(get("/api/orders/42/history").session(session))
.andExpect(status().isServiceUnavailable()).andReturn();
String message=json.readTree(response.getResponse().getContentAsString()).get("message").asText();
assertThat(message).contains("Auftragshistorie").doesNotContain("private database credentials");
}
@Test void databaseAccountDenialIsDistinguishedFromNetworkFailure() throws Exception {