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

tbonelee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new a15b82c711 [ZEPPELIN-5994] Fix cron tests for ZeppelinRestApiTest
a15b82c711 is described below

commit a15b82c711e730e2fac08cb73bf775cc604a786d
Author: namuuCY <[email protected]>
AuthorDate: Thu Aug 6 11:07:33 2026 +0900

    [ZEPPELIN-5994] Fix cron tests for ZeppelinRestApiTest
    
    ### What is this PR for?
    
    This PR fixes and re-enables the cron REST API tests that were disabled in 
`ZeppelinRestApiTest`.
    
    Cron scheduling is disabled for security when Zeppelin runs in anonymous 
mode, even if the cron configuration is enabled. This PR separates the cron 
tests by authentication mode:
    
    - Verify that cron creation and deletion are forbidden in anonymous mode.
    - Add `AuthenticatedCronRestApiTest`, which starts Zeppelin with Shiro 
authentication enabled.
    - Verify the cron lifecycle for an authenticated administrator.
    - Verify cron folder restrictions and invalid cron expression handling.
    
    
    ### What type of PR is it?
    
    Bug Fix
    
    
    ### Todos
    
    - [x] Verify that cron operations are rejected in anonymous mode.
    - [x] Add authenticated cron REST API tests.
    - [x] Test cron folder restrictions.
    - [x] Test valid and invalid cron expressions.
    
    ### What is the Jira issue?
    
    https://issues.apache.org/jira/browse/ZEPPELIN-5994
    
    ### How should this be tested?
    
    Run the focused REST API tests:
    
    ```bash
    ./mvnw -pl zeppelin-server \
      -Dtest=AuthenticatedCronRestApiTest \
      test
    
    ./mvnw -pl zeppelin-server \
      -Dtest='ZeppelinRestApiTest#testCronDisabledInAnonymousMode' \
      test
    ```
    
    ### Questions:
    - Does the license files need to update? No.
    - Is there breaking changes for older versions? No.
    - Does this needs documentation? No. This PR only updates test coverage.
    
    Closes #5367 from namuuCY/master.
    
    Signed-off-by: ChanHo Lee <[email protected]>
---
 .../rest/AuthenticatedCronRestApiTest.java         | 181 +++++++++++++++++++++
 .../apache/zeppelin/rest/ZeppelinRestApiTest.java  | 156 ++++--------------
 2 files changed, 210 insertions(+), 127 deletions(-)

diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AuthenticatedCronRestApiTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AuthenticatedCronRestApiTest.java
new file mode 100644
index 0000000000..3aff15621f
--- /dev/null
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/AuthenticatedCronRestApiTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.zeppelin.rest;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.zeppelin.MiniZeppelinServer;
+import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
+import org.apache.zeppelin.notebook.Notebook;
+import org.apache.zeppelin.notebook.Paragraph;
+import org.apache.zeppelin.user.AuthenticationInfo;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class AuthenticatedCronRestApiTest extends AbstractTestRestApi {
+  private static final String ADMIN_USER = "admin";
+  private static final String ADMIN_PASSWORD = "password1";
+  private static final String VALID_CRON_REQUEST = "{\"cron\":\"0 0 0 1 1 ? 
2099\"}";
+
+  private static MiniZeppelinServer zepServer;
+  private Notebook notebook;
+  private AuthenticationInfo admin;
+
+  @BeforeAll
+  static void init() throws Exception {
+    zepServer = new 
MiniZeppelinServer(AuthenticatedCronRestApiTest.class.getSimpleName());
+    zepServer.addConfigFile("shiro.ini", ZEPPELIN_SHIRO);
+    zepServer.addInterpreter("md");
+    zepServer.getZeppelinConfiguration().setProperty(
+        ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), "true");
+    zepServer.getZeppelinConfiguration().setProperty(
+        ConfVars.ZEPPELIN_NOTEBOOK_CRON_FOLDERS.getVarName(), "/System");
+    zepServer.start();
+  }
+
+  @AfterAll
+  static void destroy() throws Exception {
+    zepServer.destroy();
+  }
+
+  @BeforeEach
+  void setUp() {
+    zConf = zepServer.getZeppelinConfiguration();
+    notebook = zepServer.getService(Notebook.class);
+    admin = new AuthenticationInfo(ADMIN_USER);
+  }
+
+  @Test
+  void testCronForNonexistentNote() throws IOException {
+    try (
+        CloseableHttpResponse response =
+            httpPost(
+                "/notebook/cron/notexistnote",
+                VALID_CRON_REQUEST,
+                ADMIN_USER,
+                ADMIN_PASSWORD)) {
+      assertThat("", response, isNotFound());
+    }
+  }
+
+  @Test
+  void testCronLifecycleInConfiguredFolder() throws Exception {
+    String noteId = null;
+    try {
+      assertTrue(zConf.isAuthenticationEnabled());
+      assertTrue(zConf.isZeppelinNotebookCronEnable());
+      noteId = 
notebook.createNote("/System/testCronLifecycleInConfiguredFolder", admin);
+      notebook.processNote(noteId,
+          note -> {
+            assertNotNull(note, "can't create new note");
+            note.setName("testCronLifecycleInConfiguredFolder");
+            Paragraph paragraph = note.addNewParagraph(admin);
+            Map<String, Object> config = paragraph.getConfig();
+            config.put("enabled", true);
+            paragraph.setConfig(config);
+            paragraph.setText("%md This is test paragraph.");
+            notebook.saveNote(note, admin);
+            return null;
+          });
+
+      try (
+          CloseableHttpResponse response =
+              httpPost(
+                  "/notebook/cron/" + noteId,
+                  VALID_CRON_REQUEST,
+                  ADMIN_USER,
+                  ADMIN_PASSWORD)) {
+        assertThat("", response, isAllowed());
+      }
+
+      try (
+          CloseableHttpResponse response =
+              httpGet(
+                  "/notebook/cron/" + noteId,
+                  ADMIN_USER,
+                  ADMIN_PASSWORD)) {
+        assertThat("", response, isAllowed());
+      }
+
+      String invalidCronRequest = "{\"cron\":\"a * * * * ?\"}";
+      try (
+          CloseableHttpResponse response =
+              httpPost(
+                  "/notebook/cron/" + noteId,
+                  invalidCronRequest,
+                  ADMIN_USER,
+                  ADMIN_PASSWORD)) {
+        assertThat("", response, isBadRequest());
+      }
+
+      try (
+          CloseableHttpResponse response =
+              httpDelete(
+                  "/notebook/cron/" + noteId,
+                  ADMIN_USER,
+                  ADMIN_PASSWORD)) {
+        assertThat("", response, isAllowed());
+      }
+    } finally {
+      if (noteId != null) {
+        notebook.removeNote(noteId, admin);
+      }
+    }
+  }
+
+  @Test
+  void testCronRejectedOutsideConfiguredFolder() throws Exception {
+    String noteId = null;
+    try {
+      noteId = 
notebook.createNote("/Other/testCronRejectedOutsideConfiguredFolder", admin);
+      notebook.processNote(noteId,
+          note -> {
+            assertNotNull(note, "can't create new note");
+            note.setName("testCronRejectedOutsideConfiguredFolder");
+            Paragraph paragraph = note.addNewParagraph(admin);
+            Map<String, Object> config = paragraph.getConfig();
+            config.put("enabled", true);
+            paragraph.setConfig(config);
+            paragraph.setText("%md This is test paragraph.");
+            notebook.saveNote(note, admin);
+            return null;
+          });
+
+      try (
+          CloseableHttpResponse response =
+              httpPost(
+                  "/notebook/cron/" + noteId,
+                  VALID_CRON_REQUEST,
+                  ADMIN_USER,
+                  ADMIN_PASSWORD)) {
+        assertThat("", response, isForbidden());
+      }
+    } finally {
+      if (noteId != null) {
+        notebook.removeNote(noteId, admin);
+      }
+    }
+  }
+}
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
index 5b3977c2ed..385050ba77 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/rest/ZeppelinRestApiTest.java
@@ -29,7 +29,6 @@ import org.apache.zeppelin.test.DownloadUtils;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.MethodOrderer;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestMethodOrder;
@@ -54,6 +53,7 @@ import org.apache.zeppelin.user.AuthenticationInfo;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -78,6 +78,8 @@ class ZeppelinRestApiTest extends AbstractTestRestApi {
     zepServer.addInterpreter("sh");
     zepServer.addInterpreter("spark");
     zepServer.copyBinDir();
+    zepServer.getZeppelinConfiguration().setProperty(
+        ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), "true");
     zepServer.start();
     TestHelper.configureSparkInterpreter(zepServer, sparkHome);
   }
@@ -676,143 +678,43 @@ class ZeppelinRestApiTest extends AbstractTestRestApi {
     }
   }
 
-  @Disabled // TODO(ZEPPELIN-5994): Fix and enable this test
   @Test
-  void testJobs() throws Exception {
-    // create a note and a paragraph
+  void testCronDisabledInAnonymousMode() throws Exception {
     String noteId = null;
     try {
-      System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), 
"true");
-      noteId = notebook.createNote("note1_testJobs", anonymous);
-      // Use write lock, because name is overwritten
+      assertFalse(zConf.isAuthenticationEnabled());
+      assertFalse(zConf.isZeppelinNotebookCronEnable());
+      noteId = notebook.createNote("note1_testCronDisabledInAnonymousMode", 
anonymous);
       notebook.processNote(noteId,
-        note -> {
-          note.setName("note for run test");
-          Paragraph paragraph = 
note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
-          paragraph.setText("%md This is test paragraph.");
-
-          Map<String, Object> config = paragraph.getConfig();
-          config.put("enabled", true);
-          paragraph.setConfig(config);
-          return null;
-        });
-
-      notebook.processNote(noteId,
-        note -> {
-          try {
-            note.runAll(AuthenticationInfo.ANONYMOUS, false, false, new 
HashMap<>());
-          } catch (Exception e) {
-            fail();
-          }
-          return null;
-        });
+          note -> {
+            assertNotNull(note, "can't create new note");
+            note.setName("note for anonymous cron test");
+            Paragraph paragraph = 
note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
+            Map<String, Object> config = paragraph.getConfig();
+            config.put("enabled", true);
+            paragraph.setConfig(config);
+            paragraph.setText("%md This is test paragraph.");
+            notebook.saveNote(note, anonymous);
+            return null;
+          });
 
       String jsonRequest = "{\"cron\":\"* * * * * ?\" }";
-      // right cron expression but not exist note.
-      CloseableHttpResponse postCron = httpPost("/notebook/cron/notexistnote", 
jsonRequest);
-      assertThat("", postCron, isNotFound());
-      postCron.close();
-
-      // right cron expression.
-      postCron = httpPost("/notebook/cron/" + noteId, jsonRequest);
-      assertThat("", postCron, isAllowed());
-      postCron.close();
-      Thread.sleep(1000);
-
-      // wrong cron expression.
-      jsonRequest = "{\"cron\":\"a * * * * ?\" }";
-      postCron = httpPost("/notebook/cron/" + noteId, jsonRequest);
-      assertThat("", postCron, isBadRequest());
-      postCron.close();
-      Thread.sleep(1000);
-
-      // remove cron job.
-      CloseableHttpResponse deleteCron = httpDelete("/notebook/cron/" + 
noteId);
-      assertThat("", deleteCron, isAllowed());
-      deleteCron.close();
-    } finally {
-      //cleanup
-      if (null != noteId) {
-        notebook.removeNote(noteId, anonymous);
+      try (
+          CloseableHttpResponse postCron =
+              httpPost(
+                  "/notebook/cron/" + noteId,
+                  jsonRequest)) {
+        assertThat("", postCron, isForbidden());
+      }
+      try (
+          CloseableHttpResponse deleteCron =
+              httpDelete("/notebook/cron/" + noteId)) {
+        assertThat("", deleteCron, isForbidden());
       }
-      
System.clearProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName());
-    }
-  }
-
-  @Disabled // TODO(ZEPPELIN-5994): Fix and enable this test
-  @Test
-  void testCronDisable() throws Exception {
-    String noteId = null;
-    try {
-      // create a note and a paragraph
-      System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), 
"false");
-      noteId = notebook.createNote("note1_testCronDisable", anonymous);
-      // use write lock because Name is overwritten
-      notebook.processNote(noteId,
-        note -> {
-          note.setName("note for run test");
-          Paragraph paragraph = 
note.addNewParagraph(AuthenticationInfo.ANONYMOUS);
-          paragraph.setText("%md This is test paragraph.");
-
-          Map<String, Object> config = paragraph.getConfig();
-          config.put("enabled", true);
-          paragraph.setConfig(config);
-          return null;
-        });
-
-      notebook.processNote(noteId,
-        note -> {
-          try {
-            note.runAll(AuthenticationInfo.ANONYMOUS, true, true, new 
HashMap<>());
-          } catch (Exception e) {
-            fail();
-          }
-          return null;
-        });
-
-
-      String jsonRequest = "{\"cron\":\"* * * * * ?\" }";
-      // right cron expression.
-      CloseableHttpResponse postCron = httpPost("/notebook/cron/" + noteId, 
jsonRequest);
-      assertThat("", postCron, isForbidden());
-      postCron.close();
-
-      System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName(), 
"true");
-      System.setProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_FOLDERS.getVarName(), 
"/System");
-
-      // use write lock, because Name is overwritten
-      notebook.processNote(noteId,
-        note -> {
-          note.setName("System/test2");
-          return null;
-        });
-      notebook.processNote(noteId,
-        note -> {
-          try {
-            note.runAll(AuthenticationInfo.ANONYMOUS, true, true, new 
HashMap<>());
-          } catch (Exception e) {
-            fail();
-          }
-          return null;
-        });
-      postCron = httpPost("/notebook/cron/" + noteId, jsonRequest);
-      assertThat("", postCron, isAllowed());
-      postCron.close();
-      Thread.sleep(1000);
-
-      // remove cron job.
-      CloseableHttpResponse deleteCron = httpDelete("/notebook/cron/" + 
noteId);
-      assertThat("", deleteCron, isAllowed());
-      deleteCron.close();
-      Thread.sleep(1000);
-
-      
System.clearProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_FOLDERS.getVarName());
     } finally {
-      //cleanup
       if (null != noteId) {
         notebook.removeNote(noteId, anonymous);
       }
-      
System.clearProperty(ConfVars.ZEPPELIN_NOTEBOOK_CRON_ENABLE.getVarName());
     }
   }
 

Reply via email to