diff --git a/bridgeService-data/pom.xml b/bridgeService-data/pom.xml
index e67542a..602736a 100644
--- a/bridgeService-data/pom.xml
+++ b/bridgeService-data/pom.xml
@@ -18,6 +18,7 @@
Test Library for the Bridge Service
true
+ true
diff --git a/bridgeService-test-injection/pom.xml b/bridgeService-test-injection/pom.xml
new file mode 100644
index 0000000..f28cd47
--- /dev/null
+++ b/bridgeService-test-injection/pom.xml
@@ -0,0 +1,83 @@
+
+
+
+ 4.0.0
+
+ com.adobe.campaign.tests.bridge
+ bridgeService-test-injection
+ ${project.groupId}:${project.artifactId}
+ Injection model E2E test harness for the Bridge Service
+
+ true
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.5
+
+
+ src/test/resources/testng.xml
+
+
+
+
+
+
+
+
+ com.github.therapi
+ therapi-runtime-javadoc-scribe
+ 0.15.0
+ provided
+
+
+
+ com.adobe.campaign.tests.bridge.testdata
+ bridgeService-data
+ ${project.parent.version}
+
+
+
+ com.adobe.campaign.tests.bridge.service
+ integroBridgeService
+ ${project.parent.version}
+ test
+
+
+ org.testng
+ testng
+ 7.12.0
+ test
+
+
+ io.rest-assured
+ rest-assured
+ 5.5.7
+ test
+
+
+ io.rest-assured
+ json-path
+ 5.5.7
+ test
+
+
+
+ com.adobe.campaign.tests.bridge
+ parent
+ 3.11.4-SNAPSHOT
+
+
diff --git a/bridgeService-test-injection/src/main/java/com/adobe/campaign/tests/bridge/dependency/caller/DepCaller.java b/bridgeService-test-injection/src/main/java/com/adobe/campaign/tests/bridge/dependency/caller/DepCaller.java
new file mode 100644
index 0000000..d509241
--- /dev/null
+++ b/bridgeService-test-injection/src/main/java/com/adobe/campaign/tests/bridge/dependency/caller/DepCaller.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2022 Adobe
+ * All Rights Reserved.
+ *
+ * NOTICE: Adobe permits you to use, modify, and distribute this file in
+ * accordance with the terms of the Adobe license agreement accompanying
+ * it.
+ */
+package com.adobe.campaign.tests.bridge.dependency.caller;
+
+import com.adobe.campaign.tests.bridge.dependency.factory.DepFactory;
+import com.adobe.campaign.tests.bridge.dependency.model.DepResult;
+
+/**
+ * Simulates project code that calls a dependency library in a static initializer.
+ * When this class's package and DepResult's package are in STATIC_INTEGRITY_PACKAGES
+ * but DepFactory's package is not, both the IBS classloader and the parent classloader
+ * end up loading DepResult, which causes a LinkageError.
+ */
+public class DepCaller {
+
+ private final static DepResult instantiatedStaticConstant = DepFactory.makeDepResult("initial");
+
+ /**
+ * Returns a fixed string, serving as the method to invoke via the /call endpoint in tests.
+ *
+ * @return a confirmation string
+ */
+ public static String doSomething() {
+ return instantiatedStaticConstant.getValue();
+ }
+}
diff --git a/bridgeService-test-injection/src/main/java/com/adobe/campaign/tests/bridge/dependency/factory/DepFactory.java b/bridgeService-test-injection/src/main/java/com/adobe/campaign/tests/bridge/dependency/factory/DepFactory.java
new file mode 100644
index 0000000..315da61
--- /dev/null
+++ b/bridgeService-test-injection/src/main/java/com/adobe/campaign/tests/bridge/dependency/factory/DepFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 Adobe
+ * All Rights Reserved.
+ *
+ * NOTICE: Adobe permits you to use, modify, and distribute this file in
+ * accordance with the terms of the Adobe license agreement accompanying
+ * it.
+ */
+package com.adobe.campaign.tests.bridge.dependency.factory;
+
+import com.adobe.campaign.tests.bridge.dependency.model.DepResult;
+import com.adobe.campaign.tests.bridge.testdata.issue34.pckg1.MiddleMan;
+
+/**
+ * Simulated dependency library factory.
+ * Lives in a separate package from DepResult to enable the split-package classloader conflict scenario:
+ * when DepResult's package is in STATIC_INTEGRITY_PACKAGES but this factory's package is not,
+ * parent classloader loads DepResult a second time (as this method's return type), causing a LinkageError.
+ */
+public class DepFactory {
+
+ /**
+ * Creates a DepResult instance.
+ *
+ * @param in_value the string value to embed in the result
+ * @return a new DepResult
+ */
+ public static DepResult makeDepResult(String in_value) {
+ return new DepResult(in_value);
+ }
+
+ /**
+ * Creates a MiddleMan instance from the project (bridgeService-data) test data.
+ * Used to demonstrate that a dependency library can return project types when
+ * both packages are included in STATIC_INTEGRITY_PACKAGES.
+ *
+ * @return a new MiddleMan instance
+ */
+ public static MiddleMan makeMiddleMan() {
+ return new MiddleMan();
+ }
+}
diff --git a/bridgeService-test-injection/src/main/java/com/adobe/campaign/tests/bridge/dependency/model/DepResult.java b/bridgeService-test-injection/src/main/java/com/adobe/campaign/tests/bridge/dependency/model/DepResult.java
new file mode 100644
index 0000000..82303a3
--- /dev/null
+++ b/bridgeService-test-injection/src/main/java/com/adobe/campaign/tests/bridge/dependency/model/DepResult.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 Adobe
+ * All Rights Reserved.
+ *
+ * NOTICE: Adobe permits you to use, modify, and distribute this file in
+ * accordance with the terms of the Adobe license agreement accompanying
+ * it.
+ */
+package com.adobe.campaign.tests.bridge.dependency.model;
+
+/**
+ * A result type produced by the simulated dependency library.
+ * Used by the injection model tests to trigger and verify classloader isolation behaviour.
+ */
+public class DepResult {
+
+ private String value;
+
+ public DepResult(String in_value) {
+ this.value = in_value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/bridgeService-test-injection/src/test/java/com/adobe/campaign/tests/bridge/dependency/InjectionModelE2ETests.java b/bridgeService-test-injection/src/test/java/com/adobe/campaign/tests/bridge/dependency/InjectionModelE2ETests.java
new file mode 100644
index 0000000..992045c
--- /dev/null
+++ b/bridgeService-test-injection/src/test/java/com/adobe/campaign/tests/bridge/dependency/InjectionModelE2ETests.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2022 Adobe
+ * All Rights Reserved.
+ *
+ * NOTICE: Adobe permits you to use, modify, and distribute this file in
+ * accordance with the terms of the Adobe license agreement accompanying
+ * it.
+ */
+package com.adobe.campaign.tests.bridge.dependency;
+
+import com.adobe.campaign.tests.bridge.service.CallContent;
+import com.adobe.campaign.tests.bridge.service.ConfigValueHandlerIBS;
+import com.adobe.campaign.tests.bridge.service.IntegroAPI;
+import com.adobe.campaign.tests.bridge.service.JavaCalls;
+import com.adobe.campaign.tests.bridge.service.exceptions.IBSConfigurationException;
+import com.adobe.campaign.tests.bridge.dependency.caller.DepCaller;
+import com.adobe.campaign.tests.bridge.dependency.factory.DepFactory;
+import io.javalin.Javalin;
+import org.hamcrest.Matchers;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static io.restassured.RestAssured.given;
+
+/**
+ * E2E tests for the injection model (IBS.CLASSLOADER.STATIC.INTEGRITY.PACKAGES).
+ * Demonstrates that dependency library packages can and must be listed alongside
+ * project packages to avoid LinkageError when the library's factory returns a type
+ * that is also loaded by the IBS classloader.
+ */
+public class InjectionModelE2ETests {
+
+ private static final String END_POINT_URL = "http://localhost:8080/";
+
+ private static final String CALLER_PACKAGE =
+ "com.adobe.campaign.tests.bridge.dependency.caller.";
+ private static final String MODEL_PACKAGE =
+ "com.adobe.campaign.tests.bridge.dependency.model.";
+ private static final String FACTORY_PACKAGE =
+ "com.adobe.campaign.tests.bridge.dependency.factory.";
+
+ private Javalin app;
+
+ @BeforeGroups(groups = "E2E")
+ public void startUpService() {
+ app = IntegroAPI.startServices(8080);
+ }
+
+ @BeforeMethod
+ public void cleanCache() {
+ ConfigValueHandlerIBS.resetAllValues();
+ }
+
+ @AfterGroups(groups = "E2E", alwaysRun = true)
+ public void tearDown() {
+ ConfigValueHandlerIBS.resetAllValues();
+ app.stop();
+ }
+
+ /**
+ * Negative test: when DepResult's package IS in STATIC_INTEGRITY_PACKAGES but
+ * DepFactory's package is NOT, the IBS classloader and the parent classloader both
+ * load DepResult independently, producing a LinkageError.
+ */
+ @Test(groups = "E2E")
+ public void testInjectionConflict_missingFactoryPackage() {
+ ConfigValueHandlerIBS.INTEGRITY_PACKAGE_INJECTION_MODE.activate("manual");
+ ConfigValueHandlerIBS.STATIC_INTEGRITY_PACKAGES.activate(
+ MODEL_PACKAGE + "," + CALLER_PACKAGE);
+
+ JavaCalls l_myJavaCalls = new JavaCalls();
+ CallContent l_cc = new CallContent();
+ l_cc.setClassName(DepCaller.class.getTypeName());
+ l_cc.setMethodName("doSomething");
+ l_myJavaCalls.getCallContent().put("call1", l_cc);
+
+ given().body(l_myJavaCalls).post(END_POINT_URL + "call").then()
+ .assertThat().statusCode(500)
+ .body("title", Matchers.equalTo(
+ "The provided class and method for setting environment variables is not valid."))
+ .body("code", Matchers.equalTo(500))
+ .body("detail", Matchers.startsWith("Linkage Error detected"))
+ .body("bridgeServiceException",
+ Matchers.equalTo(IBSConfigurationException.class.getTypeName()))
+ .body("originalException",
+ Matchers.equalTo(LinkageError.class.getTypeName()));
+ }
+
+ /**
+ * Positive test: adding DepFactory's package to STATIC_INTEGRITY_PACKAGES ensures all
+ * three packages are loaded by the same IBS classloader, eliminating the type mismatch.
+ */
+ @Test(groups = "E2E")
+ public void testInjectionConflict_allPackagesPresent() {
+ ConfigValueHandlerIBS.INTEGRITY_PACKAGE_INJECTION_MODE.activate("manual");
+ ConfigValueHandlerIBS.STATIC_INTEGRITY_PACKAGES.activate(
+ MODEL_PACKAGE + "," + CALLER_PACKAGE + "," + FACTORY_PACKAGE);
+
+ JavaCalls l_myJavaCalls = new JavaCalls();
+ CallContent l_cc = new CallContent();
+ l_cc.setClassName(DepCaller.class.getTypeName());
+ l_cc.setMethodName("doSomething");
+ l_myJavaCalls.getCallContent().put("call1", l_cc);
+
+ given().body(l_myJavaCalls).post(END_POINT_URL + "call").then()
+ .assertThat().statusCode(200)
+ .body("returnValues.call1", Matchers.equalTo("initial"));
+ }
+
+ /**
+ * Cross-module type test: DepFactory.makeMiddleMan() returns a MiddleMan from bridgeService-data.
+ * When the factory package is in STATIC_INTEGRITY_PACKAGES, the call succeeds and the
+ * MiddleMan object is serialised correctly by the BridgeService response layer.
+ */
+ @Test(groups = "E2E")
+ public void testDepFactoryReturnsBridgeDataType() {
+ ConfigValueHandlerIBS.INTEGRITY_PACKAGE_INJECTION_MODE.activate("manual");
+ ConfigValueHandlerIBS.STATIC_INTEGRITY_PACKAGES.activate(FACTORY_PACKAGE);
+
+ JavaCalls l_myJavaCalls = new JavaCalls();
+ CallContent l_cc = new CallContent();
+ l_cc.setClassName(DepFactory.class.getTypeName());
+ l_cc.setMethodName("makeMiddleMan");
+ l_myJavaCalls.getCallContent().put("call1", l_cc);
+
+ given().body(l_myJavaCalls).post(END_POINT_URL + "call").then()
+ .assertThat().statusCode(200);
+ }
+}
diff --git a/bridgeService-test-injection/src/test/java/com/adobe/campaign/tests/bridge/dependency/InjectionModelMCPTests.java b/bridgeService-test-injection/src/test/java/com/adobe/campaign/tests/bridge/dependency/InjectionModelMCPTests.java
new file mode 100644
index 0000000..91de69e
--- /dev/null
+++ b/bridgeService-test-injection/src/test/java/com/adobe/campaign/tests/bridge/dependency/InjectionModelMCPTests.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2022 Adobe
+ * All Rights Reserved.
+ *
+ * NOTICE: Adobe permits you to use, modify, and distribute this file in
+ * accordance with the terms of the Adobe license agreement accompanying
+ * it.
+ */
+package com.adobe.campaign.tests.bridge.dependency;
+
+import com.adobe.campaign.tests.bridge.dependency.factory.DepFactory;
+import com.adobe.campaign.tests.bridge.service.CallContent;
+import com.adobe.campaign.tests.bridge.service.ConfigValueHandlerIBS;
+import com.adobe.campaign.tests.bridge.service.IntegroAPI;
+import com.adobe.campaign.tests.bridge.service.JavaCalls;
+import com.adobe.campaign.tests.bridge.testdata.one.SimpleStaticMethods;
+import io.javalin.Javalin;
+import org.hamcrest.Matchers;
+import org.testng.annotations.AfterGroups;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static io.restassured.RestAssured.given;
+
+/**
+ * Verifies that when BridgeService is started from bridgeService-test-injection's classpath,
+ * methods from both this module and bridgeService-data are reachable via REST (/call)
+ * and via MCP (tools/list).
+ *
+ * The server is started once with MCP enabled and both modules' packages in
+ * STATIC_INTEGRITY_PACKAGES so that tool discovery and class loading cover both.
+ */
+public class InjectionModelMCPTests {
+
+ private static final String REST_ENDPOINT = "http://localhost:8080/";
+ private static final String MCP_ENDPOINT = "http://localhost:8080/mcp";
+ private static final String CONTENT_TYPE_JSON = "application/json";
+
+ /** Package from bridgeService-data whose Javadoc is embedded at compile time. */
+ private static final String BRIDGE_DATA_PACKAGE =
+ "com.adobe.campaign.tests.bridge.testdata.one";
+
+ /** Package from this module whose Javadoc is embedded via therapi at compile time. */
+ private static final String FACTORY_PACKAGE =
+ "com.adobe.campaign.tests.bridge.dependency.factory.";
+
+ private static final String BOTH_PACKAGES = BRIDGE_DATA_PACKAGE + "," + FACTORY_PACKAGE;
+
+ private Javalin app;
+
+ @BeforeGroups(groups = "INJECTION_MCP")
+ public void startMCPService() {
+ ConfigValueHandlerIBS.STATIC_INTEGRITY_PACKAGES.activate(BOTH_PACKAGES);
+ ConfigValueHandlerIBS.MCP_ENABLED.activate("true");
+ app = IntegroAPI.startServices(8080);
+ }
+
+ @BeforeMethod
+ public void resetConfig() {
+ ConfigValueHandlerIBS.STATIC_INTEGRITY_PACKAGES.activate(BOTH_PACKAGES);
+ }
+
+ @AfterGroups(groups = "INJECTION_MCP", alwaysRun = true)
+ public void tearDown() {
+ ConfigValueHandlerIBS.resetAllValues();
+ app.stop();
+ }
+
+ // ---- REST access ----
+
+ /**
+ * Calls SimpleStaticMethods.methodReturningString() from bridgeService-data via REST.
+ * Proves that dependency JAR methods are callable through the /call endpoint when
+ * their package is listed in STATIC_INTEGRITY_PACKAGES.
+ */
+ @Test(groups = "INJECTION_MCP")
+ public void testRESTCall_bridgeDataMethod() {
+ JavaCalls l_calls = new JavaCalls();
+ CallContent l_cc = new CallContent();
+ l_cc.setClassName(SimpleStaticMethods.class.getTypeName());
+ l_cc.setMethodName("methodReturningString");
+ l_calls.getCallContent().put("call1", l_cc);
+
+ given().body(l_calls).post(REST_ENDPOINT + "call").then()
+ .assertThat().statusCode(200)
+ .body("returnValues.call1", Matchers.notNullValue());
+ }
+
+ /**
+ * Calls DepFactory.makeMiddleMan() from this module (bridgeService-test-injection) via REST.
+ * Proves that methods defined in the module that starts the service are also callable
+ * through the /call endpoint.
+ */
+ @Test(groups = "INJECTION_MCP")
+ public void testRESTCall_testInjectionMethod() {
+ JavaCalls l_calls = new JavaCalls();
+ CallContent l_cc = new CallContent();
+ l_cc.setClassName(DepFactory.class.getTypeName());
+ l_cc.setMethodName("makeMiddleMan");
+ l_calls.getCallContent().put("call1", l_cc);
+
+ given().body(l_calls).post(REST_ENDPOINT + "call").then()
+ .assertThat().statusCode(200);
+ }
+
+ // ---- MCP tools/list access ----
+
+ /**
+ * Verifies tools/list returns SimpleStaticMethods_methodReturningString from bridgeService-data.
+ * Proves that MCP tool discovery scans dependency JARs on the classpath when their package
+ * is listed in STATIC_INTEGRITY_PACKAGES at server startup.
+ */
+ @Test(groups = "INJECTION_MCP")
+ public void testMCPToolDiscovery_findsBridgeDataMethod() {
+ given().contentType(CONTENT_TYPE_JSON)
+ .body("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\",\"params\":{}}")
+ .post(MCP_ENDPOINT)
+ .then()
+ .assertThat().statusCode(200)
+ .body("result.tools.name",
+ Matchers.hasItem("SimpleStaticMethods_methodReturningString"));
+ }
+
+ /**
+ * Verifies tools/list returns DepFactory_makeMiddleMan from this module.
+ * Proves that MCP tool discovery also covers classes compiled into the module that
+ * starts the service — not just classes from external dependency JARs.
+ */
+ @Test(groups = "INJECTION_MCP")
+ public void testMCPToolDiscovery_findsTestInjectionMethod() {
+ given().contentType(CONTENT_TYPE_JSON)
+ .body("{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/list\",\"params\":{}}")
+ .post(MCP_ENDPOINT)
+ .then()
+ .assertThat().statusCode(200)
+ .body("result.tools.name", Matchers.hasItem("DepFactory_makeMiddleMan"));
+ }
+}
diff --git a/bridgeService-test-injection/src/test/resources/log4j2.xml b/bridgeService-test-injection/src/test/resources/log4j2.xml
new file mode 100644
index 0000000..fbc4540
--- /dev/null
+++ b/bridgeService-test-injection/src/test/resources/log4j2.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+ %-5p | %d{yyyy-MM-dd HH:mm:ss} | [%t] %C{2} (%F:%L) - %m%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bridgeService-test-injection/src/test/resources/testng.xml b/bridgeService-test-injection/src/test/resources/testng.xml
new file mode 100644
index 0000000..b31e66d
--- /dev/null
+++ b/bridgeService-test-injection/src/test/resources/testng.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index bd58a41..051987d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,7 @@
integroBridgeService
bridgeService-data
+ bridgeService-test-injection
11