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))
+ )
+ }
}