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

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git

commit a5cdabbf4eacb4599e116a9806ff70e8a604a0c8
Author: Meng Wang <[email protected]>
AuthorDate: Sat Aug 1 01:23:44 2026 -0700

    test(amber): extend WorkflowExecutionsResource coverage for 
access-controlled metadata endpoints (#7231)
    
    ### What changes were proposed in this PR?
    
    Extends the existing `MockTexeraDB`-backed
    `WorkflowExecutionsResourceSpec` to
    cover the previously-untested access-controlled execution-metadata
    endpoints.
    The result/log-URI and replay paths (`DocumentFactory` /
    `ReplayLogRecord`) are
    out of scope per the issue — these tests cover the jOOQ-metadata portion
    of each
    endpoint only.
    
    12 added tests (each seeds only what it needs, so they stay
    order-independent):
    
    - `retrieveExecutionsOfWorkflow` — empty list when the user lacks read
    access; the workflow's executions for an authorized user; a
    `BadRequestException` for an invalid `status` filter.
    - `retrieveLatestExecutionEntry` — `ForbiddenException` when the
    workflow has no executions; the most-recent entry otherwise.
    - `retrieveInteractionHistory` — empty list when the user lacks read
    access (the replay branch is out of scope).
    - `setExecutionAreBookmarked` — `WebApplicationException` for a user
    without access; the bookmark flag toggles both directions.
    - `updateWorkflowExecutionsName` — renames the execution.
    - `groupDeleteExecutionsOfWorkflow` — removes the execution rows (seeded
    with no URIs so `removeAllExecutionFiles` touches no storage).
    - `retrieveWorkflowRuntimeStatistics` — `NoSuchElementException` when
    the execution has no runtime-stats URI (the `DocumentFactory` read is
    out of scope).
    
    Read access is granted by seeding a `WORKFLOW_USER_ACCESS(READ)` row;
    that row is
    cleaned up in `cleanupTestData` before the `WORKFLOW` row (FK / leak
    safety). No
    production code was changed.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7226
    
    ### How was this PR tested?
    
    Extended unit tests, run locally against embedded Postgres:
    
    ```
    sbt "WorkflowExecutionService/testOnly 
org.apache.texera.web.resource.dashboard.user.workflow.WorkflowExecutionsResourceSpec"
    # Tests: succeeded 42, failed 0   (30 existing + 12 new; re-ran for 
stability)
    sbt "WorkflowExecutionService/Test/scalafmtCheck" 
"WorkflowExecutionService/Test/scalafix --check"   # clean
    ```
    
    The failure path was verified by deliberately breaking a new assertion
    and
    confirming the suite goes red (41 succeeded, 1 failed), then restored.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../workflow/WorkflowExecutionsResourceSpec.scala  | 147 ++++++++++++++++++++-
 1 file changed, 146 insertions(+), 1 deletion(-)

diff --git 
a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResourceSpec.scala
 
b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResourceSpec.scala
index 97bbe34abc..7a3afc03f3 100644
--- 
a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResourceSpec.scala
+++ 
b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/workflow/WorkflowExecutionsResourceSpec.scala
@@ -28,9 +28,10 @@ import org.apache.texera.amber.core.virtualidentity.{
 }
 import org.apache.texera.amber.core.workflow.{GlobalPortIdentity, PortIdentity}
 import org.apache.texera.amber.util.serde.GlobalPortIdentitySerde.SerdeOps
+import org.apache.texera.auth.SessionUser
 import org.apache.texera.dao.MockTexeraDB
 import org.apache.texera.dao.jooq.generated.Tables._
-import org.apache.texera.dao.jooq.generated.enums.WorkflowComputingUnitTypeEnum
+import org.apache.texera.dao.jooq.generated.enums.{PrivilegeEnum, 
WorkflowComputingUnitTypeEnum}
 import org.apache.texera.dao.jooq.generated.tables.daos.{
   DatasetDao,
   UserDao,
@@ -52,6 +53,7 @@ import org.apache.texera.web.service.ExecutionResultService
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, 
PrivateMethodTester}
 
+import javax.ws.rs.{BadRequestException, ForbiddenException, 
WebApplicationException}
 import java.net.URI
 import java.sql.Timestamp
 import java.util.UUID
@@ -162,6 +164,12 @@ class WorkflowExecutionsResourceSpec
       .where(WORKFLOW_VERSION.WID.eq(testWorkflowWid))
       .execute()
 
+    // Access grants seeded by the endpoint tests must go before the workflow 
row.
+    getDSLContext
+      .deleteFrom(WORKFLOW_USER_ACCESS)
+      .where(WORKFLOW_USER_ACCESS.WID.eq(testWorkflowWid))
+      .execute()
+
     getDSLContext
       .deleteFrom(WORKFLOW)
       .where(WORKFLOW.WID.eq(testWorkflowWid))
@@ -900,4 +908,141 @@ class WorkflowExecutionsResourceSpec
     )
   }
 
+  // ─── access-controlled instance endpoints (jOOQ metadata only) 
─────────────
+  // The result/log-URI and replay paths (DocumentFactory / ReplayLogRecord) 
are
+  // out of scope; these cover the DB-metadata portion of each endpoint.
+
+  private val resource = new WorkflowExecutionsResource
+
+  private def session(user: User): SessionUser = new SessionUser(user)
+
+  private def grantReadAccess(uid: Integer = testUserId): Unit =
+    getDSLContext
+      .insertInto(WORKFLOW_USER_ACCESS)
+      .set(WORKFLOW_USER_ACCESS.WID, Integer.valueOf(testWorkflowWid))
+      .set(WORKFLOW_USER_ACCESS.UID, uid)
+      .set(WORKFLOW_USER_ACCESS.PRIVILEGE, PrivilegeEnum.READ)
+      .execute()
+
+  private def userWithoutAccess(): User = {
+    val u = new User
+    u.setUid(testUserId + 5000)
+    u.setName("no_access_user")
+    u.setEmail("[email protected]")
+    u.setPassword("password")
+    u
+  }
+
+  "retrieveExecutionsOfWorkflow" should "return an empty list when the user 
lacks read access" in {
+    val result =
+      resource.retrieveExecutionsOfWorkflow(testWorkflowWid, 
session(userWithoutAccess()), null)
+    assert(result.isEmpty)
+  }
+
+  it should "return the workflow's executions for an authorized user" in {
+    grantReadAccess()
+    insertExecution()
+    insertExecution()
+    val result = resource.retrieveExecutionsOfWorkflow(testWorkflowWid, 
session(testUser), null)
+    assert(result.size == 2)
+  }
+
+  it should "reject an invalid status filter with a BadRequestException" in {
+    grantReadAccess()
+    assertThrows[BadRequestException](
+      resource.retrieveExecutionsOfWorkflow(
+        testWorkflowWid,
+        session(testUser),
+        "definitely-not-a-status"
+      )
+    )
+  }
+
+  "retrieveLatestExecutionEntry" should "throw ForbiddenException when the 
workflow has no executions" in {
+    grantReadAccess()
+    assertThrows[ForbiddenException](
+      resource.retrieveLatestExecutionEntry(testWorkflowWid, session(testUser))
+    )
+  }
+
+  it should "return the most recently created execution entry" in {
+    grantReadAccess()
+    insertExecution(name = "first")
+    val latest = insertExecution(name = "second")
+    val entry = resource.retrieveLatestExecutionEntry(testWorkflowWid, 
session(testUser))
+    // same VID, so the highest EID is the latest
+    assert(entry.eId == latest.getEid)
+    assert(entry.name == "second")
+  }
+
+  "retrieveInteractionHistory" should "return an empty list when the user 
lacks read access" in {
+    val result =
+      resource.retrieveInteractionHistory(
+        testWorkflowWid,
+        Integer.valueOf(1),
+        session(userWithoutAccess())
+      )
+    assert(result.isEmpty)
+  }
+
+  "setExecutionAreBookmarked" should "reject a user without access" in {
+    val exec = insertExecution()
+    assertThrows[WebApplicationException](
+      resource.setExecutionAreBookmarked(
+        ExecutionGroupBookmarkRequest(testWorkflowWid, Array(exec.getEid), 
isBookmarked = false),
+        session(userWithoutAccess())
+      )
+    )
+  }
+
+  it should "bookmark executions that are currently un-bookmarked" in {
+    grantReadAccess()
+    val exec = insertExecution() // bookmarked = false
+    resource.setExecutionAreBookmarked(
+      ExecutionGroupBookmarkRequest(testWorkflowWid, Array(exec.getEid), 
isBookmarked = false),
+      session(testUser)
+    )
+    assert(workflowExecutionsDao.fetchOneByEid(exec.getEid).getBookmarked == 
true)
+  }
+
+  it should "un-bookmark executions that are currently bookmarked" in {
+    grantReadAccess()
+    val exec = insertExecution()
+    resource.setExecutionAreBookmarked(
+      ExecutionGroupBookmarkRequest(testWorkflowWid, Array(exec.getEid), 
isBookmarked = true),
+      session(testUser)
+    )
+    assert(workflowExecutionsDao.fetchOneByEid(exec.getEid).getBookmarked == 
false)
+  }
+
+  "updateWorkflowExecutionsName" should "rename the execution" in {
+    grantReadAccess()
+    val exec = insertExecution(name = "old-name")
+    resource.updateWorkflowExecutionsName(
+      ExecutionRenameRequest(testWorkflowWid, exec.getEid, "new-name"),
+      session(testUser)
+    )
+    assert(workflowExecutionsDao.fetchOneByEid(exec.getEid).getName == 
"new-name")
+  }
+
+  "groupDeleteExecutionsOfWorkflow" should "delete the execution rows" in {
+    grantReadAccess()
+    val e1 = insertExecution()
+    val e2 = insertExecution()
+    resource.groupDeleteExecutionsOfWorkflow(
+      ExecutionGroupDeleteRequest(testWorkflowWid, Array(e1.getEid, 
e2.getEid)),
+      session(testUser)
+    )
+    assert(workflowExecutionsDao.fetchOneByEid(e1.getEid) == null)
+    assert(workflowExecutionsDao.fetchOneByEid(e2.getEid) == null)
+  }
+
+  "retrieveWorkflowRuntimeStatistics" should "throw when the execution has no 
runtime-stats URI" in {
+    grantReadAccess()
+    val exec = insertExecution() // runtimeStatsUri = null
+    assertThrows[java.util.NoSuchElementException](
+      resource.retrieveWorkflowRuntimeStatistics(testWorkflowWid, exec.getEid, 
session(testUser))
+    )
+  }
+
 }

Reply via email to