You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/08/01 07:03:17 UTC

(texera) branch gh-readonly-queue/main/pr-7222-58179a08a73203bae5c3cd6e615b592a235713ea created (now e970abb8ee)

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

github-merge-queue[bot] pushed a change to branch gh-readonly-queue/main/pr-7222-58179a08a73203bae5c3cd6e615b592a235713ea
in repository https://gitbox.apache.org/repos/asf/texera.git


      at e970abb8ee test(amber): extend ProjectAccessResource coverage to read/query and grant/revoke (#7222)

This branch includes the following new commits:

     new e970abb8ee test(amber): extend ProjectAccessResource coverage to read/query and grant/revoke (#7222)

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(texera) 01/01: test(amber): extend ProjectAccessResource coverage to read/query and grant/revoke (#7222)

Posted by gi...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit e970abb8eed36c8dc3a6f002ccb846a61287b47e
Author: Meng Wang <me...@uci.edu>
AuthorDate: Sat Aug 1 00:02:56 2026 -0700

    test(amber): extend ProjectAccessResource coverage to read/query and grant/revoke (#7222)
    
    ### What changes were proposed in this PR?
    
    Extends the existing `MockTexeraDB`-backed `ProjectAccessResourceSpec`
    to cover
    the read/query and grant/revoke endpoints
    
    (`amber/src/main/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResource.scala`).
    The prior spec only exercised `getProjectAccessPrivilege` /
    `userHasWriteAccess`
    for the owner/READ/NONE cases. No production code was changed.
    
    A third seeded user (a WRITE grantee) is added; +8 tests cover:
    
    - `getProjectAccessPrivilege` / `userHasWriteAccess` — the WRITE-grantee
    case
      (a non-owner with WRITE), which the existing tests didn't reach.
    - `getOwner` — returns the owning user's email.
    - `getAccessList` — empty when only the owner has access (the owner is
    excluded),
    and lists every grantee with their email, name and privilege (READ and
    WRITE).
    - `grantAccess` — a writer grants READ to another user (access row
    created), and a
      user without write access is rejected with `ForbiddenException`.
    - `revokeAccess` — a grantee's access is removed, and a user without
    write access
      is rejected with `ForbiddenException`.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7178
    
    ### How was this PR tested?
    
    Extended unit tests, run locally against embedded Postgres
    (`MockTexeraDB`). All
    pass, and the failure path was verified by breaking an assertion to
    confirm the
    suite goes red:
    
    ```
    sbt "WorkflowExecutionService/testOnly *ProjectAccessResourceSpec"
    # Tests: succeeded 11, failed 0
    sbt "WorkflowExecutionService/Test/scalafmtCheck"       # clean
    sbt "WorkflowExecutionService/Test/scalafix --check"    # clean
    ```
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../user/project/ProjectAccessResourceSpec.scala   | 115 ++++++++++++++++++++-
 1 file changed, 113 insertions(+), 2 deletions(-)

diff --git a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResourceSpec.scala b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResourceSpec.scala
index 2100be4b42..cf886e4a8e 100644
--- a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResourceSpec.scala
+++ b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/project/ProjectAccessResourceSpec.scala
@@ -25,9 +25,13 @@ import org.apache.texera.dao.jooq.generated.Tables.{PROJECT, PROJECT_USER_ACCESS
 import org.apache.texera.dao.jooq.generated.enums.{PrivilegeEnum, UserRoleEnum}
 import org.apache.texera.dao.jooq.generated.tables.daos.{ProjectUserAccessDao, UserDao}
 import org.apache.texera.dao.jooq.generated.tables.pojos.{ProjectUserAccess, User}
+import org.apache.texera.web.model.common.AccessEntry
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
 
+import javax.ws.rs.ForbiddenException
+import scala.jdk.CollectionConverters._
+
 class ProjectAccessResourceSpec
     extends AnyFlatSpec
     with BeforeAndAfterAll
@@ -36,12 +40,15 @@ class ProjectAccessResourceSpec
 
   private val ownerUid = 7101
   private val readerUid = 7102
+  private val writerUid = 7103
 
   private var owner: User = _
   private var reader: User = _
+  private var writer: User = _
   private var userDao: UserDao = _
   private var projectUserAccessDao: ProjectUserAccessDao = _
   private var projectResource: ProjectResource = _
+  private var projectAccessResource: ProjectAccessResource = _
 
   override protected def beforeAll(): Unit = {
     initializeDBAndReplaceDSLContext()
@@ -51,14 +58,17 @@ class ProjectAccessResourceSpec
     userDao = new UserDao(getDSLContext.configuration())
     projectUserAccessDao = new ProjectUserAccessDao(getDSLContext.configuration())
     projectResource = new ProjectResource()
+    projectAccessResource = new ProjectAccessResource()
 
     owner = createUser(ownerUid, "project_owner", "project-owner@test.com")
     reader = createUser(readerUid, "project_reader", "project-reader@test.com")
+    writer = createUser(writerUid, "project_writer", "project-writer@test.com")
 
     cleanupTestData()
 
     userDao.insert(owner)
     userDao.insert(reader)
+    userDao.insert(writer)
   }
 
   override protected def afterEach(): Unit = {
@@ -82,7 +92,7 @@ class ProjectAccessResourceSpec
   private def cleanupTestData(): Unit = {
     getDSLContext
       .deleteFrom(PROJECT_USER_ACCESS)
-      .where(PROJECT_USER_ACCESS.UID.in(ownerUid, readerUid))
+      .where(PROJECT_USER_ACCESS.UID.in(ownerUid, readerUid, writerUid))
       .execute()
 
     getDSLContext
@@ -92,7 +102,7 @@ class ProjectAccessResourceSpec
 
     getDSLContext
       .deleteFrom(USER)
-      .where(USER.UID.in(ownerUid, readerUid))
+      .where(USER.UID.in(ownerUid, readerUid, writerUid))
       .execute()
   }
 
@@ -129,4 +139,105 @@ class ProjectAccessResourceSpec
     assert(privilege == PrivilegeEnum.NONE)
     assert(!ProjectAccessResource.userHasWriteAccess(privateProject.getPid, readerUid))
   }
+
+  it should "return WRITE and grant write access for a WRITE grantee" in {
+    val project = projectResource.createProject(new SessionUser(owner), "writer-project")
+    projectUserAccessDao.merge(
+      new ProjectUserAccess(writerUid, project.getPid, PrivilegeEnum.WRITE)
+    )
+
+    assert(
+      ProjectAccessResource.getProjectAccessPrivilege(
+        project.getPid,
+        writerUid
+      ) == PrivilegeEnum.WRITE
+    )
+    assert(ProjectAccessResource.userHasWriteAccess(project.getPid, writerUid))
+  }
+
+  "ProjectAccessResource.getOwner" should "return the owning user's email" in {
+    val project = projectResource.createProject(new SessionUser(owner), "owned-project")
+    assert(projectAccessResource.getOwner(project.getPid) == owner.getEmail)
+  }
+
+  "ProjectAccessResource.getAccessList" should "be empty when only the owner has access" in {
+    val project = projectResource.createProject(new SessionUser(owner), "solo-project")
+    // createProject grants the owner WRITE, but getAccessList excludes the owner.
+    assert(projectAccessResource.getAccessList(project.getPid).asScala.isEmpty)
+  }
+
+  it should "list every grantee (excluding the owner) with their email, name and privilege" in {
+    val project = projectResource.createProject(new SessionUser(owner), "shared-list-project")
+    projectUserAccessDao.merge(new ProjectUserAccess(readerUid, project.getPid, PrivilegeEnum.READ))
+    projectUserAccessDao.merge(
+      new ProjectUserAccess(writerUid, project.getPid, PrivilegeEnum.WRITE)
+    )
+
+    val entries = projectAccessResource.getAccessList(project.getPid).asScala.toList
+    assert(entries.size == 2)
+    assert(!entries.map(_.email).contains(owner.getEmail)) // owner is excluded
+    assert(entries.contains(AccessEntry(reader.getEmail, reader.getName, PrivilegeEnum.READ)))
+    assert(entries.contains(AccessEntry(writer.getEmail, writer.getName, PrivilegeEnum.WRITE)))
+  }
+
+  "ProjectAccessResource.grantAccess" should "let a WRITE grantee grant READ access to another user" in {
+    val project = projectResource.createProject(new SessionUser(owner), "grant-project")
+    // writer is a non-owner WRITE grantee, so it is allowed to grant access.
+    projectUserAccessDao.merge(
+      new ProjectUserAccess(writerUid, project.getPid, PrivilegeEnum.WRITE)
+    )
+
+    projectAccessResource.grantAccess(
+      project.getPid,
+      reader.getEmail,
+      "READ",
+      new SessionUser(writer)
+    )
+
+    assert(
+      ProjectAccessResource.getProjectAccessPrivilege(
+        project.getPid,
+        readerUid
+      ) == PrivilegeEnum.READ
+    )
+  }
+
+  it should "reject a user without write access with ForbiddenException" in {
+    val project = projectResource.createProject(new SessionUser(owner), "grant-forbidden-project")
+    // reader has no access to the project, so cannot grant.
+    assertThrows[ForbiddenException](
+      projectAccessResource.grantAccess(
+        project.getPid,
+        writer.getEmail,
+        "READ",
+        new SessionUser(reader)
+      )
+    )
+  }
+
+  "ProjectAccessResource.revokeAccess" should "remove a grantee's access" in {
+    val project = projectResource.createProject(new SessionUser(owner), "revoke-project")
+    projectUserAccessDao.merge(new ProjectUserAccess(readerUid, project.getPid, PrivilegeEnum.READ))
+
+    projectAccessResource.revokeAccess(project.getPid, reader.getEmail, new SessionUser(owner))
+
+    assert(
+      ProjectAccessResource.getProjectAccessPrivilege(
+        project.getPid,
+        readerUid
+      ) == PrivilegeEnum.NONE
+    )
+  }
+
+  it should "reject a user without write access with ForbiddenException" in {
+    val project = projectResource.createProject(new SessionUser(owner), "revoke-forbidden-project")
+    projectUserAccessDao.merge(
+      new ProjectUserAccess(writerUid, project.getPid, PrivilegeEnum.WRITE)
+    )
+
+    // reader has no write access, so cannot revoke the writer's access.
+    assertThrows[ForbiddenException](
+      projectAccessResource.revokeAccess(project.getPid, writer.getEmail, new SessionUser(reader))
+    )
+  }
 }