diff --git a/qqq-all-app/README.md b/qqq-all-app/README.md
new file mode 100644
index 0000000..0fb0c20
--- /dev/null
+++ b/qqq-all-app/README.md
@@ -0,0 +1,12 @@
+# Core reference app
+
+Build with Java 21 and Maven, then start the standalone core profile:
+
+```sh
+mvn -pl qqq-all-app -am verify
+java -jar qqq-all-app/target/qqq-all-app-4.1.0-SNAPSHOT.jar
+```
+
+The core app binds to `127.0.0.1` and serves the Next dashboard at `http://127.0.0.1:8080/` and health at `/health`. Set `QQQ_ALL_PORT` to change the port and `QQQ_ALL_DATA_DIR` to change the default `./data` directory. On first start it seeds customers and orders in H2, order lines in SQLite, and products as JSON files. Artemis runs in the same JVM; inserts and updates to `order` publish to the `orderEvents` topic and trigger `syncOrder`.
+
+Core uses mock authentication. The `sessionId` cookie selects the two fixed demo identities: `aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa` for Admin and `dddddddd-dddd-4ddd-8ddd-dddddddddddd` for Demo User. Any other session ID uses Demo User. These identities are for local demonstrations; role-based permissions are wired in task C5.
diff --git a/qqq-all-app/pom.xml b/qqq-all-app/pom.xml
index 5c8db9a..0912da7 100644
--- a/qqq-all-app/pom.xml
+++ b/qqq-all-app/pom.xml
@@ -16,4 +16,76 @@
QQQ All - Reference Application
Reference QQQ application with every backend, qBit, and the ESB wired, plus seeded demo data.
+
+
+
+ com.kingsrook.qqq
+ qqq-all-bom
+ ${project.version}
+ pom
+ import
+
+
+
+
+
+ com.kingsrook.qqqqqq-backend-core
+ com.kingsrook.qqqqqq-backend-module-rdbms
+ com.kingsrook.qqqqqq-backend-module-sqlite
+ com.kingsrook.qqqqqq-backend-module-filesystem
+ com.kingsrook.qqqqqq-middleware-javalin
+ com.kingsrook.qqqqqq-middleware-health
+ com.kingsrook.qqqqqq-esb
+ com.kingsrook.qqqqqq-frontend-next
+ com.h2databaseh2
+ org.xerialsqlite-jdbc
+ org.apache.artemisartemis-jakarta-server2.57.0
+ org.junit.jupiterjunit-jupiter6.0.2test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.1
+
+
+ package
+ shade
+
+
+
+
+ com.kingsrook.qqq.all.QqqAllApplication
+
+
+
+
+ *:*
+ META-INF/*.SFMETA-INF/*.DSAMETA-INF/*.RSA
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.5.6
+
+
+ ${project.build.directory}/${project.build.finalName}.jar
+
+
+
+
+ integration-testverify
+
+
+
+
+
+
diff --git a/qqq-all-app/src/main/java/com/kingsrook/qqq/all/CoreData.java b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/CoreData.java
new file mode 100644
index 0000000..ac2b0eb
--- /dev/null
+++ b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/CoreData.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2026 Kingsrook, LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.kingsrook.qqq.all;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/** Creates the small, repeatable core demo data set before the web server starts. */
+final class CoreData
+{
+ private CoreData()
+ {
+ }
+
+ static String h2DatabaseName(Path dataDirectory)
+ {
+ return dataDirectory.resolve("h2/qqq-all").toAbsolutePath().toString();
+ }
+
+ static Path sqlitePath(Path dataDirectory)
+ {
+ return dataDirectory.resolve("sqlite/qqq-all.db");
+ }
+
+ static void seed(Path dataDirectory) throws IOException, SQLException
+ {
+ Files.createDirectories(dataDirectory.resolve("h2"));
+ Files.createDirectories(dataDirectory.resolve("sqlite"));
+ Files.createDirectories(dataDirectory.resolve("files/products"));
+
+ try(Connection connection = DriverManager.getConnection(
+ "jdbc:h2:file:" + h2DatabaseName(dataDirectory) + ";MODE=MySQL;DB_CLOSE_DELAY=-1", "sa", "");
+ Statement statement = connection.createStatement())
+ {
+ statement.execute("CREATE TABLE IF NOT EXISTS customer (id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(120), email VARCHAR(200))");
+ statement.execute("CREATE TABLE IF NOT EXISTS orders (id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, order_no VARCHAR(40), customer_id INTEGER, status VARCHAR(40))");
+ statement.execute("MERGE INTO customer (id, name, email) KEY (id) VALUES (1, 'Ada Lovelace', 'ada@example.test'), (2, 'Grace Hopper', 'grace@example.test')");
+ statement.execute("MERGE INTO orders (id, order_no, customer_id, status) KEY (id) VALUES (1, 'ORD-1001', 1, 'NEW')");
+ }
+
+ try(Connection connection = DriverManager.getConnection("jdbc:sqlite:" + sqlitePath(dataDirectory));
+ Statement statement = connection.createStatement())
+ {
+ statement.execute("CREATE TABLE IF NOT EXISTS order_line (id INTEGER PRIMARY KEY, order_id INTEGER, product_id INTEGER, quantity INTEGER)");
+ statement.execute("INSERT OR IGNORE INTO order_line (id, order_id, product_id, quantity) VALUES (1, 1, 1, 2)");
+ }
+
+ Path product = dataDirectory.resolve("files/products/1.json");
+ if(Files.notExists(product))
+ {
+ Files.writeString(product, "{\"id\":1,\"name\":\"QRun Starter Kit\",\"price\":29.00}\n");
+ }
+ }
+}
diff --git a/qqq-all-app/src/main/java/com/kingsrook/qqq/all/CoreEsbRuntimeService.java b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/CoreEsbRuntimeService.java
new file mode 100644
index 0000000..f2f3281
--- /dev/null
+++ b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/CoreEsbRuntimeService.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2026 Kingsrook, LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.kingsrook.qqq.all;
+
+import com.kingsrook.qqq.backend.core.instances.QRuntimeServiceInterface;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.esb.runtime.QEsbRuntime;
+
+/** Launches the ESB subscriber through the common QQQ application launcher. */
+public class CoreEsbRuntimeService implements QRuntimeServiceInterface
+{
+ @Override
+ public String getName()
+ {
+ return "coreEsb";
+ }
+
+ @Override
+ public void start(QInstance instance)
+ {
+ QEsbRuntime.getInstance().start(instance);
+ }
+
+ @Override
+ public void stop()
+ {
+ QEsbRuntime.getInstance().stop();
+ }
+}
diff --git a/qqq-all-app/src/main/java/com/kingsrook/qqq/all/CoreRuntime.java b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/CoreRuntime.java
new file mode 100644
index 0000000..1492c66
--- /dev/null
+++ b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/CoreRuntime.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2026 Kingsrook, LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.kingsrook.qqq.all;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.nio.file.Path;
+import com.kingsrook.qqq.middleware.javalin.QApplicationLauncher;
+import com.kingsrook.qqq.middleware.javalin.QApplicationLauncherConfig;
+import org.apache.activemq.artemis.core.config.Configuration;
+import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
+import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ;
+
+/** Owns the embedded broker and launcher as one closeable core profile. */
+final class CoreRuntime implements AutoCloseable
+{
+ private final EmbeddedActiveMQ broker;
+ private final QApplicationLauncher launcher;
+ private final Thread shutdownHook;
+
+ private CoreRuntime(EmbeddedActiveMQ broker, QApplicationLauncher launcher)
+ {
+ this.broker = broker;
+ this.launcher = launcher;
+ this.shutdownHook = new Thread(this::close, "qqq-all-core-shutdown");
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+ }
+
+ static CoreRuntime launch(Path dataDirectory, Integer port) throws Exception
+ {
+ CoreData.seed(dataDirectory);
+ Integer brokerPort = findFreePort();
+ Path brokerDirectory = dataDirectory.resolve("artemis").toAbsolutePath();
+ String brokerUrl = "tcp://127.0.0.1:" + brokerPort;
+ Configuration configuration = new ConfigurationImpl()
+ .setPersistenceEnabled(false).setSecurityEnabled(false).setJMXManagementEnabled(false)
+ .setBindingsDirectory(brokerDirectory.resolve("bindings").toString())
+ .setJournalDirectory(brokerDirectory.resolve("journal").toString())
+ .setPagingDirectory(brokerDirectory.resolve("paging").toString())
+ .setLargeMessagesDirectory(brokerDirectory.resolve("large-messages").toString())
+ .addAcceptorConfiguration("tcp", brokerUrl);
+ EmbeddedActiveMQ broker = new EmbeddedActiveMQ().setConfiguration(configuration);
+ broker.start();
+ try
+ {
+ QApplicationLauncher launcher = QApplicationLauncher.run(
+ new QqqAllApplication(dataDirectory, brokerUrl),
+ new QApplicationLauncherConfig().withRegisterShutdownHook(false)
+ .withServerCustomizer(server -> server.withPort(port)
+ .withJavalinConfigCustomizer(config -> config.jetty.host = "127.0.0.1")));
+ return new CoreRuntime(broker, launcher);
+ }
+ catch(Exception | LinkageError e)
+ {
+ broker.stop();
+ throw e;
+ }
+ }
+
+ private static Integer findFreePort() throws IOException
+ {
+ try(ServerSocket socket = new ServerSocket(0))
+ {
+ return socket.getLocalPort();
+ }
+ }
+
+ QApplicationLauncher getLauncher()
+ {
+ return launcher;
+ }
+
+ @Override
+ public void close()
+ {
+ launcher.stop();
+ try
+ {
+ broker.stop();
+ }
+ catch(Exception e)
+ {
+ throw new IllegalStateException("Could not stop embedded Artemis", e);
+ }
+ if(Thread.currentThread() != shutdownHook)
+ {
+ Runtime.getRuntime().removeShutdownHook(shutdownHook);
+ }
+ }
+}
diff --git a/qqq-all-app/src/main/java/com/kingsrook/qqq/all/DemoUsers.java b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/DemoUsers.java
new file mode 100644
index 0000000..cfb90d9
--- /dev/null
+++ b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/DemoUsers.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2026 Kingsrook, LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.kingsrook.qqq.all;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleCustomizerInterface;
+
+/** Fixed identities for local mock authentication; the session cookie selects one. */
+public class DemoUsers implements QAuthenticationModuleCustomizerInterface
+{
+ public static final String ADMIN_SESSION = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa";
+ public static final String DEMO_SESSION = "dddddddd-dddd-4ddd-8ddd-dddddddddddd";
+
+ @Override
+ public void customizeSession(QInstance instance, QSession session, Map context)
+ {
+ Boolean admin = ADMIN_SESSION.equals(session.getUuid());
+ String name = admin ? "Admin" : "Demo User";
+ String userId = admin ? "core:admin" : "core:demo";
+ session.getUser().setIdReference(userId);
+ session.getUser().setFullName(name);
+ session.withValueForFrontend("user", new LinkedHashMap<>(Map.of("name", name, "email", userId + "@example.test")));
+ }
+}
diff --git a/qqq-all-app/src/main/java/com/kingsrook/qqq/all/QqqAllApplication.java b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/QqqAllApplication.java
new file mode 100644
index 0000000..9eec1bd
--- /dev/null
+++ b/qqq-all-app/src/main/java/com/kingsrook/qqq/all/QqqAllApplication.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2026 Kingsrook, LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.kingsrook.qqq.all;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.authentication.AuthScope;
+import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.Cardinality;
+import com.kingsrook.qqq.backend.module.filesystem.base.model.metadata.RecordFormat;
+import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemBackendMetaData;
+import com.kingsrook.qqq.backend.module.filesystem.local.model.metadata.FilesystemTableBackendDetails;
+import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
+import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
+import com.kingsrook.qqq.backend.module.sqlite.model.metadata.SQLiteBackendMetaData;
+import com.kingsrook.qqq.esb.model.EsbDestinationType;
+import com.kingsrook.qqq.esb.model.EsbInstanceMetaData;
+import com.kingsrook.qqq.esb.model.EsbProcessMetaData;
+import com.kingsrook.qqq.esb.model.EsbProviderType;
+import com.kingsrook.qqq.esb.model.EsbTableEvent;
+import com.kingsrook.qqq.esb.model.EsbTableMetaData;
+import com.kingsrook.qqq.esb.model.EsbTablePublication;
+import com.kingsrook.qqq.esb.model.EsbTrigger;
+import com.kingsrook.qqq.esb.model.QEsbDestinationMetaData;
+import com.kingsrook.qqq.esb.model.QEsbProviderMetaData;
+import com.kingsrook.qqq.middleware.health.JavalinHealthRouteProvider;
+import com.kingsrook.qqq.middleware.health.indicators.MemoryHealthIndicator;
+import com.kingsrook.qqq.middleware.health.model.metadata.HealthCheckMetaData;
+import com.kingsrook.qqq.middleware.javalin.QJavalinMetaData;
+
+/** The self-contained core QQQ reference application. */
+public final class QqqAllApplication extends AbstractQQQApplication
+{
+ private final Path dataDirectory;
+ private final String brokerUrl;
+
+ public QqqAllApplication(Path dataDirectory, String brokerUrl)
+ {
+ this.dataDirectory = dataDirectory;
+ this.brokerUrl = brokerUrl;
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+ Path dataDirectory = Path.of(System.getenv().getOrDefault("QQQ_ALL_DATA_DIR", "./data"));
+ Integer port = Integer.valueOf(System.getenv().getOrDefault("QQQ_ALL_PORT", "8080"));
+ CoreRuntime.launch(dataDirectory, port);
+ }
+
+ @Override
+ public QInstance defineQInstance() throws QException
+ {
+ QInstance instance = new QInstance().withFailOnMetaDataProducerError(true);
+ instance.registerAuthenticationProvider(AuthScope.instanceDefault(), new QAuthenticationMetaData()
+ .withName("mock").withType(QAuthenticationType.MOCK)
+ .withCustomizer(new QCodeReference(DemoUsers.class)));
+
+ instance.addBackend(new RDBMSBackendMetaData()
+ .withName("h2").withVendor("h2").withHostName("file")
+ .withDatabaseName(CoreData.h2DatabaseName(dataDirectory)).withUsername("sa"));
+ instance.addBackend(new SQLiteBackendMetaData().withName("sqlite")
+ .withPath(CoreData.sqlitePath(dataDirectory).toAbsolutePath().toString()));
+ instance.addBackend(new FilesystemBackendMetaData().withName("files")
+ .withBasePath(dataDirectory.resolve("files").toAbsolutePath().toString()));
+
+ instance.addTable(new QTableMetaData().withName("customer").withLabel("Customer")
+ .withBackendName("h2").withPrimaryKeyField("id")
+ .withField(new QFieldMetaData("id", QFieldType.INTEGER))
+ .withField(new QFieldMetaData("name", QFieldType.STRING))
+ .withField(new QFieldMetaData("email", QFieldType.STRING)));
+ instance.addTable(new QTableMetaData().withName("order").withLabel("Order")
+ .withBackendName("h2").withBackendDetails(new RDBMSTableBackendDetails().withTableName("orders"))
+ .withPrimaryKeyField("id")
+ .withField(new QFieldMetaData("id", QFieldType.INTEGER))
+ .withField(new QFieldMetaData("orderNo", QFieldType.STRING).withBackendName("order_no"))
+ .withField(new QFieldMetaData("customerId", QFieldType.INTEGER).withBackendName("customer_id"))
+ .withField(new QFieldMetaData("status", QFieldType.STRING))
+ .withSupplementalMetaData(new EsbTableMetaData().withPublication(new EsbTablePublication()
+ .withDestinationName("orderEvents").withEvents(List.of(EsbTableEvent.INSERT, EsbTableEvent.UPDATE)))));
+ instance.addTable(new QTableMetaData().withName("orderLine").withLabel("Order Line")
+ .withBackendName("sqlite").withBackendDetails(new RDBMSTableBackendDetails().withTableName("order_line"))
+ .withPrimaryKeyField("id")
+ .withField(new QFieldMetaData("id", QFieldType.INTEGER))
+ .withField(new QFieldMetaData("orderId", QFieldType.INTEGER).withBackendName("order_id"))
+ .withField(new QFieldMetaData("productId", QFieldType.INTEGER).withBackendName("product_id"))
+ .withField(new QFieldMetaData("quantity", QFieldType.INTEGER)));
+ instance.addTable(new QTableMetaData().withName("product").withLabel("Product")
+ .withBackendName("files").withPrimaryKeyField("id")
+ .withBackendDetails(new FilesystemTableBackendDetails()
+ .withBasePath("products").withRecordFormat(RecordFormat.JSON)
+ .withCardinality(Cardinality.MANY).withGlob("*.json"))
+ .withField(new QFieldMetaData("id", QFieldType.INTEGER))
+ .withField(new QFieldMetaData("name", QFieldType.STRING))
+ .withField(new QFieldMetaData("price", QFieldType.DECIMAL)));
+
+ instance.addProcess(new QProcessMetaData().withName("syncOrder").withLabel("Sync Order")
+ .withTableName("order")
+ .withStep(new QBackendStepMetaData().withName("sync")
+ .withCode(new QCodeReference(SyncOrderStep.class)))
+ .withSupplementalMetaData(new EsbProcessMetaData()
+ .withTrigger(new EsbTrigger().withDestinationName("orderEvents"))));
+
+ EsbInstanceMetaData.of(instance).withInstanceName("qqqAll")
+ .withProvider(new QEsbProviderMetaData().withName("artemis")
+ .withType(EsbProviderType.ACTIVEMQ_ARTEMIS).withUrl(brokerUrl))
+ .withDestination(new QEsbDestinationMetaData().withName("orderEvents")
+ .withType(EsbDestinationType.TOPIC).withProviderName("artemis")
+ .withDestinationName("qqq.all.orderEvents"));
+ instance.withRuntimeService(new QCodeReference(CoreEsbRuntimeService.class));
+
+ instance.withSupplementalMetaData(new HealthCheckMetaData().withEnabled(true)
+ .withEndpointPath("/health").withIndicators(List.of(new MemoryHealthIndicator().withThreshold(99))));
+ QJavalinMetaData.ofOrWithNew(instance)
+ .withAdditionalRouteProviderReference(new QCodeReference(JavalinHealthRouteProvider.class));
+ return instance;
+ }
+
+ /** A visible effect for the demo and for the ESB integration test. */
+ public static final class SyncOrderStep implements BackendStep
+ {
+ private static final AtomicInteger RUN_COUNT = new AtomicInteger();
+
+ @Override
+ public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
+ {
+ RUN_COUNT.incrementAndGet();
+ }
+
+ public static Integer getRunCount()
+ {
+ return RUN_COUNT.get();
+ }
+
+ public static void reset()
+ {
+ RUN_COUNT.set(0);
+ }
+ }
+}
diff --git a/qqq-all-app/src/test/java/com/kingsrook/qqq/all/CoreApplicationTest.java b/qqq-all-app/src/test/java/com/kingsrook/qqq/all/CoreApplicationTest.java
new file mode 100644
index 0000000..de521f7
--- /dev/null
+++ b/qqq-all-app/src/test/java/com/kingsrook/qqq/all/CoreApplicationTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2026 Kingsrook, LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.kingsrook.qqq.all;
+
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
+import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
+import com.kingsrook.qqq.backend.core.context.QContext;
+import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
+import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
+import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
+import com.kingsrook.qqq.backend.core.modules.authentication.implementations.MockAuthenticationModule;
+import com.kingsrook.qqq.esb.runtime.EsbTriggerState;
+import com.kingsrook.qqq.esb.runtime.QEsbRuntime;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/** Exercises the assembled core application, including its real broker and backends. */
+class CoreApplicationTest
+{
+ @TempDir
+ Path dataDirectory;
+
+ @Test
+ void bootsQueriesEveryTableAndProcessesOrderEvents() throws Exception
+ {
+ Integer port;
+ try(ServerSocket socket = new ServerSocket(0))
+ {
+ port = socket.getLocalPort();
+ }
+
+ QqqAllApplication.SyncOrderStep.reset();
+ try(CoreRuntime runtime = CoreRuntime.launch(dataDirectory, port))
+ {
+ HttpClient client = HttpClient.newHttpClient();
+ HttpResponse health = client.send(HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:" + port + "/health")).build(),
+ HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, health.statusCode());
+ assertTrue(health.body().contains("\"UP\""));
+
+ HttpResponse dashboard = client.send(HttpRequest.newBuilder()
+ .uri(URI.create("http://localhost:" + port + "/")).build(),
+ HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, dashboard.statusCode());
+ assertTrue(dashboard.body().contains("html"));
+
+ QContext.init(runtime.getLauncher().getQInstance(), new QSystemUserSession());
+ try
+ {
+ MockAuthenticationModule mockAuth = new MockAuthenticationModule();
+ assertEquals("core:admin", mockAuth.createSession(runtime.getLauncher().getQInstance(),
+ Map.of("sessionId", DemoUsers.ADMIN_SESSION)).getUser().getIdReference());
+ assertEquals("core:demo", mockAuth.createSession(runtime.getLauncher().getQInstance(),
+ Map.of("sessionId", DemoUsers.DEMO_SESSION)).getUser().getIdReference());
+
+ for(String table : List.of("customer", "order", "orderLine", "product"))
+ {
+ assertFalse(new QueryAction().execute(new QueryInput(table)).getRecords().isEmpty(), table);
+ }
+
+ waitFor(() -> QEsbRuntime.getInstance().getRunner("syncOrder.orderEvents") != null
+ && QEsbRuntime.getInstance().getRunner("syncOrder.orderEvents").getState() == EsbTriggerState.RUNNING);
+ new InsertAction().execute(new InsertInput("order").withRecords(List.of(
+ new QRecord().withValue("orderNo", "ORD-1002")
+ .withValue("customerId", 2).withValue("status", "NEW"))));
+ waitFor(() -> QqqAllApplication.SyncOrderStep.getRunCount() > 0);
+ }
+ finally
+ {
+ QContext.clear();
+ }
+ }
+ }
+
+ private static void waitFor(java.util.function.BooleanSupplier condition) throws Exception
+ {
+ long deadline = System.nanoTime() + Duration.ofSeconds(15).toNanos();
+ while(!condition.getAsBoolean() && System.nanoTime() < deadline)
+ {
+ Thread.sleep(50);
+ }
+ assertTrue(condition.getAsBoolean());
+ }
+}
diff --git a/qqq-all-app/src/test/java/com/kingsrook/qqq/all/CoreJarIT.java b/qqq-all-app/src/test/java/com/kingsrook/qqq/all/CoreJarIT.java
new file mode 100644
index 0000000..1724904
--- /dev/null
+++ b/qqq-all-app/src/test/java/com/kingsrook/qqq/all/CoreJarIT.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2026 Kingsrook, LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
+ */
+package com.kingsrook.qqq.all;
+
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/** Exercises the shaded entry point and its HTTP API after packaging. */
+class CoreJarIT
+{
+ @TempDir
+ Path dataDirectory;
+
+ @Test
+ void packagedApplicationServesSeededData() throws Exception
+ {
+ Integer port;
+ try(ServerSocket socket = new ServerSocket(0))
+ {
+ port = socket.getLocalPort();
+ }
+
+ Path jar = Path.of(System.getProperty("qqqAllJar"));
+ Path log = dataDirectory.resolve("app.log");
+ ProcessBuilder builder = new ProcessBuilder(
+ Path.of(System.getProperty("java.home"), "bin", "java").toString(), "-jar", jar.toString());
+ builder.environment().put("QQQ_ALL_PORT", port.toString());
+ builder.environment().put("QQQ_ALL_DATA_DIR", dataDirectory.resolve("data").toString());
+ builder.redirectErrorStream(true).redirectOutput(log.toFile());
+ Process process = builder.start();
+ try
+ {
+ HttpClient client = HttpClient.newHttpClient();
+ URI healthUri = URI.create("http://127.0.0.1:" + port + "/health");
+ HttpResponse health = awaitHealth(client, healthUri, process, log);
+ assertEquals(200, health.statusCode());
+ assertTrue(health.body().contains("\"UP\""));
+ assertTrue(Files.readString(log).contains("{127.0.0.1:" + port + "}"),
+ "Core profile must bind only to loopback");
+
+ HttpRequest query = HttpRequest.newBuilder()
+ .uri(URI.create("http://127.0.0.1:" + port + "/qqq/v1/table/customer/query"))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString("{}"))
+ .build();
+ HttpResponse customers = client.send(query, HttpResponse.BodyHandlers.ofString());
+ assertEquals(200, customers.statusCode(), customers.body());
+ assertTrue(customers.body().contains("Ada Lovelace"));
+ }
+ finally
+ {
+ process.destroy();
+ if(!process.waitFor(5, TimeUnit.SECONDS))
+ {
+ process.destroyForcibly();
+ process.waitFor();
+ }
+ }
+ }
+
+ private static HttpResponse awaitHealth(HttpClient client, URI uri, Process process, Path log) throws Exception
+ {
+ long deadline = System.nanoTime() + Duration.ofSeconds(30).toNanos();
+ while(System.nanoTime() < deadline && process.isAlive())
+ {
+ try
+ {
+ return client.send(HttpRequest.newBuilder().uri(uri).build(), HttpResponse.BodyHandlers.ofString());
+ }
+ catch(java.net.ConnectException e)
+ {
+ Thread.sleep(100);
+ }
+ }
+ throw new AssertionError("Packaged app did not become healthy. Output:\n" + Files.readString(log));
+ }
+}