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
The following commit(s) were added to refs/heads/main by this push:
new 9fa6124017 test(amber): add unit test coverage for UserQuotaResource
(#7168)
9fa6124017 is described below
commit 9fa612401790f866d1e58a94f70744c996a2f696
Author: Meng Wang <[email protected]>
AuthorDate: Fri Jul 31 11:48:58 2026 -0700
test(amber): add unit test coverage for UserQuotaResource (#7168)
### What changes were proposed in this PR?
Adds `UserQuotaResourceSpec` for `UserQuotaResource` (previously ~0%
coverage,
no spec). The suite runs against embedded Postgres via `MockTexeraDB`
and seeds
users / workflows / versions / executions / datasets through the
generated DAOs.
13 tests, each starting from a clean slate (so they are
order-independent):
- `getUserCreatedWorkflow` — empty list when the user owns no workflows;
the owned workflows with their id / name / creation time when
`WORKFLOW_OF_USER` is seeded.
- `getUserAccessedWorkflow` — empty list with no grants; the accessible
workflow ids from `WORKFLOW_USER_ACCESS`.
- `getUserQuotaSize` — empty array with no executions; the assembled
per-execution `resultBytes` / `runTimeStatsBytes` / `logBytes` (seeded
across `WORKFLOW_EXECUTIONS.RUNTIME_STATS_SIZE`,
`OPERATOR_PORT_EXECUTIONS.RESULT_SIZE`, and
`OPERATOR_EXECUTIONS.CONSOLE_MESSAGES_SIZE`).
- The JAX-RS endpoints (`getCreatedWorkflow`, `getAccessedWorkflow`,
`getUserQuota`, `getCreatedDatasets`) via a `new SessionUser`, covering
the thin delegation wrappers; `getCreatedDatasets` covers the empty case
and the created-datasets case (size is deterministically 0 — the size
calculation is disabled in the current dataset implementation).
- `deleteExecutionCollection` and the `deleteCollection` endpoint —
assert the execution row is removed from `WORKFLOW_EXECUTIONS`.
No production code was changed.
### Any related issues, documentation, discussions?
Closes #7162
### How was this PR tested?
New unit tests, run locally against embedded Postgres:
```
sbt "WorkflowExecutionService/testOnly
org.apache.texera.web.resource.dashboard.user.quota.UserQuotaResourceSpec"
# Tests: succeeded 13, failed 0
sbt "WorkflowExecutionService/Test/scalafmtCheck"
"WorkflowExecutionService/Test/scalafix --check" # clean
```
The failure path was verified by deliberately breaking an assertion and
confirming the suite goes red. To keep the suite deterministic, the
`creationTime` assertion compares against the DB round-tripped value
rather than
the in-memory timestamp (Postgres rounds the stored value, so an exact
in-memory comparison would be flaky).
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---
.../user/quota/UserQuotaResourceSpec.scala | 353 +++++++++++++++++++++
1 file changed, 353 insertions(+)
diff --git
a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/quota/UserQuotaResourceSpec.scala
b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/quota/UserQuotaResourceSpec.scala
new file mode 100644
index 0000000000..0a0212c2b2
--- /dev/null
+++
b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/user/quota/UserQuotaResourceSpec.scala
@@ -0,0 +1,353 @@
+/*
+ * 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.texera.web.resource.dashboard.user.quota
+
+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.PrivilegeEnum
+import org.apache.texera.dao.jooq.generated.tables.daos.{
+ DatasetDao,
+ UserDao,
+ WorkflowDao,
+ WorkflowExecutionsDao,
+ WorkflowOfUserDao,
+ WorkflowUserAccessDao,
+ WorkflowVersionDao
+}
+import org.apache.texera.dao.jooq.generated.tables.pojos.{
+ Dataset,
+ User,
+ Workflow,
+ WorkflowExecutions,
+ WorkflowOfUser,
+ WorkflowUserAccess,
+ WorkflowVersion
+}
+import org.scalatest.BeforeAndAfterAll
+import org.scalatest.flatspec.AnyFlatSpec
+
+import java.sql.Timestamp
+import java.util.UUID
+
+class UserQuotaResourceSpec extends AnyFlatSpec with BeforeAndAfterAll with
MockTexeraDB {
+
+ private val testUid = 4000 + scala.util.Random.nextInt(1000)
+ private val testWid = 5000 + scala.util.Random.nextInt(1000)
+
+ private var userDao: UserDao = _
+ private var workflowDao: WorkflowDao = _
+ private var workflowVersionDao: WorkflowVersionDao = _
+ private var workflowOfUserDao: WorkflowOfUserDao = _
+ private var workflowUserAccessDao: WorkflowUserAccessDao = _
+ private var workflowExecutionsDao: WorkflowExecutionsDao = _
+ private var datasetDao: DatasetDao = _
+
+ private var testUser: User = _
+ private var testWorkflow: Workflow = _
+ private var testVersion: WorkflowVersion = _
+ private val resource = new UserQuotaResource
+
+ override protected def beforeAll(): Unit = {
+ initializeDBAndReplaceDSLContext()
+
+ userDao = new UserDao(getDSLContext.configuration())
+ workflowDao = new WorkflowDao(getDSLContext.configuration())
+ workflowVersionDao = new WorkflowVersionDao(getDSLContext.configuration())
+ workflowOfUserDao = new WorkflowOfUserDao(getDSLContext.configuration())
+ workflowUserAccessDao = new
WorkflowUserAccessDao(getDSLContext.configuration())
+ workflowExecutionsDao = new
WorkflowExecutionsDao(getDSLContext.configuration())
+ datasetDao = new DatasetDao(getDSLContext.configuration())
+ }
+
+ override protected def afterAll(): Unit = shutdownDB()
+
+ // Each test starts from a clean slate for the ids under test, then seeds
+ // only what it needs, so tests are order-independent.
+ private def resetFixtures(): Unit = {
+ cleanupTestData()
+
+ testUser = new User
+ testUser.setUid(testUid)
+ testUser.setName("quota_user")
+ testUser.setEmail("[email protected]")
+ testUser.setPassword("password")
+ userDao.insert(testUser)
+
+ testWorkflow = new Workflow
+ testWorkflow.setWid(testWid)
+ testWorkflow.setName("quota_workflow_" +
UUID.randomUUID().toString.substring(0, 8))
+ testWorkflow.setContent("{}")
+ testWorkflow.setDescription("desc")
+ testWorkflow.setCreationTime(new Timestamp(System.currentTimeMillis()))
+ testWorkflow.setLastModifiedTime(new Timestamp(System.currentTimeMillis()))
+ workflowDao.insert(testWorkflow)
+
+ testVersion = new WorkflowVersion
+ testVersion.setWid(testWid)
+ testVersion.setContent("{}")
+ testVersion.setCreationTime(new Timestamp(System.currentTimeMillis()))
+ workflowVersionDao.insert(testVersion)
+ }
+
+ private def cleanupTestData(): Unit = {
+ val vidSubquery = getDSLContext
+ .select(WORKFLOW_VERSION.VID)
+ .from(WORKFLOW_VERSION)
+ .where(WORKFLOW_VERSION.WID.eq(testWid))
+
+ getDSLContext
+ .deleteFrom(OPERATOR_PORT_EXECUTIONS)
+ .where(
+ OPERATOR_PORT_EXECUTIONS.WORKFLOW_EXECUTION_ID.in(
+ getDSLContext
+ .select(WORKFLOW_EXECUTIONS.EID)
+ .from(WORKFLOW_EXECUTIONS)
+ .where(WORKFLOW_EXECUTIONS.UID.eq(testUid))
+ )
+ )
+ .execute()
+ getDSLContext
+ .deleteFrom(OPERATOR_EXECUTIONS)
+ .where(
+ OPERATOR_EXECUTIONS.WORKFLOW_EXECUTION_ID.in(
+ getDSLContext
+ .select(WORKFLOW_EXECUTIONS.EID)
+ .from(WORKFLOW_EXECUTIONS)
+ .where(WORKFLOW_EXECUTIONS.UID.eq(testUid))
+ )
+ )
+ .execute()
+ getDSLContext
+ .deleteFrom(WORKFLOW_EXECUTIONS)
+ .where(WORKFLOW_EXECUTIONS.VID.in(vidSubquery))
+ .execute()
+
getDSLContext.deleteFrom(WORKFLOW_VERSION).where(WORKFLOW_VERSION.WID.eq(testWid)).execute()
+ getDSLContext
+ .deleteFrom(WORKFLOW_USER_ACCESS)
+ .where(WORKFLOW_USER_ACCESS.UID.eq(testUid))
+ .execute()
+
getDSLContext.deleteFrom(WORKFLOW_OF_USER).where(WORKFLOW_OF_USER.UID.eq(testUid)).execute()
+
getDSLContext.deleteFrom(WORKFLOW).where(WORKFLOW.WID.eq(testWid)).execute()
+
getDSLContext.deleteFrom(DATASET).where(DATASET.OWNER_UID.eq(testUid)).execute()
+ getDSLContext.deleteFrom(USER).where(USER.UID.eq(testUid)).execute()
+ }
+
+ // ─── helpers ──────────────────────────────────────────────────────────────
+
+ private def insertOwnership(): Unit = {
+ val ownership = new WorkflowOfUser
+ ownership.setUid(testUid)
+ ownership.setWid(testWid)
+ workflowOfUserDao.insert(ownership)
+ }
+
+ private def insertAccess(): Unit = {
+ val access = new WorkflowUserAccess
+ access.setUid(testUid)
+ access.setWid(testWid)
+ access.setPrivilege(PrivilegeEnum.READ)
+ workflowUserAccessDao.insert(access)
+ }
+
+ private def insertExecution(): WorkflowExecutions = {
+ val execution = new WorkflowExecutions
+ execution.setVid(testVersion.getVid)
+ execution.setUid(testUid)
+ execution.setStatus(0.toByte)
+ execution.setResult("")
+ execution.setLogLocation("")
+ execution.setStartingTime(new Timestamp(System.currentTimeMillis()))
+ execution.setBookmarked(false)
+ execution.setName("exec-" + UUID.randomUUID().toString.substring(0, 8))
+ execution.setEnvironmentVersion("test-env-1.0")
+ workflowExecutionsDao.insert(execution)
+ execution
+ }
+
+ private def insertDataset(): Dataset = {
+ val dataset = new Dataset
+ dataset.setOwnerUid(testUid)
+ dataset.setName("quota_ds_" + UUID.randomUUID().toString.substring(0, 8))
+ dataset.setRepositoryName("repo-" +
UUID.randomUUID().toString.substring(0, 8))
+ dataset.setIsPublic(false)
+ dataset.setIsDownloadable(true)
+ dataset.setDescription("")
+ dataset.setCreationTime(new Timestamp(System.currentTimeMillis()))
+ datasetDao.insert(dataset)
+ dataset
+ }
+
+ // ─── getUserCreatedWorkflow
────────────────────────────────────────────────
+
+ "getUserCreatedWorkflow" should "return an empty list when the user owns no
workflows" in {
+ resetFixtures()
+ assert(UserQuotaResource.getUserCreatedWorkflow(testUid).isEmpty)
+ }
+
+ it should "return the workflows owned by the user with their names and
timestamps" in {
+ resetFixtures()
+ insertOwnership()
+
+ val workflows = UserQuotaResource.getUserCreatedWorkflow(testUid)
+
+ assert(workflows.size == 1)
+ val w = workflows.head
+ assert(w.userId == testUid)
+ assert(w.workflowId == testWid)
+ assert(w.workflowName == testWorkflow.getName)
+ // Compare against the DB round-tripped value: Postgres rounds the stored
+ // timestamp, so the in-memory pojo's millis would not match exactly.
+ assert(w.creationTime ==
workflowDao.fetchOneByWid(testWid).getCreationTime.getTime)
+ }
+
+ // ─── getUserAccessedWorkflow
───────────────────────────────────────────────
+
+ "getUserAccessedWorkflow" should "return an empty list when the user has no
access grants" in {
+ resetFixtures()
+ assert(UserQuotaResource.getUserAccessedWorkflow(testUid).isEmpty)
+ }
+
+ it should "return the workflow ids the user can access" in {
+ resetFixtures()
+ insertAccess()
+
+ val accessed = UserQuotaResource.getUserAccessedWorkflow(testUid)
+
+ assert(accessed.size == 1)
+ assert(accessed.get(0) == testWid)
+ }
+
+ // ─── getUserQuotaSize
──────────────────────────────────────────────────────
+
+ "getUserQuotaSize" should "return an empty array when the user has no
executions" in {
+ resetFixtures()
+ assert(UserQuotaResource.getUserQuotaSize(testUid).isEmpty)
+ }
+
+ it should "assemble per-execution result / runtime-stats / log sizes" in {
+ resetFixtures()
+ val execution = insertExecution()
+ getDSLContext
+ .update(WORKFLOW_EXECUTIONS)
+ .set(WORKFLOW_EXECUTIONS.RUNTIME_STATS_SIZE,
java.lang.Long.valueOf(200L))
+ .where(WORKFLOW_EXECUTIONS.EID.eq(execution.getEid))
+ .execute()
+ getDSLContext
+ .insertInto(OPERATOR_PORT_EXECUTIONS)
+ .columns(
+ OPERATOR_PORT_EXECUTIONS.WORKFLOW_EXECUTION_ID,
+ OPERATOR_PORT_EXECUTIONS.GLOBAL_PORT_ID,
+ OPERATOR_PORT_EXECUTIONS.RESULT_URI,
+ OPERATOR_PORT_EXECUTIONS.RESULT_SIZE
+ )
+ .values(execution.getEid, "gp-1", "vfs:///r",
java.lang.Long.valueOf(100L))
+ .execute()
+ getDSLContext
+ .insertInto(OPERATOR_EXECUTIONS)
+ .columns(
+ OPERATOR_EXECUTIONS.WORKFLOW_EXECUTION_ID,
+ OPERATOR_EXECUTIONS.OPERATOR_ID,
+ OPERATOR_EXECUTIONS.CONSOLE_MESSAGES_URI,
+ OPERATOR_EXECUTIONS.CONSOLE_MESSAGES_SIZE
+ )
+ .values(execution.getEid, "op-1", "vfs:///c",
java.lang.Long.valueOf(50L))
+ .execute()
+
+ val quota = UserQuotaResource.getUserQuotaSize(testUid)
+
+ assert(quota.length == 1)
+ val q = quota.head
+ assert(q.eid == execution.getEid)
+ assert(q.workflowId == testWid)
+ assert(q.workflowName == testWorkflow.getName)
+ assert(q.resultBytes == 100L)
+ assert(q.runTimeStatsBytes == 200L)
+ assert(q.logBytes == 50L)
+ }
+
+ // ─── class endpoints (delegate to the object using the SessionUser's uid)
──
+
+ "getCreatedWorkflow (endpoint)" should "return the current user's created
workflows" in {
+ resetFixtures()
+ insertOwnership()
+
+ val workflows = resource.getCreatedWorkflow(new SessionUser(testUser))
+
+ assert(workflows.size == 1)
+ assert(workflows.head.workflowId == testWid)
+ }
+
+ "getAccessedWorkflow (endpoint)" should "return the current user's
accessible workflow ids" in {
+ resetFixtures()
+ insertAccess()
+
+ val accessed = resource.getAccessedWorkflow(new SessionUser(testUser))
+
+ assert(accessed.size == 1)
+ assert(accessed.get(0) == testWid)
+ }
+
+ "getUserQuota (endpoint)" should "return the current user's quota storage
array" in {
+ resetFixtures()
+ insertExecution()
+
+ val quota = resource.getUserQuota(new SessionUser(testUser))
+
+ assert(quota.length == 1)
+ assert(quota.head.workflowId == testWid)
+ }
+
+ "getCreatedDatasets (endpoint)" should "return an empty list when the user
owns no datasets" in {
+ resetFixtures()
+ assert(resource.getCreatedDatasets(new SessionUser(testUser)).isEmpty)
+ }
+
+ it should "return the datasets the user created (with size disabled to 0)"
in {
+ resetFixtures()
+ val dataset = insertDataset()
+
+ val datasets = resource.getCreatedDatasets(new SessionUser(testUser))
+
+ assert(datasets.size == 1)
+ assert(datasets.head.name == dataset.getName)
+ assert(datasets.head.size == 0L)
+ }
+
+ // ─── deleteExecutionCollection / deleteCollection
──────────────────────────
+
+ "deleteExecutionCollection" should "delete the execution row from
WORKFLOW_EXECUTIONS" in {
+ resetFixtures()
+ val execution = insertExecution()
+
+ UserQuotaResource.deleteExecutionCollection(execution.getEid)
+
+ assert(workflowExecutionsDao.fetchOneByEid(execution.getEid) == null)
+ }
+
+ "deleteCollection (endpoint)" should "delete the execution row for the given
eid" in {
+ resetFixtures()
+ val execution = insertExecution()
+
+ resource.deleteCollection(execution.getEid)
+
+ assert(workflowExecutionsDao.fetchOneByEid(execution.getEid) == null)
+ }
+}