This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch UNOMI-986-oneshot-import-tenant-dir
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to 
refs/heads/UNOMI-986-oneshot-import-tenant-dir by this push:
     new 008f19070 UNOMI-986: Reject a one-shot import that has no tenant 
context
008f19070 is described below

commit 008f1907062976e868f297ac641069776966f3a2
Author: Serge Huber <[email protected]>
AuthorDate: Sun Sep 27 09:41:52 2026 +0200

    UNOMI-986: Reject a one-shot import that has no tenant context
    
    A missing execution context was an uncaught NullPointerException. Return 
400 instead, and assert the CSV is written under the current tenant directory.
---
 extensions/router/router-rest/pom.xml              |  5 ++
 .../rest/ImportConfigurationServiceEndPoint.java   |  9 ++-
 .../rest/ImportConfigurationOneshotUploadTest.java | 82 ++++++++++++++++++++++
 3 files changed, 95 insertions(+), 1 deletion(-)

diff --git a/extensions/router/router-rest/pom.xml 
b/extensions/router/router-rest/pom.xml
index 5d7f84404..ef2c4b848 100644
--- a/extensions/router/router-rest/pom.xml
+++ b/extensions/router/router-rest/pom.xml
@@ -113,6 +113,11 @@
             <artifactId>junit-jupiter</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java
 
b/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java
index 759bdb0e5..8c7e4b1ef 100644
--- 
a/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java
+++ 
b/extensions/router/router-rest/src/main/java/org/apache/unomi/router/rest/ImportConfigurationServiceEndPoint.java
@@ -19,6 +19,7 @@ package org.apache.unomi.router.rest;
 import org.apache.cxf.jaxrs.ext.multipart.Attachment;
 import org.apache.cxf.jaxrs.ext.multipart.Multipart;
 import org.apache.cxf.rs.security.cors.CrossOriginResourceSharing;
+import org.apache.unomi.api.ExecutionContext;
 import org.apache.unomi.api.security.UnomiRoles;
 import org.apache.unomi.api.services.ConfigSharingService;
 import org.apache.unomi.api.services.ExecutionContextManager;
@@ -150,6 +151,7 @@ public class ImportConfigurationServiceEndPoint extends 
AbstractConfigurationSer
      * @param file the CSV file upload (multipart field)
      * @return an empty success response
      * @api.status 200 empty CSV uploaded and ready for processing.
+     * @api.status 400 empty No current tenant context.
      * @api.status 500 empty Error writing the uploaded file.
      * @api.example {}
      */
@@ -159,11 +161,16 @@ public class ImportConfigurationServiceEndPoint extends 
AbstractConfigurationSer
     @Produces(MediaType.APPLICATION_JSON)
     public Response processOneshotImportConfigurationCSV(@Multipart(value = 
"importConfigId") @NotNull @Pattern(regexp = "^[a-zA-Z0-9_.\\-]{1,255}$") 
String importConfigId,
                                                          @Multipart(value = 
"file") Attachment file) {
+        ExecutionContext context = executionContextManager.getCurrentContext();
+        if (context == null || context.getTenantId() == null || 
context.getTenantId().trim().isEmpty()) {
+            LOGGER.warn("One-shot import refused: no current tenant");
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+        String tenantId = context.getTenantId();
         try {
             // The Camel route reads the tenant from the directory holding the 
file, so the upload
             // writes it there. Without that directory 
ImportConfigByFileNameProcessor answers
             // "Invalid or missing tenant ID in path" and drops the file, and 
the import never runs.
-            String tenantId = 
executionContextManager.getCurrentContext().getTenantId();
             java.nio.file.Path tenantDir = 
Paths.get(String.valueOf(configSharingService.getProperty(RouterConstants.IMPORT_ONESHOT_UPLOAD_DIR)),
 tenantId);
             Files.createDirectories(tenantDir);
             java.nio.file.Path path = tenantDir.resolve(importConfigId + 
".csv");
diff --git 
a/extensions/router/router-rest/src/test/java/org/apache/unomi/router/rest/ImportConfigurationOneshotUploadTest.java
 
b/extensions/router/router-rest/src/test/java/org/apache/unomi/router/rest/ImportConfigurationOneshotUploadTest.java
new file mode 100644
index 000000000..86414903d
--- /dev/null
+++ 
b/extensions/router/router-rest/src/test/java/org/apache/unomi/router/rest/ImportConfigurationOneshotUploadTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.unomi.router.rest;
+
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.unomi.api.ExecutionContext;
+import org.apache.unomi.api.services.ConfigSharingService;
+import org.apache.unomi.api.services.ExecutionContextManager;
+import org.apache.unomi.router.api.RouterConstants;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import javax.ws.rs.core.Response;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * The one-shot upload must land where the Camel route reads it: under the 
current tenant.
+ */
+class ImportConfigurationOneshotUploadTest {
+
+    @TempDir
+    Path uploadDir;
+
+    @Test
+    void writesTheCsvUnderTheCurrentTenantDirectory() throws Exception {
+        ImportConfigurationServiceEndPoint endpoint = endpointWith(new 
ExecutionContext("acme", null, null));
+        Attachment file = mock(Attachment.class);
+        doReturn(new 
ByteArrayInputStream("email\n".getBytes(StandardCharsets.UTF_8)))
+                .when(file).getObject(InputStream.class);
+
+        Response response = 
endpoint.processOneshotImportConfigurationCSV("profile-csv", file);
+
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+        Path written = uploadDir.resolve("acme").resolve("profile-csv.csv");
+        assertEquals("email\n", new String(Files.readAllBytes(written), 
StandardCharsets.UTF_8));
+    }
+
+    @Test
+    void missingContextIsRejectedWithoutWriting() throws Exception {
+        ImportConfigurationServiceEndPoint endpoint = endpointWith(null);
+
+        Response response = 
endpoint.processOneshotImportConfigurationCSV("profile-csv", 
mock(Attachment.class));
+
+        assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), 
response.getStatus());
+        assertFalse(Files.exists(uploadDir.resolve("profile-csv.csv")));
+    }
+
+    private ImportConfigurationServiceEndPoint endpointWith(ExecutionContext 
context) throws Exception {
+        ImportConfigurationServiceEndPoint endpoint = new 
ImportConfigurationServiceEndPoint();
+        ConfigSharingService configSharingService = 
mock(ConfigSharingService.class);
+        
when(configSharingService.getProperty(RouterConstants.IMPORT_ONESHOT_UPLOAD_DIR)).thenReturn(uploadDir.toString());
+        ExecutionContextManager executionContextManager = 
mock(ExecutionContextManager.class);
+        when(executionContextManager.getCurrentContext()).thenReturn(context);
+        endpoint.setConfigSharingService(configSharingService);
+        endpoint.setExecutionContextManager(executionContextManager);
+        return endpoint;
+    }
+}

Reply via email to