From 502b9c9f7831e3169019624b33888fd10b9d8510 Mon Sep 17 00:00:00 2001 From: Iliyan Velichkov Date: Fri, 11 Sep 2026 14:51:07 +0300 Subject: [PATCH] ide-workspace: a project descriptor without an actions section is not an error (#7286) A `project.json` that omits the `actions` key is the normal case - every intent-generated project, every sample, every hand-written descriptor that declares only a guid and its dependencies. `ActionsService` logged it at ERROR from both of its read sites, so a single Publish emitted two of these lines (`ProjectActionsPublisherHandler` calls `listRegisteredActions` in `beforePublishProject` and again in `afterPublishProject`), and a CI shard publishing a handful of projects filled the Logs view with them. Nothing had failed and nothing was retried. The message also concatenated the project name instead of using an SLF4J `{}` placeholder. `listRegisteredActions` now says nothing: an empty list is its documented return, and the caller iterates it either way. `executeAction` keeps a line - the caller asked for an action by name - but at `debug`, with a placeholder. Return values are unchanged on both paths. Verified: `ActionsServiceTest#projectWithoutActionsSectionIsNotAnError` is a new regression test that publishes a descriptor without an actions section and asserts the service logs nothing at ERROR; against the unfixed service it fails with both messages captured, and passes with the change. The ide-workspace unit suite is green (35 tests), `mvn -T 1C formatter:validate` is green with the formatter cache wiped, and `CamelDirigibleJavaScriptComponentHttpRouteIT` (a publish-exercising integration test) is green. Fixes #7286 Co-Authored-By: Claude Opus 5 (1M context) --- .../ide/workspace/service/ActionsService.java | 3 +- .../workspace/service/ActionsServiceTest.java | 82 +++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/components/ide/ide-workspace/src/main/java/org/eclipse/dirigible/components/ide/workspace/service/ActionsService.java b/components/ide/ide-workspace/src/main/java/org/eclipse/dirigible/components/ide/workspace/service/ActionsService.java index 0afbd394b71..de2dfdf02a9 100644 --- a/components/ide/ide-workspace/src/main/java/org/eclipse/dirigible/components/ide/workspace/service/ActionsService.java +++ b/components/ide/ide-workspace/src/main/java/org/eclipse/dirigible/components/ide/workspace/service/ActionsService.java @@ -68,7 +68,7 @@ public int executeAction(String workspace, String project, String action) { ProjectMetadata projectJson = GsonHelper.fromJson(new String(fileObject.getContent()), ProjectMetadata.class); List actions = projectJson.getActions(); if (actions == null) { - logger.error("Actions section not found in the project descriptor file: " + project); + logger.debug("No actions section in the project descriptor file of project [{}]", project); } else { ProjectAction projectAction = actions.stream() .filter(a -> a.getName() @@ -196,7 +196,6 @@ public List listRegisteredActions(String workspace, String projec ProjectMetadata projectJson = GsonHelper.fromJson(new String(fileObject.getContent()), ProjectMetadata.class); List actions = projectJson.getActions(); if (actions == null) { - logger.error("Actions section not found in the project descriptor file: " + project); return new ArrayList(); } return actions; diff --git a/components/ide/ide-workspace/src/test/java/org/eclipse/dirigible/components/ide/workspace/service/ActionsServiceTest.java b/components/ide/ide-workspace/src/test/java/org/eclipse/dirigible/components/ide/workspace/service/ActionsServiceTest.java index 33edab12668..7b876fa5f15 100644 --- a/components/ide/ide-workspace/src/test/java/org/eclipse/dirigible/components/ide/workspace/service/ActionsServiceTest.java +++ b/components/ide/ide-workspace/src/test/java/org/eclipse/dirigible/components/ide/workspace/service/ActionsServiceTest.java @@ -11,12 +11,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.apache.commons.lang3.SystemUtils; import org.eclipse.dirigible.commons.config.Configuration; import org.eclipse.dirigible.components.ide.workspace.domain.File; import org.eclipse.dirigible.components.ide.workspace.domain.Project; import org.eclipse.dirigible.components.ide.workspace.domain.Workspace; +import org.eclipse.dirigible.components.project.ProjectAction; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -27,6 +31,14 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; /** * The Class WorkspacesCoreServiceTest. @@ -48,6 +60,12 @@ public class ActionsServiceTest { @Autowired private WorkspaceService workspaceService; + /** Captures what the service logs while a test runs. */ + private ListAppender appender; + + /** The logger of the service under test. */ + private Logger serviceLogger; + /** The project json content. */ private static final String PROJECT_JSON_CONTENT = """ { @@ -69,6 +87,34 @@ public class ActionsServiceTest { } """; + /** A descriptor without an actions section - what a project that declares none looks like. */ + private static final String PROJECT_JSON_WITHOUT_ACTIONS = """ + { + "guid": "TestProject2" + } + """; + + /** + * Attaches the log appender. Spring re-initializes logback while the application context starts, so + * the appender is attached per test rather than in a field initializer. + */ + @BeforeEach + public void attachLogAppender() { + appender = new ListAppender<>(); + appender.start(); + serviceLogger = (Logger) LoggerFactory.getLogger(ActionsService.class); + serviceLogger.addAppender(appender); + } + + /** + * Detaches the log appender. + */ + @AfterEach + public void detachLogAppender() { + serviceLogger.detachAppender(appender); + appender.stop(); + } + /** * Publish with action test. @@ -127,6 +173,42 @@ public void publishWithoutActionTest() { } } + /** + * A project descriptor without an actions section is the normal case - it yields no actions and + * must not be reported as an error. + */ + @Test + public void projectWithoutActionsSectionIsNotAnError() { + workspaceService.createWorkspace("TestWorkspace2"); + workspaceService.createProject("TestWorkspace2", "TestProject2"); + workspaceService.createFile("TestWorkspace2", "TestProject2", "project.json", PROJECT_JSON_WITHOUT_ACTIONS.getBytes(), + "application/json"); + try { + List actions = actionsService.listRegisteredActions("TestWorkspace2", "TestProject2"); + assertTrue(actions.isEmpty()); + + int result = actionsService.executeAction("TestWorkspace2", "TestProject2", "MyAction"); + assertEquals(-1, result); + + List errors = loggedErrors(); + assertTrue(errors.isEmpty(), "unexpected error logged: " + errors); + } finally { + workspaceService.deleteWorkspace("TestWorkspace2"); + } + } + + /** + * Logged errors. + * + * @return the messages the service under test logged at ERROR + */ + private List loggedErrors() { + return appender.list.stream() + .filter(event -> event.getLevel() == Level.ERROR) + .map(ILoggingEvent::getFormattedMessage) + .toList(); + } + /** * The Class TestConfiguration. */