From aiup-alfresco
Generate self-contained integration tests for Alfresco extensions using Testcontainers and Docker Compose from REQUIREMENTS.md. Covers Platform JAR, Share JAR, and Event Handler projects.
npx claudepluginhub aborroy/aiup-alfresco[path to REQUIREMENTS.md or description]# /test — Test Generator Generate tests for the Alfresco extension. ## Input Read `REQUIREMENTS.md` and all generated artefacts from previous commands. Resolve the project `Root path` values from Section 2 (Project Architecture) before writing tests. - Generate Platform JAR tests only when a `Platform JAR` project exists. - Generate Share JAR tests only when a `Share JAR` project exists. - Generate Event Handler tests only when an `Event Handler` project exists. - In Mixed mode, write each test file under its own project root; do not place both test suites in the same module. ## Outpu...
/integrationGenerates integration tests using Testcontainers for real dependencies like databases/APIs/queues/caches, with seeding/cleanup/fixtures and optional CI configs.
/coverGenerates and runs unit, integration (docker-compose/testcontainers), and Playwright E2E test suites for existing code. Analyzes coverage gaps, spawns parallel agents per tier, executes tests, and heals failures up to 3 times.
/testllmExecutes LLM-driven test workflow: reads testing_llm/ specs, catalogs all tests including integrations, builds checklists, validates with evidence, produces verified report.
/devkit.java.write-integration-testsGenerates integration tests for Spring Boot service or repository classes using Testcontainers for PostgreSQL, Redis, MongoDB with @ServiceConnection patterns.
/testRuns pytest tests for CLI harness on local path or GitHub repo, verifies CLI resolution, and updates TEST.md with results if all pass.
/testRuns TDD workflow: write failing tests for new features or bugs (Prove-It pattern), implement code to pass, verify full suite, refactor. Supports browser DevTools testing.
Share bugs, ideas, or general feedback.
Generate tests for the Alfresco extension.
Read REQUIREMENTS.md and all generated artefacts from previous commands.
Resolve the project Root path values from Section 2 (Project Architecture) before writing tests.
Platform JAR project exists.Share JAR project exists.Event Handler project exists.{platform-project-root}/src/test/java/{package}/{Name}ContainerIT.java
Self-contained: starts the ACS stack from compose.yaml via DockerComposeContainer, runs all
scenarios, then tears everything down. No pre-running ACS instance required.
@Testcontainers(disabledWithoutDocker = true)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class {Name}ContainerIT {
@Container
static final DockerComposeContainer<?> STACK =
new DockerComposeContainer<>(new File("{compose-file-relative-path}"))
.withExposedService("alfresco_1", 8080,
Wait.forHttp("/alfresco/api/-default-/public/alfresco/versions/1/probes/-ready-")
.withStartupTimeout(Duration.ofMinutes(10)))
.withLocalCompose(true);
private String nodesApi;
private String authHeader;
private HttpClient http;
@BeforeAll
void setup() throws Exception {
String host = STACK.getServiceHost("alfresco_1", 8080);
int port = STACK.getServicePort("alfresco_1", 8080);
nodesApi = "http://" + host + ":" + port
+ "/alfresco/api/-default-/public/alfresco/versions/1/nodes";
authHeader = "Basic " + Base64.getEncoder()
.encodeToString("admin:admin".getBytes(StandardCharsets.UTF_8));
http = HttpClient.newHttpClient();
// create any shared test folder structure here
}
@AfterAll
void cleanup() throws Exception {
// permanently delete all test data
}
// @Test methods using java.net.http.HttpClient against nodesApi
}
java.net.http.HttpClient for HTTP calls — no extra test dependencies.@TestInstance(PER_CLASS) + @Order when tests share state (e.g., a node created in test 1 is used in test 2).REQUIREMENTS.md.@BeforeAll; permanently delete them in @AfterAll.{platform-project-root}/src/test/java/{package}/{Name}IT.java
Runs against an already-running ACS instance when -Dacs.endpoint.path= is provided.
Skips gracefully otherwise so mvn verify always succeeds without Docker.
@BeforeAll
void setup() throws Exception {
assumeTrue(System.getProperty("acs.endpoint.path") != null,
"Skipping: set -Dacs.endpoint.path=http://host:8080 to run against an external ACS instance");
// ...
}
{platform-project-root}/http-tests/{extension-name}.sh
curl + jqHOST, USERNAME, PASSWORDsha256sum vs shasum where content hashing is neededAdd test methods to {Name}ContainerIT.java for each workflow scenario:
// --- Workflow Testing Setup ---
private String workflowApi;
private String processId; // shared across @Test methods
// In @BeforeAll, after the STACK starts:
workflowApi = "http://" + host + ":" + port
+ "/alfresco/api/-default-/public/workflow/versions/1";
// Discover processDefinitionId dynamically — never hardcode the version suffix (:1:104)
HttpRequest defReq = HttpRequest.newBuilder()
.uri(URI.create(workflowApi + "/process-definitions?name={processName}"))
.header("Authorization", authHeader)
.GET().build();
HttpResponse<String> defResp = http.send(defReq, HttpResponse.BodyHandlers.ofString());
assertEquals(200, defResp.statusCode(), "Expected 200 on process-definitions query");
// Parse: $.list.entries[0].entry.id → processDefinitionId
String processDefinitionId = /* parse from defResp.body() */ null;
assertNotNull(processDefinitionId, "Process definition not found — did /workflow deploy correctly?");
// --- Test methods ---
@Test
@Order(10)
void shouldStartWorkflow() throws Exception {
String body = """
{
"processDefinitionId": "%s",
"variables": [
{"name": "bpm_workflowDescription", "value": "Integration test workflow", "type": "d:text"}
]
}
""".formatted(processDefinitionId);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(workflowApi + "/processes"))
.header("Authorization", authHeader)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build();
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
assertEquals(201, resp.statusCode(), "Expected 201 on process start: " + resp.body());
// Parse processId from resp.body() → $.entry.id
}
@Test
@Order(20)
void shouldShowPendingTask() throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(workflowApi + "/tasks?processId=" + processId))
.header("Authorization", authHeader)
.GET().build();
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
assertEquals(200, resp.statusCode());
// Assert task count > 0 and verify candidateGroup / assignee
}
@Test
@Order(30)
void shouldCompleteTaskWithApproveOutcome() throws Exception {
// First retrieve the task ID
// Then complete with outcome variable in UNDERSCORE form (not colon)
String taskId = /* retrieve first task ID */ null;
String body = """
{
"action": "complete",
"variables": [
{"name": "{prefix}wf_{taskName}Outcome", "value": "Approve", "type": "d:text"}
]
}
""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(workflowApi + "/tasks/" + taskId))
.header("Authorization", authHeader)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build();
HttpResponse<String> resp = http.send(req, HttpResponse.BodyHandlers.ofString());
assertEquals(200, resp.statusCode(), "Expected 200 on task complete: " + resp.body());
}
@Test
@Order(40)
void shouldRejectAndReturnToInitiator() throws Exception {
// Start a new process, complete with Reject outcome, assert workflow routes to revise task
}
@AfterAll
void cleanupWorkflows() throws Exception {
// Delete all test-started processes using admin credentials
if (processId != null) {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(workflowApi + "/processes/" + processId))
.header("Authorization", authHeader)
.DELETE().build();
http.send(req, HttpResponse.BodyHandlers.ofString());
}
}
Workflow test conventions:
processDefinitionId via GET /process-definitions?name={processName} — never hardcode the version suffix (:1:104)POST /tasks/{taskId} with "action": "complete" — not legacy patterns{prefix}wf_{propName}) — matches Alfresco's colon→underscore mapping@AfterAll using admin credentials; use DELETE /processes/{id}PT5S in a test-specific BPMN variant, or document separately as requiring a slow test suiteAdd to {platform-project-root}/pom.xml:
<properties>
<testcontainers.version>1.20.2</testcontainers.version>
<maven.failsafe.plugin.version>3.2.5</maven.failsafe.plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- existing Alfresco BOM ... -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>${testcontainers.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- existing deps ... -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Surefire: unit tests only -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludes><exclude>**/*IT.java</exclude></excludes>
</configuration>
</plugin>
<!-- Failsafe: IT classes (Testcontainers + optional external) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven.failsafe.plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<includes><include>**/*IT.java</include></includes>
<systemPropertyVariables>
<acs.endpoint.path>${acs.endpoint.path}</acs.endpoint.path>
<acs.username>${acs.username}</acs.username>
<acs.password>${acs.password}</acs.password>
</systemPropertyVariables>
<!-- Docker Desktop 29.x on macOS requires API >= 1.40; docker-java defaults to older versions.
API_VERSION (not DOCKER_API_VERSION) is the env var docker-java 3.3.x actually reads. -->
<environmentVariables>
<API_VERSION>1.44</API_VERSION>
</environmentVariables>
</configuration>
</plugin>
</plugins>
</build>
Generate these only when a Share JAR project is declared in REQUIREMENTS.md.
{share-project-root}/src/test/java/{package}/share/{Name}ShareResourcesTest.java
Use lightweight tests that validate generated Share resources without requiring a browser:
class {Name}ShareResourcesTest {
@Test
void shouldLoadShareConfigWhenPresent() throws Exception {
// parse share-config-custom.xml when /share-config was used
}
@Test
void shouldLoadSurfExtensionMetadataWhenPresent() throws Exception {
// parse site-data/extensions/*.xml when /surf was used
}
@Test
void shouldLoadAikauPageArtifactsWhenPresent() throws Exception {
// assert descriptor + JS model files exist when /aikau was used
}
}
Required checks:
share-config-custom.xml is well-formed XML when generatedalfresco/module/...{share-project-root}/http-tests/share-smoke.sh
Generate a plain shell script using curl that:
http://{host}/share/ and accepts 2xx or 3xx/surf or /aikau) and accepts login redirect or successExample checks:
curl -s -o /dev/null -w '%{http_code}' "$HOST/share/" | grep -qE '^[23]'
curl -s -o /dev/null -w '%{http_code}' "$HOST/share/page/$PAGE_ID" | grep -qE '^[23]'
302 redirect to login as evidence that the Share route is registeredmacOS one-time setup: Docker Desktop 29.x rejects Docker API versions below 1.40. The
<environmentVariables>block above covers CI, but local developer machines also need:echo "api.version=1.44" >> ~/.docker-java.properties cat > ~/.testcontainers.properties <<'EOF' testcontainers.reuse.enable=true docker.client.strategy=org.testcontainers.dockerclient.UnixSocketClientProviderStrategy EOFSee the Docker Desktop on macOS — Testcontainers Compatibility section in
AGENTS.mdfor details.
Run commands:
# Self-contained (Docker required, port 8080 must be free)
mvn verify
# Against a running stack
mvn verify -Dacs.endpoint.path=http://localhost:8080
# Skip container tests, only build
mvn package -DskipITs
Prerequisite:
compose.yamlmust exist at the repository root with the ACS stack and a volume mount for the extension JAR. Run/docker-composefirst if it has not been generated yet.
{event-project-root}/src/test/java/{package}/handler/{Name}EventHandlerTest.java
AlfrescoEvent payloads and verify handler logic in isolation{event-project-root}/src/test/java/{package}/{Name}IT.java
@EmbeddedActiveMQ) or TestcontainersUpdate the Traceability Matrix in REQUIREMENTS.md with test references pointing to the
generated test class and method names.
{platform-project-root} is . for Platform JAR only mode, or {name}-platform/ for Mixed mode{event-project-root} is . for Event Handler only mode, or {name}-events/ for Mixed mode{compose-file-relative-path} is compose.yaml for single-project layouts and typically ../compose.yaml for child modules in Mixed modeIT@TestMethodOrder(MethodOrderer.OrderAnnotation.class) for ordered execution@AfterAll using ?permanent=true deletesset -euo pipefail and report pass/fail counts