url();
+
+ @WithName(STREAMX_PLATFORM_INSECURE)
+ @WithDefault(BooleanUtils.FALSE)
+ boolean insecure();
+
+ static PlatformConfig load() {
+ SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder()
+ .withMapping(PlatformConfig.class)
+ .addDefaultSources();
+
+ try {
+ builder.withSources(new PropertiesConfigSource(StreamxHome.getConfigUrl(), 260));
+ } catch (IOException expected) {
+ }
+
+ return builder
+ .build()
+ .getConfigMapping(PlatformConfig.class);
+ }
+}
diff --git a/src/main/java/com/streamx/cli/platform/PlatformContext.java b/src/main/java/com/streamx/cli/platform/PlatformContext.java
new file mode 100644
index 00000000..033cf87c
--- /dev/null
+++ b/src/main/java/com/streamx/cli/platform/PlatformContext.java
@@ -0,0 +1,106 @@
+package com.streamx.cli.platform;
+
+import static com.streamx.cli.i18n.MessageProvider.msg;
+
+import com.streamx.cli.config.StreamxHome;
+import com.streamx.cli.framework.CliException;
+
+/**
+ * Resolves the organization/project a command operates on.
+ *
+ *
+ * explicit argument > STREAMX_ORG / STREAMX_PROJECT > context's current-org/current-project
+ *
+ *
+ * The env vars are invocation-scoped overrides (CI, scripts) and deliberately not bound to a
+ * context; the files are the per-context persisted context written by {@code org use} and
+ * {@code project use}.
+ */
+public final class PlatformContext {
+
+ public static final String STREAMX_ORG = "STREAMX_ORG";
+ public static final String STREAMX_PROJECT = "STREAMX_PROJECT";
+
+ private PlatformContext() {
+ }
+
+ public record OrgProject(String org, String project) {
+ }
+
+ public static String effectiveOrg() {
+ String env = override(STREAMX_ORG);
+ return env != null ? env : StreamxHome.readCurrentOrg();
+ }
+
+ public static String effectiveProject() {
+ String env = override(STREAMX_PROJECT);
+ return env != null ? env : StreamxHome.readCurrentProject();
+ }
+
+ public static String effectiveOrgSource() {
+ if (override(STREAMX_ORG) != null) {
+ return "from the STREAMX_ORG environment variable";
+ }
+ return StreamxHome.readCurrentOrg() != null ? "from the current-org file" : null;
+ }
+
+ public static String effectiveProjectSource() {
+ if (override(STREAMX_PROJECT) != null) {
+ return "from the STREAMX_PROJECT environment variable";
+ }
+ return StreamxHome.readCurrentProject() != null ? "from the current-project file" : null;
+ }
+
+ public static String requireOrg(String orgArg) {
+ if (orgArg != null && !orgArg.isBlank()) {
+ return orgArg;
+ }
+ String effective = effectiveOrg();
+ if (effective == null) {
+ throw new CliException(msg.noOrgContext());
+ }
+ return effective;
+ }
+
+ public static OrgProject orgAndProject(String orgArg, String projectArg) {
+ String project = projectArg != null ? projectArg : effectiveProject();
+ if (project == null) {
+ throw new CliException(msg.noProjectContext());
+ }
+ return new OrgProject(requireOrg(orgArg), project);
+ }
+
+ public static String setCurrentOrg(String orgId) {
+ try {
+ String previousOrg = StreamxHome.readCurrentOrg();
+ String currentProject = StreamxHome.readCurrentProject();
+ StreamxHome.writeCurrentOrg(orgId);
+ if (currentProject != null && previousOrg != null && !previousOrg.equals(orgId)) {
+ StreamxHome.clearCurrentProject();
+ return currentProject;
+ }
+ return null;
+ } catch (java.io.IOException e) {
+ throw new CliException(e.getMessage(), e);
+ }
+ }
+
+ public static void setCurrentProject(String projectId) {
+ if (StreamxHome.readCurrentOrg() == null) {
+ throw new CliException(msg.noCurrentOrg());
+ }
+ try {
+ StreamxHome.writeCurrentProject(projectId);
+ } catch (java.io.IOException e) {
+ throw new CliException(e.getMessage(), e);
+ }
+ }
+
+ private static String override(String name) {
+ String value = System.getProperty(name);
+ if (value == null || value.isBlank()) {
+ value = System.getenv(name);
+ }
+ return value == null || value.isBlank() ? null : value;
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index d3e014ce..970bc01d 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,5 +1,13 @@
quarkus.package.jar.type=uber-jar
+# Default endpoints offered by `streamx context configure` (baked in at build time).
+# Ingestion has no default: its URL is per-project (ingestion..).
+# TEMPORARY. Restore before release:
+# streamx.defaults.auth.server-url=https://auth.streamx.com
+# streamx.defaults.platform.url=https://cloud.streamx.com
+streamx.defaults.auth.server-url=https://auth.demo-1.82-29-94-68.sslip.io:1443
+streamx.defaults.platform.url=https://console.demo-1.82-29-94-68.sslip.io:1443
+
quarkus.banner.enabled=false
quarkus.log.level=ERROR
quarkus.log.console.enable=true
@@ -19,4 +27,4 @@ quarkus.native.additional-build-args=\
# Fixes "Cannot load required properties from maven-build.properties" error
# when run streamx-runner with native-image executable.
-quarkus.native.resources.includes=maven-build.properties,default-event-templates/**,container/**
\ No newline at end of file
+quarkus.native.resources.includes=maven-build.properties,default-event-templates/**,container/**
diff --git a/src/test/java/com/streamx/cli/commands/completion/CompletionCommandIT.java b/src/test/java/com/streamx/cli/commands/completion/CompletionCommandIT.java
index 16e393e0..97d0b538 100644
--- a/src/test/java/com/streamx/cli/commands/completion/CompletionCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/completion/CompletionCommandIT.java
@@ -36,6 +36,14 @@ void shouldEmitDynamicTemplateIdCompletionForPublishEvent() throws Exception {
.contains("$(streamx __complete-template-ids 2>/dev/null)");
}
+ @Test
+ void shouldEmitDynamicContextNameCompletion() throws Exception {
+ ProcessResult result = exec("completion", "zsh");
+ result.assertSuccess();
+ assertThat(result.stdout())
+ .contains("$(streamx __complete-context-names 2>/dev/null)");
+ }
+
@Test
void shouldHideInternalCompleteTemplateIdsCommandFromZshSubcommands() throws Exception {
ProcessResult result = exec("completion", "zsh");
diff --git a/src/test/java/com/streamx/cli/commands/context/ContextCommandIT.java b/src/test/java/com/streamx/cli/commands/context/ContextCommandIT.java
new file mode 100644
index 00000000..7b5175b8
--- /dev/null
+++ b/src/test/java/com/streamx/cli/commands/context/ContextCommandIT.java
@@ -0,0 +1,295 @@
+package com.streamx.cli.commands.context;
+
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.sampleTemplate;
+import static com.streamx.cli.i18n.MessageProvider.msg;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.streamx.cli.test.CliBaseIT;
+import io.quarkus.test.junit.QuarkusTest;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+@QuarkusTest
+class ContextCommandIT extends CliBaseIT {
+
+
+ @BeforeEach
+ void cleanContexts() throws IOException {
+ deleteRecursively(streamxHome.resolve("contexts"));
+ Files.deleteIfExists(streamxHome.resolve("current-context"));
+ }
+
+ @Test
+ void firstUseBootstrapsDefaultContext() throws Exception {
+ ProcessResult result = exec("context", "current");
+
+ result.assertSuccess();
+ assertThat(result.stdout().strip()).isEqualTo("default");
+ assertThat(streamxHome.resolve("contexts/default/config")).isDirectory();
+ assertThat(streamxHome.resolve("contexts/default/event-templates")).isDirectory();
+ assertThat(streamxHome.resolve("current-context")).content().contains("default");
+ }
+
+ @Test
+ void createSwitchesToNewContextAndSuggestsConfigure() throws Exception {
+ ProcessResult created = exec("context", "create", "prod");
+
+ created.assertSuccess();
+ assertThat(created.stdout())
+ .contains("Context 'prod' created")
+ .contains("Switched to context 'prod'");
+ assertThat(created.stderr()).contains("streamx context configure");
+ assertThat(exec("context", "current").stdout().strip()).isEqualTo("prod");
+ }
+
+ @Test
+ void createUseCurrentLifecycle() throws Exception {
+ exec("context", "create", "prod").assertSuccess();
+ exec("context", "use", "prod").assertSuccess();
+
+ ProcessResult current = exec("context", "current");
+ current.assertSuccess();
+ assertThat(current.stdout().strip()).isEqualTo("prod");
+
+ ProcessResult list = exec("context", "list");
+ list.assertSuccess();
+ assertThat(list.stdout()).contains("default").contains("prod").contains("*");
+
+ ProcessResult quiet = exec("context", "list", "-q");
+ quiet.assertSuccess();
+ assertThat(quiet.stdout().strip().lines()).containsExactly("default", "prod");
+ }
+
+ @Test
+ void useMissingContextFailsHard() throws Exception {
+ ProcessResult result = exec("context", "use", "nope");
+
+ result.assertExitCode(1);
+ assertThat(result.stderr()).contains("does not exist");
+ assertThat(exec("context", "current").stdout().strip()).isEqualTo("default");
+ }
+
+ @Test
+ void corruptedPointerFileErrorsWithPathAndFlagRepairs() throws Exception {
+ Files.writeString(streamxHome.resolve("current-context"), "Bad_Name\n");
+
+ ProcessResult result = exec("context", "current");
+ result.assertExitCode(1);
+ assertThat(result.stderr()).contains("Bad_Name").contains("current-context");
+
+ ProcessResult repaired = exec("context", "current", "-C", "default");
+ repaired.assertSuccess();
+ assertThat(repaired.stdout().strip()).isEqualTo("default");
+ }
+
+ @Test
+ void createRejectsInvalidNamesAndDuplicates() throws Exception {
+ assertThat(exec("context", "create", "Bad_Name").stderr()).contains("Invalid context name");
+ assertThat(exec("context", "create", "default").stderr()).contains("already exists");
+
+ exec("context", "create", "dup").assertSuccess();
+ assertThat(exec("context", "create", "dup").stderr()).contains("already exists");
+ }
+
+ @Test
+ void missingContextFailsAndCreatesNothing() throws Exception {
+ ProcessResult result = exec("settings", "list", "--context", "ghost");
+
+ result.assertExitCode(1);
+ assertThat(result.stderr()).contains("does not exist");
+ assertThat(streamxHome.resolve("contexts/ghost")).doesNotExist();
+
+ ProcessResult use = exec("context", "use", "ghost2", "--context", "ghost2");
+ use.assertExitCode(1);
+ assertThat(use.stderr()).contains("does not exist");
+ assertThat(streamxHome.resolve("contexts/ghost2")).doesNotExist();
+ }
+
+ @Test
+ void createFromCopiesSettingsAndTemplatesButNeverCredentials() throws Exception {
+ exec("context", "current").assertSuccess();
+ Files.writeString(streamxHome.resolve("contexts/default/config/application.properties"),
+ "streamx.platform.url=https://dev.example.com\n");
+ Files.writeString(streamxHome.resolve("contexts/default/config/credentials.json"), "{}");
+ Files.writeString(streamxHome.resolve("contexts/default/event-templates/mine.json"),
+ sampleTemplate("com.example.mine.v1"));
+
+ exec("context", "create", "clone", "--from", "default").assertSuccess();
+
+ Path cloneDir = streamxHome.resolve("contexts/clone");
+ assertThat(cloneDir.resolve("config/application.properties"))
+ .content().contains("dev.example.com");
+ assertThat(cloneDir.resolve("event-templates/mine.json")).isRegularFile();
+ assertThat(cloneDir.resolve("config/credentials.json")).doesNotExist();
+ }
+
+ @Test
+ void settingsFollowTheActiveContext() throws Exception {
+ exec("context", "create", "prod").assertSuccess();
+ exec("context", "use", "prod").assertSuccess();
+
+ exec("settings", "set", "streamx.platform.url", "https://prod.example.com").assertSuccess();
+
+ assertThat(streamxHome.resolve("contexts/prod/config/application.properties"))
+ .content().contains("prod.example.com");
+ Path defaultSettings = streamxHome.resolve("contexts/default/config/application.properties");
+ if (Files.exists(defaultSettings)) {
+ assertThat(defaultSettings).content().doesNotContain("prod.example.com");
+ }
+ }
+
+ @Test
+ void customTemplatesAndRegistrationsAreContextScoped() throws Exception {
+ exec("context", "create", "prod").assertSuccess();
+ exec("context", "use", "prod").assertSuccess();
+ Files.writeString(streamxHome.resolve("contexts/prod/event-templates/mine.json"),
+ sampleTemplate("com.example.mine.v1"));
+ Path registeredFile = streamxHome.resolve("reg-src.json");
+ Files.writeString(registeredFile, sampleTemplate("com.example.registered.v1"));
+ exec("settings", "event-templates", "register", "reg.tpl", registeredFile.toString())
+ .assertSuccess();
+
+ ProcessResult prodList = exec("settings", "event-templates", "list");
+ prodList.assertSuccess();
+ assertThat(prodList.stdout())
+ .contains("mine")
+ .contains("reg.tpl")
+ .contains("page.published");
+
+ exec("context", "use", "default").assertSuccess();
+ ProcessResult defaultList = exec("settings", "event-templates", "list");
+ defaultList.assertSuccess();
+ assertThat(defaultList.stdout())
+ .doesNotContain("mine")
+ .doesNotContain("reg.tpl")
+ .contains("page.published");
+ }
+
+ @Test
+ void contextFlagOverridesPointerWithoutChangingIt() throws Exception {
+ exec("context", "create", "prod").assertSuccess();
+ exec("context", "use", "default").assertSuccess();
+
+ ProcessResult overridden = exec("context", "current", "--context", "prod");
+ overridden.assertSuccess();
+ assertThat(overridden.stdout().strip()).isEqualTo("prod");
+
+ assertThat(exec("context", "current").stdout().strip()).isEqualTo("default");
+ }
+
+ @Test
+ void globalFlagsWorkAtAnyPosition() throws Exception {
+ exec("context", "create", "prod").assertSuccess();
+ // Point the pointer away from prod so each flag position must actually override it.
+ exec("context", "use", "default").assertSuccess();
+
+ ProcessResult root = exec("--context", "prod", "context", "current");
+ root.assertSuccess();
+ assertThat(root.stdout().strip()).isEqualTo("prod");
+
+ ProcessResult mid = exec("context", "--context", "prod", "current");
+ mid.assertSuccess();
+ assertThat(mid.stdout().strip()).isEqualTo("prod");
+
+ ProcessResult shortFlag = exec("-C", "prod", "context", "current");
+ shortFlag.assertSuccess();
+ assertThat(shortFlag.stdout().strip()).isEqualTo("prod");
+
+ Path altHome = streamxHome.resolve("alt-home");
+ ProcessResult alt = exec("-H", altHome.toString(), "context", "current");
+ alt.assertSuccess();
+ assertThat(alt.stdout().strip()).isEqualTo("default");
+ assertThat(altHome.resolve("contexts/default/config")).isDirectory();
+ }
+
+ @Test
+ void helpHeaderShowsCurrentContext() throws Exception {
+ ProcessResult defaultHelp = exec("--help");
+ defaultHelp.assertSuccess();
+ String out = defaultHelp.stdout();
+ assertThat(out).contains("Current context: default");
+ assertThat(out).doesNotContain("Usage:");
+ assertThat(out.indexOf("Current context:"))
+ .as("context line renders above the command list")
+ .isLessThan(out.indexOf("Commands:"));
+
+ exec("context", "create", "prod").assertSuccess();
+ exec("context", "use", "prod").assertSuccess();
+
+ ProcessResult prodHelp = exec("--help");
+ prodHelp.assertSuccess();
+ assertThat(prodHelp.stdout()).contains("Current context: prod");
+
+ ProcessResult flagHelp = exec("-C", "default", "--help");
+ flagHelp.assertSuccess();
+ assertThat(flagHelp.stdout())
+ .as("help header honors --context over the pointer")
+ .contains("Current context: default");
+ }
+
+ @Test
+ void completeContextNamesListsAllContexts() throws Exception {
+ exec("context", "create", "prod").assertSuccess();
+ exec("context", "create", "staging").assertSuccess();
+
+ ProcessResult result = exec("__complete-context-names");
+
+ result.assertSuccess();
+ assertThat(result.stdout().strip().lines())
+ .containsExactly("default", "prod", "staging");
+ }
+
+ @Test
+ void deleteRefusesActiveAndCurrentContext() throws Exception {
+ exec("context", "create", "prod").assertSuccess();
+ exec("context", "use", "prod").assertSuccess();
+
+ assertThat(exec("context", "delete", "missing").stderr()).contains("does not exist");
+ assertThat(exec("context", "delete", "prod").stderr()).contains("is active");
+ assertThat(exec("context", "delete", "prod", "--context", "default").stderr())
+ .contains("is set as the current context");
+ assertThat(streamxHome.resolve("contexts/prod")).isDirectory();
+ }
+
+ @Test
+ void deleteDefaultAllowedWhenNotCurrentAndBootstrapRecreatesIt() throws Exception {
+ exec("context", "create", "prod").assertSuccess();
+ exec("context", "use", "prod").assertSuccess();
+ Files.writeString(
+ streamxHome.resolve("contexts/default/config/credentials.json"), "{}");
+
+ ProcessResult deleted = exec("context", "delete", "default");
+ deleted.assertSuccess();
+ assertThat(deleted.stderr()).contains("NOT revoked");
+ assertThat(streamxHome.resolve("contexts/default")).doesNotExist();
+
+ assertThat(exec("context", "use", "default").stderr()).contains("does not exist");
+
+ ProcessResult bootstrapped = exec("context", "current", "--context", "default");
+ bootstrapped.assertSuccess();
+ assertThat(bootstrapped.stdout().strip()).isEqualTo("default");
+ assertThat(streamxHome.resolve("contexts/default/config")).isDirectory();
+ assertThat(exec("context", "current").stdout().strip()).isEqualTo("prod");
+ }
+
+ private static void deleteRecursively(Path root) throws IOException {
+ if (!Files.exists(root)) {
+ return;
+ }
+ try (Stream paths = Files.walk(root)) {
+ paths.sorted(Comparator.reverseOrder()).forEach(path -> {
+ try {
+ Files.delete(path);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+ }
+
+}
diff --git a/src/test/java/com/streamx/cli/commands/publish/event/DefaultEventTemplatesIT.java b/src/test/java/com/streamx/cli/commands/publish/event/DefaultEventTemplatesIT.java
index 757bc716..f0c7c17f 100644
--- a/src/test/java/com/streamx/cli/commands/publish/event/DefaultEventTemplatesIT.java
+++ b/src/test/java/com/streamx/cli/commands/publish/event/DefaultEventTemplatesIT.java
@@ -173,9 +173,9 @@ void shouldPreferSettingsOverPopulatedTemplate(@TempDir Path tempDir) throws Exc
Path payloadFile = tempDir.resolve("payload.html");
Files.writeString(payloadFile, "hello");
- Files.createDirectories(customHome);
+ Files.createDirectories(customHome.resolve("contexts/default"));
Files.writeString(
- customHome.resolve("override-page-published.json"),
+ customHome.resolve("contexts/default/override-page-published.json"),
templateWithOrigin("settings")
);
diff --git a/src/test/java/com/streamx/cli/commands/publish/event/EventTemplateCatalogTest.java b/src/test/java/com/streamx/cli/commands/publish/event/EventTemplateCatalogTest.java
index 6b495c89..8e38397b 100644
--- a/src/test/java/com/streamx/cli/commands/publish/event/EventTemplateCatalogTest.java
+++ b/src/test/java/com/streamx/cli/commands/publish/event/EventTemplateCatalogTest.java
@@ -44,6 +44,7 @@ class EventTemplateCatalogTest {
@BeforeEach
void redirectStreamxHome() {
StreamxHome.setStreamxHomeCliArg(home.toString());
+ StreamxHome.clearContextCliArg();
}
@AfterEach
@@ -100,7 +101,7 @@ void settingsRegistrationOverridesUserAndDefaults() throws Exception {
seedDefault("page.published", SAMPLE_DEFAULT);
seedUser("page.published", SAMPLE_USER);
- Path registered = home.resolve("registered.json");
+ Path registered = home.resolve("contexts/default/registered.json");
Files.writeString(registered, SAMPLE_REGISTERED);
seedSettings("page.published", "registered.json");
@@ -124,7 +125,8 @@ void listAllSortsByIdAndDeduplicates() throws Exception {
@Test
void listSettingsRegistrationsSkipsBlankAndNonPrefixedKeys() throws Exception {
- Path some = home.resolve("some.json");
+ Path some = home.resolve("contexts/default/some.json");
+ Files.createDirectories(some.getParent());
Files.writeString(some, SAMPLE_REGISTERED);
Properties props = new Properties();
props.setProperty("eventtemplate.real", "some.json");
@@ -138,16 +140,17 @@ void listSettingsRegistrationsSkipsBlankAndNonPrefixedKeys() throws Exception {
}
@Test
- void resolveRelativeToHomeAbsolutizesAgainstStreamxHome() {
- Path resolved = EventTemplateCatalog.resolveRelativeToHome("nested/file.json");
+ void resolveRelativeToContextDirAbsolutizesAgainstContextDir() {
+ Path resolved = EventTemplateCatalog.resolveRelativeToContextDir("nested/file.json");
assertThat(resolved).isAbsolute();
- assertThat(resolved).isEqualTo(home.resolve("nested/file.json").toAbsolutePath());
+ assertThat(resolved)
+ .isEqualTo(home.resolve("contexts/default/nested/file.json").toAbsolutePath());
}
@Test
- void resolveRelativeToHomeKeepsAbsolutePathsUntouched() {
+ void resolveRelativeToContextDirKeepsAbsolutePathsUntouched() {
Path absolute = home.resolve("abs.json").toAbsolutePath();
- Path resolved = EventTemplateCatalog.resolveRelativeToHome(absolute.toString());
+ Path resolved = EventTemplateCatalog.resolveRelativeToContextDir(absolute.toString());
assertThat(resolved).isEqualTo(absolute);
}
@@ -166,7 +169,7 @@ private void seedDefault(String id, String body) throws Exception {
}
private void seedUser(String id, String body) throws Exception {
- Path dir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path dir = home.resolve("contexts/default/event-templates");
Files.createDirectories(dir);
Files.writeString(dir.resolve(id + UserEventTemplates.EXTENSION), body);
}
@@ -178,7 +181,7 @@ private void seedSettings(String id, String pathValue) throws Exception {
}
private void writeConfig(Properties props) throws Exception {
- Path config = home.resolve("config/application.properties");
+ Path config = home.resolve("contexts/default/config/application.properties");
Files.createDirectories(config.getParent());
try (OutputStream out = Files.newOutputStream(config)) {
props.store(out, null);
diff --git a/src/test/java/com/streamx/cli/commands/publish/event/EventTemplateLoaderIT.java b/src/test/java/com/streamx/cli/commands/publish/event/EventTemplateLoaderIT.java
index 9730cc9c..32d3ee8e 100644
--- a/src/test/java/com/streamx/cli/commands/publish/event/EventTemplateLoaderIT.java
+++ b/src/test/java/com/streamx/cli/commands/publish/event/EventTemplateLoaderIT.java
@@ -34,6 +34,13 @@ static void stopMesh() {
private static final String DEFAULT_TEMPLATE_TYPE = "page.published";
private static final ObjectMapper MAPPER = new ObjectMapper();
+ /** Relative registration paths resolve against the active (default) context's dir. */
+ private static Path contextDir() throws Exception {
+ Path dir = streamxHome.resolve("contexts/default");
+ Files.createDirectories(dir);
+ return dir;
+ }
+
private static String createTemplateContent() {
return """
{
@@ -82,7 +89,7 @@ void shouldResolveTemplateFromRelativeFilename(@TempDir Path tempDir) throws Exc
Path payloadFile = tempDir.resolve("payload.html");
Files.writeString(payloadFile, "hello");
- Path templateFile = streamxHome.resolve("relative-test-template.json");
+ Path templateFile = contextDir().resolve("relative-test-template.json");
Files.writeString(templateFile, createTemplateContent());
try {
@@ -111,7 +118,7 @@ void shouldResolveTemplateFromRelativeSubdirectoryPath(@TempDir Path tempDir) th
Path payloadFile = tempDir.resolve("payload.html");
Files.writeString(payloadFile, "hello");
- Path templateSubDir = streamxHome.resolve("templates");
+ Path templateSubDir = contextDir().resolve("templates");
Files.createDirectories(templateSubDir);
Path templateFile = templateSubDir.resolve("sub-template.json");
Files.writeString(templateFile, createTemplateContent());
@@ -188,7 +195,7 @@ void shouldPreferSettingsTemplateWithRelativePathOverDefault(@TempDir Path tempD
Path payloadFile = tempDir.resolve("payload.html");
Files.writeString(payloadFile, "hello");
- Path templateFile = streamxHome.resolve("override-page-published.json");
+ Path templateFile = contextDir().resolve("override-page-published.json");
Files.writeString(templateFile, createTemplateContent());
try {
@@ -257,7 +264,8 @@ void shouldFailWhenRelativePathDoesNotExist(@TempDir Path tempDir) throws Except
);
result.assertExitCode(1);
- Path expectedPath = streamxHome.resolve("non-existent-template.json").toAbsolutePath();
+ Path expectedPath =
+ streamxHome.resolve("contexts/default/non-existent-template.json").toAbsolutePath();
assertThat(result.stderr()).contains(msg.eventTemplateFileMissing(expectedPath.toString()));
}
@@ -285,7 +293,7 @@ void shouldFailWhenRelativePathResolvesToDirectory(@TempDir Path tempDir) throws
Path payloadFile = tempDir.resolve("payload.html");
Files.writeString(payloadFile, "hello");
- Path templateDir = streamxHome.resolve("template-dir");
+ Path templateDir = contextDir().resolve("template-dir");
Files.createDirectories(templateDir);
try {
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/EventTemplatesTestSupport.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/EventTemplatesTestSupport.java
index 3c1f0ae0..77f88c91 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/EventTemplatesTestSupport.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/EventTemplatesTestSupport.java
@@ -3,6 +3,10 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
+import com.streamx.cli.commands.publish.event.DefaultEventTemplates;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
public final class EventTemplatesTestSupport {
@@ -12,6 +16,24 @@ public final class EventTemplatesTestSupport {
private EventTemplatesTestSupport() {
}
+ public static Path userTemplatesDir(Path home) {
+ return home.resolve("contexts/default/event-templates");
+ }
+
+ public static Path defaultTemplatesDir(Path home) {
+ return home.resolve(DefaultEventTemplates.DIRECTORY);
+ }
+
+ public static Path configFile(Path home) {
+ return home.resolve("contexts/default/config/application.properties");
+ }
+
+ public static Path contextFile(Path home, String name) throws IOException {
+ Path file = home.resolve("contexts/default").resolve(name);
+ Files.createDirectories(file.getParent());
+ return file;
+ }
+
public static String sampleTemplate(String type) {
return """
{
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/copy/CopyCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/copy/CopyCommandIT.java
index 5d2b4872..d3dd65e0 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/copy/CopyCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/copy/CopyCommandIT.java
@@ -1,11 +1,12 @@
package com.streamx.cli.commands.settings.eventtemplates.copy;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.defaultTemplatesDir;
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.sampleTemplate;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.userTemplatesDir;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.streamx.cli.commands.publish.event.UserEventTemplates;
import com.streamx.cli.test.CliBaseIT;
import io.quarkus.test.junit.QuarkusTest;
import java.nio.file.Files;
@@ -56,18 +57,18 @@ void shouldCopyDefaultTemplateToUserFolder(@TempDir Path tempDir) throws Excepti
result.assertSuccess();
- Path copy = home.resolve(UserEventTemplates.DIRECTORY).resolve("my.page.json");
+ Path copy = userTemplatesDir(home).resolve("my.page.json");
assertThat(copy).isRegularFile();
String content = Files.readString(copy);
assertThat(content).contains("com.streamx.blueprints.page.published");
- assertThat(home.resolve("event-templates/default/page.published.json")).isRegularFile();
+ assertThat(defaultTemplatesDir(home).resolve("page.published.json")).isRegularFile();
}
@Test
void shouldCopyUserTemplateUnderNewId(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path source = userDir.resolve("source.json");
Files.writeString(source, sampleTemplate("com.example.source.v1"));
@@ -88,7 +89,7 @@ void shouldCopyUserTemplateUnderNewId(@TempDir Path tempDir) throws Exception {
@Test
void shouldRefuseToOverwriteExistingId(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(userDir.resolve("source.json"), sampleTemplate("com.example.source.v1"));
Files.writeString(userDir.resolve("dest.json"), sampleTemplate("com.example.dest.v1"));
@@ -132,7 +133,7 @@ void shouldCopyViaInteractivePrompts(@TempDir Path tempDir) throws Exception {
);
result.assertSuccess();
- assertThat(home.resolve(UserEventTemplates.DIRECTORY).resolve("my.copy.json"))
+ assertThat(userTemplatesDir(home).resolve("my.copy.json"))
.isRegularFile();
}
}
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/create/CreateCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/create/CreateCommandIT.java
index 510bdca8..038861d1 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/create/CreateCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/create/CreateCommandIT.java
@@ -1,10 +1,11 @@
package com.streamx.cli.commands.settings.eventtemplates.create;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.defaultTemplatesDir;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.userTemplatesDir;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.streamx.cli.commands.publish.event.UserEventTemplates;
import com.streamx.cli.test.CliBaseIT;
import io.quarkus.test.junit.QuarkusTest;
import java.nio.file.Files;
@@ -29,7 +30,7 @@ void shouldCreateTemplateFromWizard(@TempDir Path tempDir) throws Exception {
result.assertSuccess();
- Path created = home.resolve(UserEventTemplates.DIRECTORY)
+ Path created = userTemplatesDir(home)
.resolve("my.new.template.json");
assertThat(created).isRegularFile();
@@ -71,14 +72,14 @@ void shouldFailWhenTypeBlank(@TempDir Path tempDir) throws Exception {
assertThat(result.exitCode()).isNotZero();
assertThat(result.stderr()).contains("CloudEvent type is required");
- Path notCreated = home.resolve(UserEventTemplates.DIRECTORY).resolve("my.blank.json");
+ Path notCreated = userTemplatesDir(home).resolve("my.blank.json");
assertThat(notCreated).doesNotExist();
}
@Test
void shouldRepromptOnIdConflictAndContinueWithFreshId(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path existing = userDir.resolve("already.there.json");
Files.writeString(existing, "{}");
@@ -121,17 +122,17 @@ void shouldRepromptOnConflictWithDefaultTemplate(@TempDir Path tempDir) throws E
assertThat(result.stderr()).contains("already exists");
assertThat(result.stderr())
- .contains(home.resolve("event-templates/default/page.published.json")
+ .contains(defaultTemplatesDir(home).resolve("page.published.json")
.toAbsolutePath().toString());
- Path created = home.resolve(UserEventTemplates.DIRECTORY).resolve("my.custom.json");
+ Path created = userTemplatesDir(home).resolve("my.custom.json");
assertThat(created).isRegularFile();
}
@Test
void shouldFailWhenInputExhaustedDuringConflictLoop(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(userDir.resolve("already.there.json"), "{}");
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/delete/DeleteCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/delete/DeleteCommandIT.java
index 4aa0827f..bc09506b 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/delete/DeleteCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/delete/DeleteCommandIT.java
@@ -1,11 +1,13 @@
package com.streamx.cli.commands.settings.eventtemplates.delete;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.contextFile;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.defaultTemplatesDir;
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.sampleTemplate;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.userTemplatesDir;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.streamx.cli.commands.publish.event.UserEventTemplates;
import com.streamx.cli.test.CliBaseIT;
import io.quarkus.test.junit.QuarkusTest;
import java.nio.file.Files;
@@ -21,7 +23,7 @@ class DeleteCommandIT extends CliBaseIT {
@Test
void shouldWorkWithJsonOutput(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path target = userDir.resolve("my.thing.json");
Files.writeString(target, sampleTemplate("com.example.thing.v1"));
@@ -30,7 +32,7 @@ void shouldWorkWithJsonOutput(@TempDir Path tempDir) throws Exception {
"settings", "event-templates", "delete",
"--streamx-home", home.toString(),
"my.thing",
- "--yes",
+ "--force",
"-o", "json"
);
@@ -44,7 +46,7 @@ void shouldWorkWithJsonOutput(@TempDir Path tempDir) throws Exception {
@Test
void shouldDeleteUserTemplateWithYesFlag(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path target = userDir.resolve("my.thing.json");
Files.writeString(target, sampleTemplate("com.example.thing.v1"));
@@ -53,7 +55,7 @@ void shouldDeleteUserTemplateWithYesFlag(@TempDir Path tempDir) throws Exception
"settings", "event-templates", "delete",
"--streamx-home", home.toString(),
"my.thing",
- "--yes"
+ "--force"
);
result.assertSuccess();
@@ -63,7 +65,7 @@ void shouldDeleteUserTemplateWithYesFlag(@TempDir Path tempDir) throws Exception
@Test
void shouldDeleteUserTemplateAfterConfirmation(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path target = userDir.resolve("my.thing.json");
Files.writeString(target, sampleTemplate("com.example.thing.v1"));
@@ -82,7 +84,7 @@ void shouldDeleteUserTemplateAfterConfirmation(@TempDir Path tempDir) throws Exc
@Test
void shouldCancelOnNo(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path target = userDir.resolve("my.thing.json");
Files.writeString(target, sampleTemplate("com.example.thing.v1"));
@@ -110,13 +112,13 @@ void shouldRefuseDeleteOfDefaultTemplate(@TempDir Path tempDir) throws Exception
"settings", "event-templates", "delete",
"--streamx-home", home.toString(),
"page.published",
- "--yes"
+ "--force"
);
assertThat(result.exitCode()).isNotZero();
assertThat(result.stderr()).contains("Cannot delete a default template");
assertThat(result.stderr()).contains("reset-default-templates");
- Path defaultFile = home.resolve("event-templates/default/page.published.json");
+ Path defaultFile = defaultTemplatesDir(home).resolve("page.published.json");
assertThat(defaultFile).isRegularFile();
}
@@ -124,7 +126,7 @@ void shouldRefuseDeleteOfDefaultTemplate(@TempDir Path tempDir) throws Exception
void shouldRefuseDeleteOfRegisteredTemplate(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
Files.createDirectories(home);
- Path file = home.resolve("registered.json");
+ Path file = contextFile(home, "registered.json");
Files.writeString(file, sampleTemplate("com.example.reg.v1"));
exec("settings", "event-templates", "register",
"--streamx-home", home.toString(),
@@ -134,7 +136,7 @@ void shouldRefuseDeleteOfRegisteredTemplate(@TempDir Path tempDir) throws Except
"settings", "event-templates", "delete",
"--streamx-home", home.toString(),
"my.alias",
- "--yes"
+ "--force"
);
assertThat(result.exitCode()).isNotZero();
@@ -151,7 +153,7 @@ void shouldFailForUnknownTemplate(@TempDir Path tempDir) throws Exception {
"settings", "event-templates", "delete",
"--streamx-home", home.toString(),
"definitely.does.not.exist",
- "--yes"
+ "--force"
);
assertThat(result.exitCode()).isNotZero();
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/edit/EditCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/edit/EditCommandIT.java
index 62162633..97dd42e7 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/edit/EditCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/edit/EditCommandIT.java
@@ -1,10 +1,11 @@
package com.streamx.cli.commands.settings.eventtemplates.edit;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.defaultTemplatesDir;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.userTemplatesDir;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.streamx.cli.commands.publish.event.UserEventTemplates;
import com.streamx.cli.test.CliBaseIT;
import io.quarkus.test.junit.QuarkusTest;
import java.nio.file.Files;
@@ -34,7 +35,7 @@ void clearEditor() {
@Test
void shouldWorkWithJsonOutput(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path target = userDir.resolve("my.thing.json");
Files.writeString(target, "{\"type\":\"x\"}");
@@ -65,19 +66,19 @@ void shouldCopyDefaultIntoUserFolderOnFirstEdit(@TempDir Path tempDir) throws Ex
result.assertSuccess();
- Path userCopy = home.resolve(UserEventTemplates.DIRECTORY).resolve("asset.published.json");
+ Path userCopy = userTemplatesDir(home).resolve("asset.published.json");
assertThat(userCopy).isRegularFile();
String content = Files.readString(userCopy);
assertThat(content).contains("com.streamx.blueprints.asset.published.v1");
- Path defaultFile = home.resolve("event-templates/default/asset.published.json");
+ Path defaultFile = defaultTemplatesDir(home).resolve("asset.published.json");
assertThat(defaultFile).isRegularFile();
}
@Test
void shouldEditUserTemplateInPlace(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path target = userDir.resolve("my.thing.json");
Files.writeString(target, "{\"type\":\"x\"}");
@@ -104,7 +105,7 @@ void shouldEditViaPrompt(@TempDir Path tempDir) throws Exception {
);
result.assertSuccess();
- Path userCopy = home.resolve(UserEventTemplates.DIRECTORY).resolve("page.published.json");
+ Path userCopy = userTemplatesDir(home).resolve("page.published.json");
assertThat(userCopy).isRegularFile();
}
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/list/ListCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/list/ListCommandIT.java
index 42ef2d64..2ca9e45a 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/list/ListCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/list/ListCommandIT.java
@@ -4,12 +4,11 @@
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.YAML;
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.findById;
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.sampleTemplate;
-import static com.streamx.cli.i18n.MessageProvider.msg;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.userTemplatesDir;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.JsonNode;
import com.streamx.cli.commands.publish.event.EventTemplateCatalog;
-import com.streamx.cli.commands.publish.event.UserEventTemplates;
import com.streamx.cli.test.CliBaseIT;
import io.quarkus.test.junit.QuarkusTest;
import java.nio.file.Files;
@@ -53,7 +52,7 @@ void shouldListBuiltinTemplatesAsJson(@TempDir Path tempDir) throws Exception {
@Test
void shouldShowUserTemplateFromEventTemplatesFolder(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(userDir.resolve("my.custom.json"), sampleTemplate("com.example.custom.v1"));
@@ -76,7 +75,7 @@ void shouldPrioritizeUserOverDefaults(@TempDir Path tempDir) throws Exception {
exec("settings", "event-templates", "list",
"--streamx-home", home.toString()).assertSuccess();
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(
userDir.resolve("page.published.json"),
@@ -109,13 +108,13 @@ void shouldPrioritizeSettingsOverUserAndDefaults(@TempDir Path tempDir) throws E
exec("settings", "event-templates", "list",
"--streamx-home", home.toString()).assertSuccess();
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(
userDir.resolve("page.published.json"),
sampleTemplate("com.example.user.page.v1"));
- Path settingsFile = home.resolve("page-from-settings.json");
+ Path settingsFile = home.resolve("contexts/default/page-from-settings.json");
Files.writeString(settingsFile, sampleTemplate("com.example.settings.page.v1"));
exec("settings", "event-templates", "register",
@@ -142,7 +141,6 @@ void shouldRenderTextOutput(@TempDir Path tempDir) throws Exception {
ProcessResult result = exec("settings", "event-templates", "list",
"--streamx-home", home.toString());
result.assertSuccess();
- assertThat(result.stdout()).contains(msg.eventTemplatesListHeader().strip());
assertThat(result.stdout()).contains("TEMPLATE ID");
assertThat(result.stdout()).contains("page.published");
}
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/register/RegisterCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/register/RegisterCommandIT.java
index 6eb0e6ad..af9f6729 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/register/RegisterCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/register/RegisterCommandIT.java
@@ -1,5 +1,7 @@
package com.streamx.cli.commands.settings.eventtemplates.register;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.configFile;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.contextFile;
import static org.assertj.core.api.Assertions.assertThat;
import com.streamx.cli.commands.publish.event.EventTemplateLoader;
@@ -19,7 +21,7 @@ class RegisterCommandIT extends CliBaseIT {
void shouldWriteSettingsEntry(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
Files.createDirectories(home);
- Path templateFile = home.resolve("custom.json");
+ Path templateFile = contextFile(home, "custom.json");
Files.writeString(templateFile, "{}");
ProcessResult result = exec(
@@ -31,7 +33,7 @@ void shouldWriteSettingsEntry(@TempDir Path tempDir) throws Exception {
result.assertSuccess();
- Path config = home.resolve("config/application.properties");
+ Path config = configFile(home);
assertThat(config).isRegularFile();
Properties props = new Properties();
try (InputStream is = Files.newInputStream(config)) {
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/rename/RenameCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/rename/RenameCommandIT.java
index bfe1f8ad..a212bb84 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/rename/RenameCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/rename/RenameCommandIT.java
@@ -1,12 +1,14 @@
package com.streamx.cli.commands.settings.eventtemplates.rename;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.configFile;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.contextFile;
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.sampleTemplate;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.userTemplatesDir;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.streamx.cli.commands.publish.event.EventTemplateLoader;
-import com.streamx.cli.commands.publish.event.UserEventTemplates;
import com.streamx.cli.test.CliBaseIT;
import io.quarkus.test.junit.QuarkusTest;
import java.io.InputStream;
@@ -24,7 +26,7 @@ class RenameCommandIT extends CliBaseIT {
@Test
void shouldWorkWithJsonOutput(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(userDir.resolve("old.json"), sampleTemplate("com.example.v1"));
@@ -46,7 +48,7 @@ void shouldWorkWithJsonOutput(@TempDir Path tempDir) throws Exception {
@Test
void shouldRenameUserTemplateFile(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path original = userDir.resolve("old.json");
String content = sampleTemplate("com.example.v1");
@@ -69,7 +71,7 @@ void shouldRenameUserTemplateFile(@TempDir Path tempDir) throws Exception {
void shouldRenameSettingsRegisteredTemplate(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
Files.createDirectories(home);
- Path file = home.resolve("registered.json");
+ Path file = contextFile(home, "registered.json");
Files.writeString(file, sampleTemplate("com.example.reg.v1"));
exec("settings", "event-templates", "register",
"--streamx-home", home.toString(),
@@ -112,7 +114,7 @@ void shouldRefuseToRenameDefault(@TempDir Path tempDir) throws Exception {
@Test
void shouldRefuseToRenameWhenNewIdAlreadyExists(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(userDir.resolve("a.json"), sampleTemplate("com.example.a.v1"));
Files.writeString(userDir.resolve("b.json"), sampleTemplate("com.example.b.v1"));
@@ -128,7 +130,7 @@ void shouldRefuseToRenameWhenNewIdAlreadyExists(@TempDir Path tempDir) throws Ex
}
private static Properties readConfig(Path home) throws Exception {
- Path config = home.resolve("config/application.properties");
+ Path config = configFile(home);
Properties props = new Properties();
try (InputStream is = Files.newInputStream(config)) {
props.load(is);
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/resetdefaulttemplates/ResetDefaultTemplatesCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/resetdefaulttemplates/ResetDefaultTemplatesCommandIT.java
index f3d926a5..6827f51f 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/resetdefaulttemplates/ResetDefaultTemplatesCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/resetdefaulttemplates/ResetDefaultTemplatesCommandIT.java
@@ -2,6 +2,7 @@
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.JSON;
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.YAML;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.userTemplatesDir;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.JsonNode;
@@ -104,7 +105,7 @@ void shouldResetWithoutPromptWhenYesFlagSet(@TempDir Path tempDir) throws Except
ProcessResult result = exec(
"settings", "event-templates", "reset-default-templates",
"--streamx-home", home.toString(),
- "--yes"
+ "--force"
);
result.assertSuccess();
@@ -121,7 +122,7 @@ void shouldRecreateMissingDefaultsDir(@TempDir Path tempDir) throws Exception {
ProcessResult result = exec(
"settings", "event-templates", "reset-default-templates",
"--streamx-home", home.toString(),
- "--yes"
+ "--force"
);
result.assertSuccess();
@@ -138,7 +139,7 @@ void shouldOutputJson(@TempDir Path tempDir) throws Exception {
ProcessResult result = exec(
"settings", "event-templates", "reset-default-templates",
"--streamx-home", home.toString(),
- "--yes",
+ "--force",
"--output", "json"
);
@@ -167,7 +168,7 @@ void shouldOutputYaml(@TempDir Path tempDir) throws Exception {
ProcessResult result = exec(
"settings", "event-templates", "reset-default-templates",
"--streamx-home", home.toString(),
- "--yes",
+ "--force",
"--output", "yaml"
);
@@ -180,7 +181,7 @@ void shouldOutputYaml(@TempDir Path tempDir) throws Exception {
@Test
void shouldNotTouchUserEventTemplatesFolder(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve("event-templates/custom");
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Path userFile = userDir.resolve("my.custom.json");
Files.writeString(userFile, "{\"type\":\"user\"}");
@@ -188,7 +189,7 @@ void shouldNotTouchUserEventTemplatesFolder(@TempDir Path tempDir) throws Except
ProcessResult result = exec(
"settings", "event-templates", "reset-default-templates",
"--streamx-home", home.toString(),
- "--yes"
+ "--force"
);
result.assertSuccess();
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/unregister/UnregisterCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/unregister/UnregisterCommandIT.java
index cd6082cd..03835334 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/unregister/UnregisterCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/unregister/UnregisterCommandIT.java
@@ -1,5 +1,8 @@
package com.streamx.cli.commands.settings.eventtemplates.unregister;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.configFile;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.contextFile;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.defaultTemplatesDir;
import static org.assertj.core.api.Assertions.assertThat;
import com.streamx.cli.commands.publish.event.EventTemplateLoader;
@@ -19,7 +22,7 @@ class UnregisterCommandIT extends CliBaseIT {
void shouldRemoveSettingsEntryByName(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
Files.createDirectories(home);
- Files.writeString(home.resolve("custom.json"), "{}");
+ Files.writeString(contextFile(home, "custom.json"), "{}");
exec("settings", "event-templates", "register",
"--streamx-home", home.toString(),
@@ -43,8 +46,8 @@ void shouldRemoveSettingsEntryByName(@TempDir Path tempDir) throws Exception {
void shouldRemoveSettingsEntryViaPrompt(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
Files.createDirectories(home);
- Files.writeString(home.resolve("a.json"), "{}");
- Files.writeString(home.resolve("b.json"), "{}");
+ Files.writeString(contextFile(home, "a.json"), "{}");
+ Files.writeString(contextFile(home, "b.json"), "{}");
exec("settings", "event-templates", "register",
"--streamx-home", home.toString(),
@@ -72,7 +75,7 @@ void shouldRemoveSettingsEntryViaPrompt(@TempDir Path tempDir) throws Exception
void shouldRefuseUnknownName(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
Files.createDirectories(home);
- Files.writeString(home.resolve("a.json"), "{}");
+ Files.writeString(contextFile(home, "a.json"), "{}");
exec("settings", "event-templates", "register",
"--streamx-home", home.toString(),
"real.one", "a.json").assertSuccess();
@@ -104,13 +107,13 @@ void shouldRefuseWhenNoRegistrationsExist(@TempDir Path tempDir) throws Exceptio
void shouldNotTouchDefaultsFolder(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
Files.createDirectories(home);
- Files.writeString(home.resolve("a.json"), "{}");
+ Files.writeString(contextFile(home, "a.json"), "{}");
exec("settings", "event-templates", "register",
"--streamx-home", home.toString(),
"my.alias", "a.json").assertSuccess();
- Path defaultPagePublished = home.resolve("event-templates/default/page.published.json");
+ Path defaultPagePublished = defaultTemplatesDir(home).resolve("page.published.json");
assertThat(defaultPagePublished).isRegularFile();
exec("settings", "event-templates", "unregister",
@@ -121,7 +124,7 @@ void shouldNotTouchDefaultsFolder(@TempDir Path tempDir) throws Exception {
}
private static Properties readConfig(Path home) throws Exception {
- Path config = home.resolve("config/application.properties");
+ Path config = configFile(home);
Properties props = new Properties();
try (InputStream is = Files.newInputStream(config)) {
props.load(is);
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/validate/ValidateCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/validate/ValidateCommandIT.java
index 96156f4d..44d6e19b 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/validate/ValidateCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/validate/ValidateCommandIT.java
@@ -2,10 +2,10 @@
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.JSON;
import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.sampleTemplate;
+import static com.streamx.cli.commands.settings.eventtemplates.EventTemplatesTestSupport.userTemplatesDir;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.JsonNode;
-import com.streamx.cli.commands.publish.event.UserEventTemplates;
import com.streamx.cli.test.CliBaseIT;
import io.quarkus.test.junit.QuarkusTest;
import java.nio.file.Files;
@@ -31,7 +31,7 @@ void shouldValidateBundledDefault(@TempDir Path tempDir) throws Exception {
@Test
void shouldFailOnInvalidJson(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(userDir.resolve("broken.json"), "{ this is not json");
@@ -48,7 +48,7 @@ void shouldFailOnInvalidJson(@TempDir Path tempDir) throws Exception {
@Test
void shouldFailOnMissingRequiredField(@TempDir Path tempDir) throws Exception {
Path home = tempDir.resolve("streamx-home");
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(userDir.resolve("nospec.json"), "{\"id\":\"x\",\"source\":\"s\"}");
@@ -89,7 +89,7 @@ void shouldReportMixedResultsWithAllFlag(@TempDir Path tempDir) throws Exception
exec("settings", "event-templates", "list",
"--streamx-home", home.toString()).assertSuccess();
- Path userDir = home.resolve(UserEventTemplates.DIRECTORY);
+ Path userDir = userTemplatesDir(home);
Files.createDirectories(userDir);
Files.writeString(userDir.resolve("good.json"), sampleTemplate("com.example.good.v1"));
Files.writeString(userDir.resolve("bad.json"), "{}");
diff --git a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/which/WhichCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/which/WhichCommandIT.java
index 04847e1c..510b3ae8 100644
--- a/src/test/java/com/streamx/cli/commands/settings/eventtemplates/which/WhichCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/eventtemplates/which/WhichCommandIT.java
@@ -23,7 +23,7 @@ void shouldPrintAbsolutePathOfDefault(@TempDir Path tempDir) throws Exception {
);
result.assertSuccess();
String stdout = result.stdout().strip();
- assertThat(stdout).endsWith("event-templates/default/page.published.json");
+ assertThat(stdout).endsWith("default-event-templates/page.published.json");
assertThat(Path.of(stdout)).isAbsolute();
}
@@ -51,7 +51,7 @@ void shouldOutputJsonWithFullLocation(@TempDir Path tempDir) throws Exception {
result.assertSuccess();
JsonNode root = JSON.readTree(result.stdout());
assertThat(root.get("id").asText()).isEqualTo("page.published");
- assertThat(root.get("source").asText()).isEqualTo("event-templates/default");
+ assertThat(root.get("source").asText()).isEqualTo("default");
assertThat(root.get("path").asText()).endsWith("page.published.json");
}
}
diff --git a/src/test/java/com/streamx/cli/commands/settings/list/ListCommandIT.java b/src/test/java/com/streamx/cli/commands/settings/list/ListCommandIT.java
index 44757524..6b6d5ba9 100644
--- a/src/test/java/com/streamx/cli/commands/settings/list/ListCommandIT.java
+++ b/src/test/java/com/streamx/cli/commands/settings/list/ListCommandIT.java
@@ -48,12 +48,13 @@ void shouldFormatOutputAsText() throws Exception {
ProcessResult result = exec("settings", "list");
- String expectedOutput = msg.listSettingsHeader() + "\n" + """
- another.key = another.value
- empty.value =
- spaced.value = value with spaces
- special.chars = value=with:special@chars!
- test.key = test.value
+ String expectedOutput = """
+ KEY VALUE
+ another.key another.value
+ empty.value -
+ spaced.value value with spaces
+ special.chars value=with:special@chars!
+ test.key test.value
""".strip();
assertThat(result.stdout().strip()).isEqualTo(expectedOutput);
diff --git a/src/test/java/com/streamx/cli/config/StreamxHomeTest.java b/src/test/java/com/streamx/cli/config/StreamxHomeTest.java
index 5c3bbfef..92d3f961 100644
--- a/src/test/java/com/streamx/cli/config/StreamxHomeTest.java
+++ b/src/test/java/com/streamx/cli/config/StreamxHomeTest.java
@@ -23,6 +23,7 @@ class StreamxHomeTest {
void cleanup() {
System.clearProperty("STREAMX_HOME");
StreamxHome.clearStreamxHomeCliArg();
+ StreamxHome.clearContextCliArg();
}
@Test
@@ -36,7 +37,7 @@ void shouldUseStreamxHomeEnvVariable() throws Exception {
URL url = StreamxHome.getConfigUrl();
Path result = Path.of(url.toURI());
- assertEquals(tempDir.resolve("config/application.properties"), result);
+ assertEquals(tempDir.resolve("contexts/default/config/application.properties"), result);
assertTrue(Files.exists(result));
}
}
@@ -49,7 +50,7 @@ void shouldUseStreamxHomeSystemProperty() throws Exception {
URL url = StreamxHome.getConfigUrl();
Path result = Path.of(url.toURI());
- assertEquals(tempDir.resolve("config/application.properties"), result);
+ assertEquals(tempDir.resolve("contexts/default/config/application.properties"), result);
assertTrue(Files.exists(result), "application.properties should be created");
}
@@ -73,7 +74,7 @@ void shouldCreateConfigDirectoryWhenItDoesNotExist() throws Exception {
StreamxHome.createConfigIfNotExists();
- Path configDir = homeDir.resolve("config");
+ Path configDir = homeDir.resolve("contexts/default/config");
assertTrue(Files.isDirectory(configDir), "Config directory should be created");
assertTrue(Files.exists(configDir.resolve("application.properties")));
}
@@ -86,7 +87,7 @@ void shouldApplySettingsToSystemProperties() throws Exception {
System.clearProperty(key);
System.clearProperty(otherKey);
try {
- Path configDir = tempDir.resolve("config");
+ Path configDir = tempDir.resolve("contexts/default/config");
Files.createDirectories(configDir);
Files.writeString(configDir.resolve("application.properties"),
key + "=true\n" + otherKey + "=8081\n");
@@ -107,7 +108,7 @@ void shouldNotOverrideExplicitlySetSystemProperty() throws Exception {
String key = "streamx.runner.gateway.http-port";
System.setProperty(key, "9999");
try {
- Path configDir = tempDir.resolve("config");
+ Path configDir = tempDir.resolve("contexts/default/config");
Files.createDirectories(configDir);
Files.writeString(configDir.resolve("application.properties"), key + "=8081\n");
@@ -125,7 +126,7 @@ void shouldClearStaleAppliedKeysOnReapply() throws Exception {
String key = "streamx.runner.gateway.http-port";
System.clearProperty(key);
try {
- Path configDir = tempDir.resolve("config");
+ Path configDir = tempDir.resolve("contexts/default/config");
Files.createDirectories(configDir);
Path configFile = configDir.resolve("application.properties");
diff --git a/src/test/java/com/streamx/cli/framework/UrlsTest.java b/src/test/java/com/streamx/cli/framework/UrlsTest.java
new file mode 100644
index 00000000..7b64d9fa
--- /dev/null
+++ b/src/test/java/com/streamx/cli/framework/UrlsTest.java
@@ -0,0 +1,43 @@
+package com.streamx.cli.framework;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class UrlsTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "http://example.com",
+ "http://example.com:8080/api",
+ "http://10.0.0.5",
+ "HTTP://EXAMPLE.COM",
+ "http://127.0.0.1.evil.example",
+ "http://127.evil.example",
+ "http://my_host.example",
+ "http://localhost@evil.example/",
+ "http://",
+ "http:// bad url"
+ })
+ void cleartextToRemoteOrUnprovableHostsIsBlocked(String url) {
+ assertThat(Urls.isCleartextRemote(url)).isTrue();
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "https://example.com",
+ "https://keycloak.127.0.0.1.nip.io",
+ "http://localhost:8085",
+ "http://LOCALHOST:8080",
+ "http://127.0.0.1:8080",
+ "http://127.1.2.3",
+ "http://[::1]:8080",
+ "not a url",
+ "ftp://example.com"
+ })
+ void httpsAndProvableLoopbackAndOtherSchemesAreAllowed(String url) {
+ assertThat(Urls.isCleartextRemote(url)).isFalse();
+ }
+}
diff --git a/src/test/java/com/streamx/cli/test/CliBaseIT.java b/src/test/java/com/streamx/cli/test/CliBaseIT.java
index ba49fc2f..b5dcf23d 100644
--- a/src/test/java/com/streamx/cli/test/CliBaseIT.java
+++ b/src/test/java/com/streamx/cli/test/CliBaseIT.java
@@ -31,7 +31,7 @@ public abstract class CliBaseIT {
private static final long DEFAULT_TIMEOUT_SECONDS = 30;
protected static final String CONFIG_FILE_PATH =
- "config/application.properties";
+ "contexts/default/config/application.properties";
@TempDir
public static Path streamxHome;
@@ -171,7 +171,9 @@ public K create(Class cls) throws Exception {
if (command instanceof AbstractCommand> abstractCommand) {
try {
- abstractCommand.populateStreamxHome();
+ abstractCommand.populateStreamxHome(parsed);
+ // -H/--context are applied now; refresh the root help header to reflect them.
+ com.streamx.cli.framework.SynopsisHelper.applyRootUsageLayout(parsed.get(0));
} catch (Exception e) {
return abstractCommand.handleExecutionError(e);
}
@@ -192,6 +194,7 @@ public K create(Class cls) throws Exception {
return new CommandLine.RunLast().execute(parseResult);
});
+ com.streamx.cli.framework.SynopsisHelper.applyRootUsageLayout(cmd);
return cmd;
}
diff --git a/src/test/java/com/streamx/cli/test/MeshAssertions.java b/src/test/java/com/streamx/cli/test/MeshAssertions.java
index 4641abd8..eebde2c6 100644
--- a/src/test/java/com/streamx/cli/test/MeshAssertions.java
+++ b/src/test/java/com/streamx/cli/test/MeshAssertions.java
@@ -25,7 +25,7 @@ public static synchronized void assertEventsPublished(long count) {
prevEventCount.set(topicMessageCount);
} catch (PulsarAdminException e) {
throw new AssertionError(
- "Failed to retrieve stats for topic: " + PULSAR_TOPIC + " — " + e.getMessage(), e);
+ "Failed to retrieve stats for topic: " + PULSAR_TOPIC + " - " + e.getMessage(), e);
} catch (Exception e) {
throw new RuntimeException(
"Failed to create Pulsar admin client: " + e.getMessage(), e);
@@ -38,7 +38,7 @@ public static synchronized void resetPublishedEventsBaseline() {
TopicStats stats = admin.topics().getStats(PULSAR_TOPIC);
prevEventCount.set(stats.getMsgInCounter());
} catch (Exception e) {
- // ignore — topic may not exist yet
+ // ignore - topic may not exist yet
}
}
}