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:18 UTC
(texera) 01/01: test(amber): extend ProjectAccessResource coverage to read/query and grant/revoke (#7222)
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))
+ )
+ }
}