From a2e008cc9896abb252717994e554a1408706a8e8 Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Mon, 18 May 2026 15:04:49 +0200 Subject: [PATCH 1/7] UNOMI-921: Replace elasticsearch-maven-plugin with Docker-based Elasticsearch in integration tests Implements the core configuration switch from UNOMI-921: https://issues.apache.org/jira/browse/UNOMI-921 Replace the com.github.alexcojocaru:elasticsearch-maven-plugin (binary download + forked JVM) with io.fabric8:docker-maven-plugin in the elasticsearch profile of itests, mirroring how the opensearch profile already runs OpenSearch in a Docker container. itests/pom.xml (elasticsearch profile) * Add an 9400 property and pass it through the failsafe systemPropertyVariables so tests resolve the HTTP port from a single source (unchanged from the previous 9400). * Replace the elasticsearch-maven-plugin block with a docker-maven-plugin block that runs docker.elastic.co/elasticsearch/elasticsearch:${elasticsearch.test.version}, binds target/snapshots_repository to /tmp/snapshots_repository, and waits on the HTTP port before the integration-test phase. Heap aligned to 8GB (-Xms8g -Xmx8g) to match the OpenSearch configuration and the ES 9 recommendation. Discovery=single-node, replicas=0, xpack.ml and xpack.security disabled. * Container lifecycle matches OpenSearch exactly: pre-integration-test runs stop+remove then start (with showLogs); post-integration-test runs stop only -- container is kept around for inspection. * Add a chmod -R ugo+rwx on snapshots_repository in the antrun unzip step: the ES container runs as UID 1000, so on Linux CI the bind-mounted snapshot repo otherwise hits access_denied during repository verify. pom.xml (root) * Declare 0.48.0 and add the pluginManagement entry so the elasticsearch profile (and any future user of the plugin) inherits a single version. Scope kept minimal for the PR #757 stack split: only the test infrastructure switch lives here. The follow-up UNOMI-921 acceptance items below ship in the platform PR (P) once it lands: * Remove BaseIT.fixDefaultTemplateIfNeeded() and the call in checkSearchEngine() (no longer needed with Docker). * Migrate16xToCurrentVersionIT: replace hardcoded ES_BASE_URL = "http://localhost:9400" with dynamic getSearchPort(). * Drop the comments referring to the elasticsearch-maven-plugin template-override workaround. See docs/PR-757-stack-extraction-tracker.md for the full split plan and how this PR fits in the stack. --- itests/pom.xml | 91 ++++++++++++++++++++++++++++++++++---------------- pom.xml | 6 ++++ 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/itests/pom.xml b/itests/pom.xml index 98e98e9e5..0d0ccde8c 100644 --- a/itests/pom.xml +++ b/itests/pom.xml @@ -196,6 +196,14 @@ + + + + + + @@ -210,6 +218,9 @@ elasticsearch + + 9400 + true @@ -229,6 +240,7 @@ foo elasticsearch + ${elasticsearch.port} @@ -247,43 +259,64 @@ - com.github.alexcojocaru - elasticsearch-maven-plugin - - 6.29 + io.fabric8 + docker-maven-plugin - - contextElasticSearchITests - 9500 - 9400 - ${elasticsearch.test.version} - true - 120 - - -Xms4g -Xmx4g - - - - false - ${project.build.directory}/snapshots_repository - false - OPTIONS,HEAD,GET,POST,PUT,DELETE - Authorization,X-Requested-With,X-Auth-Token,Content-Type,Content-Length - - + itests-elasticsearch + + + docker.elastic.co/elasticsearch/elasticsearch:${elasticsearch.test.version} + elasticsearch + + + ${elasticsearch.port}:9200 + + + single-node + -Xms8g -Xmx8g -Dcluster.default.index.settings.number_of_replicas=0 + false + false + /tmp/snapshots_repository + false + + + + ${project.build.directory}/snapshots_repository:/tmp/snapshots_repository + + + + + http://localhost:${elasticsearch.port} + GET + 200 + + + + ${project.build.directory}/elasticsearch-port.properties + + + - + + + remove-existing-container + pre-integration-test + + stop + remove + + + start-elasticsearch pre-integration-test - runforked + start + + true + stop-elasticsearch diff --git a/pom.xml b/pom.xml index 25360797d..f5c1c2aa5 100644 --- a/pom.xml +++ b/pom.xml @@ -146,6 +146,7 @@ 3.21.0 0.16.1 1.0-m5.1 + 0.48.0 v16.20.2 v1.22.19 @@ -865,6 +866,11 @@ dependency-check-maven ${dependency-check.plugin.version} + + io.fabric8 + docker-maven-plugin + ${docker-maven-plugin.version} + From 944c39bdf91238e937eac739c532eb7083e39db9 Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Sat, 23 May 2026 10:31:07 +0200 Subject: [PATCH 2/7] UNOMI-937: Add build.sh CI mode for non-interactive GitHub Actions Non-interactive prompts when CI, GITHUB_ACTIONS, or BUILD_NON_INTERACTIVE is set. Add --ci (no Karaf, no Maven build cache) and MAVEN_EXTRA_OPTS for matrix ports. --- build.sh | 48 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/build.sh b/build.sh index 19dd5d0f6..80435acc8 100755 --- a/build.sh +++ b/build.sh @@ -222,13 +222,23 @@ print_progress() { fi } +# Non-interactive when run from CI or when explicitly requested (e.g. GitHub Actions). +is_non_interactive() { + [ -n "${CI:-}" ] || [ -n "${GITHUB_ACTIONS:-}" ] || [ "${BUILD_NON_INTERACTIVE:-}" = "true" ] +} + # Function to prompt for continuation prompt_continue() { local prompt_text="$1" if [ -z "$prompt_text" ]; then prompt_text="Continue?" fi - + + if is_non_interactive; then + print_status "info" "Non-interactive mode: continuing ($prompt_text)" + return 0 + fi + read -p "$prompt_text (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then @@ -296,6 +306,7 @@ EOF echo -e " ${CYAN}--it-debug-port PORT${NC} Set integration test debug port" echo -e " ${CYAN}--it-debug-suspend${NC} Suspend integration test until debugger connects" echo -e " ${CYAN}--skip-migration-tests${NC} Skip migration-related tests" + echo -e " ${CYAN}--ci${NC} CI mode: no Karaf, no Maven build cache, non-interactive" else cat << "EOF" _ _ _____ _ ____ @@ -329,6 +340,7 @@ EOF echo " --it-debug-port PORT Set integration test debug port" echo " --it-debug-suspend Suspend integration test until debugger connects" echo " --skip-migration-tests Skip migration-related tests" + echo " --ci CI mode: no Karaf, no Maven build cache, non-interactive" fi echo @@ -459,6 +471,11 @@ while [ "$1" != "" ]; do --skip-migration-tests) SKIP_MIGRATION_TESTS=true ;; + --ci) + NO_KARAF=true + USE_MAVEN_CACHE=false + BUILD_NON_INTERACTIVE=true + ;; *) echo "Unknown option: $1" usage @@ -784,10 +801,14 @@ if [ "$MAVEN_OFFLINE" = true ]; then # Warn if purge cache is enabled with offline mode if [ "$PURGE_MAVEN_CACHE" = true ]; then echo "Warning: Purging Maven cache while in offline mode may cause build failures" - read -p "Continue anyway? (y/N) " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 + if is_non_interactive; then + print_status "warning" "Non-interactive mode: continuing despite purge + offline" + else + read -p "Continue anyway? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi fi fi fi @@ -797,13 +818,22 @@ if [ "$USE_MAVEN_CACHE" = false ]; then MVN_OPTS="$MVN_OPTS -Dmaven.build.cache.enabled=false" fi +# Extra Maven options (e.g. CI matrix ports: -Delasticsearch.port=9400) +if [ -n "${MAVEN_EXTRA_OPTS:-}" ]; then + MVN_OPTS="$MVN_OPTS $MAVEN_EXTRA_OPTS" +fi + # Verify Maven settings if [ ! -f ~/.m2/settings.xml ]; then echo "Warning: Maven settings.xml not found at ~/.m2/settings.xml" - read -p "Continue anyway? (y/N) " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 + if is_non_interactive; then + print_status "info" "Non-interactive mode: continuing without ~/.m2/settings.xml" + else + read -p "Continue anyway? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi fi fi From eb294da69a3e5b28cd31cc75c2f6a260350b4ba0 Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Sat, 23 May 2026 10:32:18 +0200 Subject: [PATCH 3/7] UNOMI-937: Harden flaky integration tests with keepTrying polling Replace fixed sleeps in GraphQLListIT and poll after patch refresh in PatchIT. --- .../java/org/apache/unomi/itests/PatchIT.java | 24 ++++++++++++----- .../unomi/itests/graphql/GraphQLListIT.java | 26 ++++++++++++------- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/itests/src/test/java/org/apache/unomi/itests/PatchIT.java b/itests/src/test/java/org/apache/unomi/itests/PatchIT.java index 098a03ad2..1c9cd5a43 100644 --- a/itests/src/test/java/org/apache/unomi/itests/PatchIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/PatchIT.java @@ -49,7 +49,10 @@ public void testPatch() throws IOException { profileService.refresh(); - newCompany = profileService.getPropertyType("company"); + newCompany = keepTrying("Failed waiting for patched property type", + () -> profileService.getPropertyType("company"), + pt -> pt != null && "foo".equals(pt.getDefaultValue()), + DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); Assert.assertEquals("foo", newCompany.getDefaultValue()); } finally { profileService.setPropertyType(company); @@ -68,7 +71,10 @@ public void testOverride() throws IOException { profileService.refresh(); - newGender = profileService.getPropertyType("gender"); + newGender = keepTrying("Failed waiting for patched property type", + () -> profileService.getPropertyType("gender"), + pt -> pt != null && "foo".equals(pt.getDefaultValue()), + DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); Assert.assertEquals("foo", newGender.getDefaultValue()); } finally { profileService.setPropertyType(gender); @@ -86,8 +92,8 @@ public void testRemove() throws IOException, InterruptedException { profileService.refresh(); - PropertyType newIncome = profileService.getPropertyType("income"); - Assert.assertNull(newIncome); + waitForNullValue("Failed waiting for property type removal", + () -> profileService.getPropertyType("income"), DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); } finally { profileService.setPropertyType(income); } @@ -105,7 +111,10 @@ public void testPatchOnConditionType() throws IOException, InterruptedException definitionsService.refresh(); - ConditionType newFormCondition = definitionsService.getConditionType("formEventCondition"); + ConditionType newFormCondition = keepTrying("Failed waiting for patched condition type", + () -> definitionsService.getConditionType("formEventCondition"), + ct -> ct != null && !ct.getMetadata().getSystemTags().contains("profileTags"), + DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); Assert.assertFalse(newFormCondition.getMetadata().getSystemTags().contains("profileTags")); } finally { definitionsService.setConditionType(formCondition); @@ -124,7 +133,10 @@ public void testPatchOnActionType() throws IOException, InterruptedException { definitionsService.refresh(); - ActionType newMailAction = definitionsService.getActionType("sendMailAction"); + ActionType newMailAction = keepTrying("Failed waiting for patched action type", + () -> definitionsService.getActionType("sendMailAction"), + at -> at != null && !at.getMetadata().getSystemTags().contains("availableToEndUser"), + DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); Assert.assertFalse(newMailAction.getMetadata().getSystemTags().contains("availableToEndUser")); } finally { definitionsService.setActionType(mailAction); diff --git a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java index 96a6eb986..757904b72 100644 --- a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java @@ -77,15 +77,23 @@ public void testCRUD() throws Exception { refreshPersistence(UserList.class); - Thread.sleep(6000); - - try (CloseableHttpResponse response = post("graphql/list/find-lists.json")) { - final ResponseContext context = ResponseContext.parse(response.getEntity()); - - Assert.assertEquals(1, ((Integer) context.getValue("data.cdp.findLists.totalCount")).intValue()); - Assert.assertEquals("testListId", context.getValue("data.cdp.findLists.edges[0].node.id")); - Assert.assertEquals(profile.getItemId(), context.getValue("data.cdp.findLists.edges[0].node.active.edges[0].node.cdp_profileIDs[0].id")); - } + final ResponseContext findListsContext = keepTrying("Failed waiting for profile in list query", + () -> { + try (CloseableHttpResponse response = post("graphql/list/find-lists.json")) { + return ResponseContext.parse(response.getEntity()); + } + }, + context -> { + Integer totalCount = (Integer) context.getValue("data.cdp.findLists.totalCount"); + if (totalCount == null || totalCount != 1) { + return false; + } + Object profileId = context.getValue("data.cdp.findLists.edges[0].node.active.edges[0].node.cdp_profileIDs[0].id"); + return profile.getItemId().equals(profileId); + }, + DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); + + Assert.assertEquals("testListId", findListsContext.getValue("data.cdp.findLists.edges[0].node.id")); try (CloseableHttpResponse response = post("graphql/list/delete-list.json")) { final ResponseContext context = ResponseContext.parse(response.getEntity()); From c2f30010c6a1701f331eac783bde4ee1dc56df41 Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Sat, 23 May 2026 10:32:18 +0200 Subject: [PATCH 4/7] UNOMI-937: Add waitForProfileProperty helper for async rule updates Poll profile properties after events in PropertiesUpdateActionIT. --- .../test/java/org/apache/unomi/itests/BaseIT.java | 8 ++++++++ .../unomi/itests/PropertiesUpdateActionIT.java | 14 +++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java index 1c1bd6f8c..676639b0d 100644 --- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java @@ -467,6 +467,14 @@ protected T keepTrying(String failMessage, Supplier call, Predicate pr return value; } + protected void waitForProfileProperty(String profileId, String propertyName, Object expected) + throws InterruptedException { + keepTrying("Profile " + profileId + " property " + propertyName + " not updated", + () -> profileService.load(profileId), + profile -> profile != null && java.util.Objects.equals(expected, profile.getProperty(propertyName)), + DEFAULT_TRYING_TIMEOUT, DEFAULT_TRYING_TRIES); + } + protected void waitForNullValue(String failMessage, Supplier call, int timeout, int retries) throws InterruptedException { int count = 0; while (call.get() != null) { diff --git a/itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java b/itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java index a1e340680..e07d2bd5c 100644 --- a/itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java @@ -78,7 +78,7 @@ public void setUp() throws InterruptedException { } @Test - public void testUpdateProperties_CurrentProfile() { + public void testUpdateProperties_CurrentProfile() throws InterruptedException { Profile profile = profileService.load(PROFILE_TARGET_TEST_ID); Assert.assertNull(profile.getProperty("firstName")); @@ -92,16 +92,13 @@ public void testUpdateProperties_CurrentProfile() { updateProperties.setProperty(UpdatePropertiesAction.TARGET_ID_KEY, PROFILE_TARGET_TEST_ID); updateProperties.setProperty(UpdatePropertiesAction.TARGET_TYPE_KEY, "profile"); - int changes = eventService.send(updateProperties); - - LOGGER.info("Changes of the event : {}", changes); + eventService.send(updateProperties); - Assert.assertTrue(changes > 0); - Assert.assertEquals("UPDATED FIRST NAME CURRENT PROFILE", profile.getProperty("firstName")); + waitForProfileProperty(PROFILE_TARGET_TEST_ID, "firstName", "UPDATED FIRST NAME CURRENT PROFILE"); } @Test - public void testUpdateProperties_NotCurrentProfile() { + public void testUpdateProperties_NotCurrentProfile() throws InterruptedException { Profile profile = profileService.load(PROFILE_TARGET_TEST_ID); Profile profileToUpdate = profileService.load(PROFILE_TEST_ID); Assert.assertNull(profileToUpdate.getProperty("firstName")); @@ -117,8 +114,7 @@ public void testUpdateProperties_NotCurrentProfile() { updateProperties.setProperty(UpdatePropertiesAction.TARGET_TYPE_KEY, "profile"); eventService.send(updateProperties); - profileToUpdate = profileService.load(PROFILE_TEST_ID); - Assert.assertEquals("UPDATED FIRST NAME", profileToUpdate.getProperty("firstName")); + waitForProfileProperty(PROFILE_TEST_ID, "firstName", "UPDATED FIRST NAME"); } @Test From 2611dea64f026ea2897bf5c9ef7235d3407f4e28 Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Sat, 23 May 2026 10:32:18 +0200 Subject: [PATCH 5/7] UNOMI-937: Run GitHub Actions unit and IT jobs via build.sh Align CI with local developer workflow; pass matrix ports via MAVEN_EXTRA_OPTS. --- .github/workflows/unomi-ci-build-tests.yml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/unomi-ci-build-tests.yml b/.github/workflows/unomi-ci-build-tests.yml index e629253cd..2533a8b23 100644 --- a/.github/workflows/unomi-ci-build-tests.yml +++ b/.github/workflows/unomi-ci-build-tests.yml @@ -31,7 +31,7 @@ jobs: sudo apt-get install -y graphviz dot -V - name: Build and Unit tests - run: mvn -U -ntp -e clean install + run: ./build.sh --ci integration-tests: name: Execute integration tests @@ -54,16 +54,22 @@ jobs: distribution: 'temurin' java-version: '17' cache: 'maven' + - name: Install GraphViz + run: | + sudo apt-get update + sudo apt-get install -y graphviz + dot -V - name: Integration tests + env: + MAVEN_EXTRA_OPTS: >- + -Dopensearch.port=${{ matrix.port }} + -Delasticsearch.port=${{ matrix.port }} run: | - FLAGS="-Pintegration-tests" if [ "${{ matrix.search-engine }}" = "opensearch" ]; then - # Trigger OpenSearch profile activation via property; do not pass any -P profile toggles - FLAGS="$FLAGS -Duse.opensearch=true" + ./build.sh --ci --integration-tests --use-opensearch + else + ./build.sh --ci --integration-tests fi - mvn -ntp clean install $FLAGS \ - -Dopensearch.port=${{ matrix.port }} \ - -Delasticsearch.port=${{ matrix.port }} - name: Archive code coverage logs uses: actions/upload-artifact@v4 if: false # UNOMI-746 Reactivate if necessary From 88da78ad2be9f541b480e423a72021b85778e1b0 Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Mon, 18 May 2026 15:04:49 +0200 Subject: [PATCH 6/7] UNOMI-921: Replace elasticsearch-maven-plugin with Docker-based Elasticsearch in integration tests Implements the core configuration switch from UNOMI-921: https://issues.apache.org/jira/browse/UNOMI-921 Replace the com.github.alexcojocaru:elasticsearch-maven-plugin (binary download + forked JVM) with io.fabric8:docker-maven-plugin in the elasticsearch profile of itests, mirroring how the opensearch profile already runs OpenSearch in a Docker container. itests/pom.xml (elasticsearch profile) * Add an 9400 property and pass it through the failsafe systemPropertyVariables so tests resolve the HTTP port from a single source (unchanged from the previous 9400). * Replace the elasticsearch-maven-plugin block with a docker-maven-plugin block that runs docker.elastic.co/elasticsearch/elasticsearch:${elasticsearch.test.version}, binds target/snapshots_repository to /tmp/snapshots_repository, and waits on the HTTP port before the integration-test phase. Heap aligned to 8GB (-Xms8g -Xmx8g) to match the OpenSearch configuration and the ES 9 recommendation. Discovery=single-node, replicas=0, xpack.ml and xpack.security disabled. * Container lifecycle matches OpenSearch exactly: pre-integration-test runs stop+remove then start (with showLogs); post-integration-test runs stop only -- container is kept around for inspection. * Add a chmod -R ugo+rwx on snapshots_repository in the antrun unzip step: the ES container runs as UID 1000, so on Linux CI the bind-mounted snapshot repo otherwise hits access_denied during repository verify. pom.xml (root) * Declare 0.48.0 and add the pluginManagement entry so the elasticsearch profile (and any future user of the plugin) inherits a single version. Scope kept minimal for the PR #757 stack split: only the test infrastructure switch lives here. The follow-up UNOMI-921 acceptance items below ship in the platform PR (P) once it lands: * Remove BaseIT.fixDefaultTemplateIfNeeded() and the call in checkSearchEngine() (no longer needed with Docker). * Migrate16xToCurrentVersionIT: replace hardcoded ES_BASE_URL = "http://localhost:9400" with dynamic getSearchPort(). * Drop the comments referring to the elasticsearch-maven-plugin template-override workaround. See docs/PR-757-stack-extraction-tracker.md for the full split plan and how this PR fits in the stack. --- itests/pom.xml | 91 ++++++++++++++++++++++++++++++++++---------------- pom.xml | 6 ++++ 2 files changed, 68 insertions(+), 29 deletions(-) diff --git a/itests/pom.xml b/itests/pom.xml index 98e98e9e5..0d0ccde8c 100644 --- a/itests/pom.xml +++ b/itests/pom.xml @@ -196,6 +196,14 @@ + + + + + + @@ -210,6 +218,9 @@ elasticsearch + + 9400 + true @@ -229,6 +240,7 @@ foo elasticsearch + ${elasticsearch.port} @@ -247,43 +259,64 @@ - com.github.alexcojocaru - elasticsearch-maven-plugin - - 6.29 + io.fabric8 + docker-maven-plugin - - contextElasticSearchITests - 9500 - 9400 - ${elasticsearch.test.version} - true - 120 - - -Xms4g -Xmx4g - - - - false - ${project.build.directory}/snapshots_repository - false - OPTIONS,HEAD,GET,POST,PUT,DELETE - Authorization,X-Requested-With,X-Auth-Token,Content-Type,Content-Length - - + itests-elasticsearch + + + docker.elastic.co/elasticsearch/elasticsearch:${elasticsearch.test.version} + elasticsearch + + + ${elasticsearch.port}:9200 + + + single-node + -Xms8g -Xmx8g -Dcluster.default.index.settings.number_of_replicas=0 + false + false + /tmp/snapshots_repository + false + + + + ${project.build.directory}/snapshots_repository:/tmp/snapshots_repository + + + + + http://localhost:${elasticsearch.port} + GET + 200 + + + + ${project.build.directory}/elasticsearch-port.properties + + + - + + + remove-existing-container + pre-integration-test + + stop + remove + + + start-elasticsearch pre-integration-test - runforked + start + + true + stop-elasticsearch diff --git a/pom.xml b/pom.xml index 174e68308..f7e85f403 100644 --- a/pom.xml +++ b/pom.xml @@ -146,6 +146,7 @@ 3.21.0 0.16.1 1.0-m5.1 + 0.48.0 v16.20.2 v1.22.19 @@ -867,6 +868,11 @@ dependency-check-maven ${dependency-check.plugin.version} + + io.fabric8 + docker-maven-plugin + ${docker-maven-plugin.version} + From 7a3db7709f01fb8881720813692efe69423b31b1 Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Fri, 29 May 2026 08:55:55 +0200 Subject: [PATCH 7/7] UNOMI-921: address Copilot review comments and add --keep-container flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Make ES heap configurable via ${elasticsearch.heap} (default 4g, aligns with OpenSearch) - Remove -Dcluster.default.index.settings.number_of_replicas=0 from JAVA_OPTS (was a JVM property, not an ES/OS setting — had no effect) - Add it.keepContainer Maven property + --keep-container flag in build.sh to keep the search engine container alive after tests for inspection - Add missing stop-opensearch post-integration-test execution (symmetry with ES) - Add ignoreRunningContainers to both pre-start cleanup executions - Restore GraphQLListIT catch block and null-guard for polling resilience - PropertiesUpdateActionIT: keep in-memory assertion for current-profile path --- build.sh | 23 +++++++---- itests/pom.xml | 38 +++++++++++++++---- .../itests/PropertiesUpdateActionIT.java | 9 +++-- .../unomi/itests/graphql/GraphQLListIT.java | 10 +++++ 4 files changed, 60 insertions(+), 20 deletions(-) diff --git a/build.sh b/build.sh index 80435acc8..94891527d 100755 --- a/build.sh +++ b/build.sh @@ -19,15 +19,13 @@ ################################################################################ set -e # Exit on error -trap 'handle_error $? $LINENO $BASH_LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]:-})' ERR +trap 'handle_error $? $LINENO "${BASH_COMMAND:0:200}"' ERR # Error handling function handle_error() { local exit_code=$1 local line_no=$2 - local bash_lineno=$3 - local last_command=$4 - local func_trace=$5 + local failed_cmd=$3 cat << "EOF" _____ ____ ____ ___ ____ @@ -38,12 +36,9 @@ handle_error() { EOF echo "Error occurred in:" - echo " Command: $last_command" + echo " Command: $failed_cmd" echo " Line: $line_no" echo " Exit code: $exit_code" - if [ ! -z "$func_trace" ]; then - echo " Function trace: $func_trace" - fi exit $exit_code } @@ -268,6 +263,7 @@ IT_DEBUG=false IT_DEBUG_PORT=5006 IT_DEBUG_SUSPEND=false SKIP_MIGRATION_TESTS=false +KEEP_CONTAINER=false # Enhanced usage function with color support usage() { @@ -306,6 +302,7 @@ EOF echo -e " ${CYAN}--it-debug-port PORT${NC} Set integration test debug port" echo -e " ${CYAN}--it-debug-suspend${NC} Suspend integration test until debugger connects" echo -e " ${CYAN}--skip-migration-tests${NC} Skip migration-related tests" + echo -e " ${CYAN}--keep-container${NC} Keep search engine container running after tests (for post-failure inspection)" echo -e " ${CYAN}--ci${NC} CI mode: no Karaf, no Maven build cache, non-interactive" else cat << "EOF" @@ -340,6 +337,7 @@ EOF echo " --it-debug-port PORT Set integration test debug port" echo " --it-debug-suspend Suspend integration test until debugger connects" echo " --skip-migration-tests Skip migration-related tests" + echo " --keep-container Keep search engine container running after tests (for post-failure inspection)" echo " --ci CI mode: no Karaf, no Maven build cache, non-interactive" fi @@ -471,6 +469,9 @@ while [ "$1" != "" ]; do --skip-migration-tests) SKIP_MIGRATION_TESTS=true ;; + --keep-container) + KEEP_CONTAINER=true + ;; --ci) NO_KARAF=true USE_MAVEN_CACHE=false @@ -872,6 +873,12 @@ if [ "$RUN_INTEGRATION_TESTS" = true ]; then MVN_OPTS="$MVN_OPTS -Dit.test.exclude.pattern=**/migration/**/*IT.java" echo "Skipping migration tests" fi + + # Keep container running after tests if requested + if [ "$KEEP_CONTAINER" = true ]; then + MVN_OPTS="$MVN_OPTS -Dit.keepContainer=true" + echo "Search engine container will be kept running after tests" + fi else if [ "$SKIP_TESTS" = true ]; then PROFILES="$PROFILES,!integration-tests,!run-tests" diff --git a/itests/pom.xml b/itests/pom.xml index 0d0ccde8c..239ef3015 100644 --- a/itests/pom.xml +++ b/itests/pom.xml @@ -32,6 +32,8 @@ elasticsearch false itests-opensearch + + false @@ -220,6 +222,7 @@ elasticsearch 9400 + 4g true @@ -273,7 +276,7 @@ single-node - -Xms8g -Xmx8g -Dcluster.default.index.settings.number_of_replicas=0 + -Xms${elasticsearch.heap} -Xmx${elasticsearch.heap} false false /tmp/snapshots_repository @@ -298,13 +301,16 @@ - + remove-existing-container pre-integration-test + + true + - stop - remove + stop + remove @@ -321,6 +327,9 @@ stop-elasticsearch post-integration-test + + ${it.keepContainer} + stop @@ -387,7 +396,7 @@ single-node - -Xms4g -Xmx4g -Dcluster.default.index.settings.number_of_replicas=0 + -Xms4g -Xmx4g /tmp/snapshots_repository true Unomi.1ntegrat10n.Tests @@ -411,13 +420,16 @@ - + remove-existing-container pre-integration-test + + true + - stop - remove + stop + remove @@ -431,6 +443,16 @@ true + + stop-opensearch + post-integration-test + + ${it.keepContainer} + + + stop + + diff --git a/itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java b/itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java index e07d2bd5c..66f1de7fc 100644 --- a/itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/PropertiesUpdateActionIT.java @@ -78,7 +78,7 @@ public void setUp() throws InterruptedException { } @Test - public void testUpdateProperties_CurrentProfile() throws InterruptedException { + public void testUpdateProperties_CurrentProfile() { Profile profile = profileService.load(PROFILE_TARGET_TEST_ID); Assert.assertNull(profile.getProperty("firstName")); @@ -92,9 +92,10 @@ public void testUpdateProperties_CurrentProfile() throws InterruptedException { updateProperties.setProperty(UpdatePropertiesAction.TARGET_ID_KEY, PROFILE_TARGET_TEST_ID); updateProperties.setProperty(UpdatePropertiesAction.TARGET_TYPE_KEY, "profile"); - eventService.send(updateProperties); - - waitForProfileProperty(PROFILE_TARGET_TEST_ID, "firstName", "UPDATED FIRST NAME CURRENT PROFILE"); + int changes = eventService.send(updateProperties); + Assert.assertTrue("eventService.send() reported no changes — action may not have fired", changes > 0); + // Current profile on the event is updated in memory; do not poll persistence here. + Assert.assertEquals("UPDATED FIRST NAME CURRENT PROFILE", profile.getProperty("firstName")); } @Test diff --git a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java index 757904b72..60a9a8507 100644 --- a/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/graphql/GraphQLListIT.java @@ -21,10 +21,14 @@ import org.apache.unomi.lists.UserList; import org.junit.Assert; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Objects; public class GraphQLListIT extends BaseGraphQLIT { + private static final Logger LOGGER = LoggerFactory.getLogger(GraphQLListIT.class); + @Test public void testCRUD() throws Exception { Profile persistedProfile = null; @@ -81,9 +85,15 @@ public void testCRUD() throws Exception { () -> { try (CloseableHttpResponse response = post("graphql/list/find-lists.json")) { return ResponseContext.parse(response.getEntity()); + } catch (Exception e) { + LOGGER.warn("find-lists poll attempt failed: {}", e.getMessage()); + return null; } }, context -> { + if (context == null) { + return false; + } Integer totalCount = (Integer) context.getValue("data.cdp.findLists.totalCount"); if (totalCount == null || totalCount != 1) { return false;