From e2704a8b450dea0cd7f9b5dd2106e387e565dfa2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:15:10 +0000 Subject: [PATCH 1/7] COG-1149: add UserFront CI workflow (mvn verify + AdminPortal build) Co-Authored-By: patrick.bradley --- .github/workflows/userfront-ci.yml | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/userfront-ci.yml diff --git a/.github/workflows/userfront-ci.yml b/.github/workflows/userfront-ci.yml new file mode 100644 index 00000000..a6ccccc6 --- /dev/null +++ b/.github/workflows/userfront-ci.yml @@ -0,0 +1,63 @@ +name: UserFront CI + +on: + pull_request: + push: + branches: [master, main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + userfront-verify: + name: UserFront · mvn verify + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Keep in sync with in UserFront/pom.xml (1.8 today). + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '8' + cache: maven + cache-dependency-path: UserFront/pom.xml + + - name: mvn verify + run: mvn -B -f UserFront/pom.xml verify + + - name: Upload surefire reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: userfront-surefire-reports + path: | + UserFront/target/surefire-reports/ + UserFront/target/failsafe-reports/ + if-no-files-found: ignore + + adminportal-build: + name: AdminPortal · ng build (non-blocking) + runs-on: ubuntu-latest + # Angular CLI 1.1.2 / Angular 4 needs legacy Node 12. + continue-on-error: true + defaults: + run: + working-directory: AdminPortal + steps: + - uses: actions/checkout@v4 + + - name: Set up Node 12 + uses: actions/setup-node@v4 + with: + node-version: '12' + + - name: Install dependencies + run: | + if [ -f package-lock.json ]; then npm ci; else npm install --no-audit --no-fund; fi + + - name: Build + run: npm run build From 449843a7d94c41814c9f467e0c72330beaeb4e55 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:16:35 +0000 Subject: [PATCH 2/7] COG-1149: bump actions to v5 (Node 24 runtime) Co-Authored-By: patrick.bradley --- .github/workflows/userfront-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/userfront-ci.yml b/.github/workflows/userfront-ci.yml index a6ccccc6..3fbfaeb6 100644 --- a/.github/workflows/userfront-ci.yml +++ b/.github/workflows/userfront-ci.yml @@ -15,11 +15,11 @@ jobs: name: UserFront · mvn verify runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 # Keep in sync with in UserFront/pom.xml (1.8 today). - name: Set up JDK 8 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: '8' @@ -31,7 +31,7 @@ jobs: - name: Upload surefire reports if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: userfront-surefire-reports path: | @@ -48,10 +48,10 @@ jobs: run: working-directory: AdminPortal steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Node 12 - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version: '12' From 3672d0eed1063a2277c0ccb963eff2fd88c79bca Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:16:36 +0000 Subject: [PATCH 3/7] COG-1141: Testcontainers MySQL harness for @SpringBootTest (Boot 1.5.4 / Java 8) Co-Authored-By: patrick.bradley --- UserFront/pom.xml | 25 +++++++ .../userFront/AbstractIntegrationTest.java | 75 +++++++++++++++++++ .../userFront/UserFrontApplicationTests.java | 7 +- 3 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java diff --git a/UserFront/pom.xml b/UserFront/pom.xml index 902ec2b3..212ce79a 100644 --- a/UserFront/pom.xml +++ b/UserFront/pom.xml @@ -22,6 +22,8 @@ UTF-8 UTF-8 1.8 + + 5.1.49 @@ -60,6 +62,29 @@ spring-boot-starter-test test + + + org.springframework.security + spring-security-test + test + + + + + org.testcontainers + mysql + 1.19.7 + test + + + + + org.testcontainers + testcontainers + 1.19.7 + test + diff --git a/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java b/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java new file mode 100644 index 00000000..e9698e70 --- /dev/null +++ b/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java @@ -0,0 +1,75 @@ +package com.userFront; + +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.utility.DockerImageName; + +/** + * Entry point for the UserFront smoke suite: every {@code @SpringBootTest} extends this class. + * + *

Boots the whole application against a MySQL Testcontainer, so {@code mvn verify} needs only + * Docker, not a local MySQL. A single container is started once per JVM and its JDBC coordinates + * are pushed into the environment by {@link DataSourceInitializer} before the context refreshes + * ({@code spring-boot-testcontainers} / {@code @ServiceConnection} only exist from Boot 3.1). + * The schema is created from the JPA entities with {@code spring.jpa.hibernate.ddl-auto=update}. + * + *

The image defaults to {@value #DEFAULT_MYSQL_IMAGE}, the assumed production major version, + * and can be overridden with {@code -Dtest.mysql.image=mysql:5.7}. The server is started with + * {@code mysql_native_password} because the Boot 1.5 stack ships Connector/J 5.1, which cannot + * speak {@code caching_sha2_password}. + * + *

Subclasses get an autoconfigured {@link MockMvc} (with the Spring Security filter chain) + * as {@link #mockMvc}. + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = UserFrontApplication.class) +@AutoConfigureMockMvc +@ContextConfiguration(initializers = AbstractIntegrationTest.DataSourceInitializer.class) +public abstract class AbstractIntegrationTest { + + public static final String DEFAULT_MYSQL_IMAGE = "mysql:8.0"; + + static { + // Testcontainers 1.19.x pins docker-java to API 1.32, which Docker Engine 25+ rejects. + if (System.getProperty("api.version") == null && System.getenv("DOCKER_API_VERSION") == null) { + System.setProperty("api.version", "1.44"); + } + } + + protected static final MySQLContainer MYSQL = new MySQLContainer<>( + DockerImageName.parse(System.getProperty("test.mysql.image", DEFAULT_MYSQL_IMAGE)) + .asCompatibleSubstituteFor("mysql")) + .withDatabaseName("onlinebanking") + .withUrlParam("useSSL", "false") + .withCommand("--default-authentication-plugin=mysql_native_password"); + + static { + MYSQL.start(); + } + + @Autowired + protected MockMvc mockMvc; + + public static class DataSourceInitializer + implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + EnvironmentTestUtils.addEnvironment(applicationContext, + "spring.datasource.url=" + MYSQL.getJdbcUrl(), + "spring.datasource.username=" + MYSQL.getUsername(), + "spring.datasource.password=" + MYSQL.getPassword(), + "spring.datasource.driver-class-name=" + MYSQL.getDriverClassName(), + "spring.jpa.hibernate.ddl-auto=update"); + } + } +} diff --git a/UserFront/src/test/java/com/userFront/UserFrontApplicationTests.java b/UserFront/src/test/java/com/userFront/UserFrontApplicationTests.java index 81fed97b..039acb05 100644 --- a/UserFront/src/test/java/com/userFront/UserFrontApplicationTests.java +++ b/UserFront/src/test/java/com/userFront/UserFrontApplicationTests.java @@ -1,13 +1,8 @@ package com.userFront; import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringRunner.class) -@SpringBootTest -public class UserFrontApplicationTests { +public class UserFrontApplicationTests extends AbstractIntegrationTest { @Test public void contextLoads() { From f28c095702d8900e31020a5e01074146d2cae5cf Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:18:06 +0000 Subject: [PATCH 4/7] COG-1149: upload-artifact v6 (node24) Co-Authored-By: patrick.bradley --- .github/workflows/userfront-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/userfront-ci.yml b/.github/workflows/userfront-ci.yml index 3fbfaeb6..d99370d7 100644 --- a/.github/workflows/userfront-ci.yml +++ b/.github/workflows/userfront-ci.yml @@ -31,7 +31,7 @@ jobs: - name: Upload surefire reports if: failure() - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: userfront-surefire-reports path: | From ecbe4c57ad10e0776bb3679568059fe41332257f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:19:17 +0000 Subject: [PATCH 5/7] COG-1141: cap Docker API version at the daemon's maximum Co-Authored-By: patrick.bradley --- .../userFront/AbstractIntegrationTest.java | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java b/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java index e9698e70..c7e95917 100644 --- a/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java +++ b/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java @@ -1,5 +1,10 @@ package com.userFront; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.concurrent.TimeUnit; + import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -38,10 +43,12 @@ public abstract class AbstractIntegrationTest { public static final String DEFAULT_MYSQL_IMAGE = "mysql:8.0"; + /** Docker Engine 25+ rejects the API 1.32 that Testcontainers 1.19.x requests by default. */ + private static final String PREFERRED_DOCKER_API_VERSION = "1.44"; + static { - // Testcontainers 1.19.x pins docker-java to API 1.32, which Docker Engine 25+ rejects. if (System.getProperty("api.version") == null && System.getenv("DOCKER_API_VERSION") == null) { - System.setProperty("api.version", "1.44"); + System.setProperty("api.version", dockerApiVersion()); } } @@ -59,6 +66,44 @@ public abstract class AbstractIntegrationTest { @Autowired protected MockMvc mockMvc; + /** + * {@value #PREFERRED_DOCKER_API_VERSION}, capped at the daemon's maximum API version (as + * reported by the {@code docker} CLI) so older engines such as Docker 24 (API 1.43) still work. + */ + private static String dockerApiVersion() { + try { + Process process = new ProcessBuilder("docker", "version", "--format", "{{.Server.APIVersion}}") + .redirectErrorStream(true) + .start(); + if (!process.waitFor(10, TimeUnit.SECONDS)) { + process.destroyForcibly(); + return PREFERRED_DOCKER_API_VERSION; + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String serverMax = reader.readLine(); + if (process.exitValue() == 0 && serverMax != null && serverMax.trim().matches("\\d+\\.\\d+")) { + return compareApiVersions(serverMax.trim(), PREFERRED_DOCKER_API_VERSION) < 0 + ? serverMax.trim() + : PREFERRED_DOCKER_API_VERSION; + } + } + } + catch (IOException e) { + // No docker CLI on the PATH: fall back to the preferred version. + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return PREFERRED_DOCKER_API_VERSION; + } + + private static int compareApiVersions(String a, String b) { + String[] x = a.split("\\."); + String[] y = b.split("\\."); + int major = Integer.compare(Integer.parseInt(x[0]), Integer.parseInt(y[0])); + return major != 0 ? major : Integer.compare(Integer.parseInt(x[1]), Integer.parseInt(y[1])); + } + public static class DataSourceInitializer implements ApplicationContextInitializer { From c129a882d951f54a52b0a5736b6e8967abd99b7b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:20:58 +0000 Subject: [PATCH 6/7] COG-1141: default harness to mysql:5.7.44 until the Connector/J upgrade Co-Authored-By: patrick.bradley --- UserFront/pom.xml | 2 -- .../com/userFront/AbstractIntegrationTest.java | 15 ++++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/UserFront/pom.xml b/UserFront/pom.xml index 212ce79a..ec078f38 100644 --- a/UserFront/pom.xml +++ b/UserFront/pom.xml @@ -22,8 +22,6 @@ UTF-8 UTF-8 1.8 - - 5.1.49 diff --git a/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java b/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java index c7e95917..b5dd2870 100644 --- a/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java +++ b/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java @@ -27,10 +27,12 @@ * ({@code spring-boot-testcontainers} / {@code @ServiceConnection} only exist from Boot 3.1). * The schema is created from the JPA entities with {@code spring.jpa.hibernate.ddl-auto=update}. * - *

The image defaults to {@value #DEFAULT_MYSQL_IMAGE}, the assumed production major version, - * and can be overridden with {@code -Dtest.mysql.image=mysql:5.7}. The server is started with - * {@code mysql_native_password} because the Boot 1.5 stack ships Connector/J 5.1, which cannot - * speak {@code caching_sha2_password}. + *

The image defaults to {@value #DEFAULT_MYSQL_IMAGE} and can be overridden with + * {@code -Dtest.mysql.image=...}. It stays on MySQL 5.7 because the Boot 1.5 BOM ships + * Connector/J 5.1.42, which cannot connect to MySQL 8 ({@code Unknown system variable + * 'query_cache_size'}) or authenticate with {@code caching_sha2_password}; move to the production + * major version (8.0) together with the Connector/J upgrade. {@code useSSL=false} is appended to + * the JDBC URL because current JDK 8 builds disable the TLSv1/1.1 that Connector/J 5.1 negotiates. * *

Subclasses get an autoconfigured {@link MockMvc} (with the Spring Security filter chain) * as {@link #mockMvc}. @@ -41,7 +43,7 @@ @ContextConfiguration(initializers = AbstractIntegrationTest.DataSourceInitializer.class) public abstract class AbstractIntegrationTest { - public static final String DEFAULT_MYSQL_IMAGE = "mysql:8.0"; + public static final String DEFAULT_MYSQL_IMAGE = "mysql:5.7.44"; /** Docker Engine 25+ rejects the API 1.32 that Testcontainers 1.19.x requests by default. */ private static final String PREFERRED_DOCKER_API_VERSION = "1.44"; @@ -56,8 +58,7 @@ public abstract class AbstractIntegrationTest { DockerImageName.parse(System.getProperty("test.mysql.image", DEFAULT_MYSQL_IMAGE)) .asCompatibleSubstituteFor("mysql")) .withDatabaseName("onlinebanking") - .withUrlParam("useSSL", "false") - .withCommand("--default-authentication-plugin=mysql_native_password"); + .withUrlParam("useSSL", "false"); static { MYSQL.start(); From ba5a6c09da84bc3196f61b25b0de38aa8a646649 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:21:53 +0000 Subject: [PATCH 7/7] COG-1141: query the default Docker context when DOCKER_HOST is unset Co-Authored-By: patrick.bradley --- .../java/com/userFront/AbstractIntegrationTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java b/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java index b5dd2870..1e767f16 100644 --- a/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java +++ b/UserFront/src/test/java/com/userFront/AbstractIntegrationTest.java @@ -3,6 +3,9 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.runner.RunWith; @@ -70,10 +73,16 @@ public abstract class AbstractIntegrationTest { /** * {@value #PREFERRED_DOCKER_API_VERSION}, capped at the daemon's maximum API version (as * reported by the {@code docker} CLI) so older engines such as Docker 24 (API 1.43) still work. + * Without {@code DOCKER_HOST} the CLI is pinned to the {@code default} context, i.e. the local + * socket Testcontainers falls back to, rather than whatever context the CLI has selected. */ private static String dockerApiVersion() { + List command = new ArrayList<>(Arrays.asList("docker", "version", "--format", "{{.Server.APIVersion}}")); + if (System.getenv("DOCKER_HOST") == null) { + command.addAll(1, Arrays.asList("--context", "default")); + } try { - Process process = new ProcessBuilder("docker", "version", "--format", "{{.Server.APIVersion}}") + Process process = new ProcessBuilder(command) .redirectErrorStream(true) .start(); if (!process.waitFor(10, TimeUnit.SECONDS)) {