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 01071832cd test(amber): add unit test coverage for UserConfigResource
(#7181)
01071832cd is described below
commit 01071832cd2e64d45b54f46ffd193d1ffa359299
Author: Meng Wang <[email protected]>
AuthorDate: Fri Jul 31 17:21:43 2026 -0700
test(amber): add unit test coverage for UserConfigResource (#7181)
### What changes were proposed in this PR?
Adds `UserConfigResourceSpec` for `UserConfigResource` (previously ~0%
coverage,
no spec) — the small jOOQ-backed per-user key/value config store. The
suite runs
against embedded Postgres via `MockTexeraDB` and seeds users through the
generated `UserDao`.
11 tests, each starting from a clean slate (so they are
order-independent),
cover the full CRUD round-trip through the resource's own API:
- `getEntry` — returns `null` for a key that was never set.
- `setEntry` — stores a value that `getEntry` then returns; overwriting
the same key updates the value (and leaves a single entry, not two).
- `getAllDict` — empty map when the user has no entries; every key/value
pair after N sets; and it is **scoped to the requesting user** (a second
user's entries are not returned, and the same key set by two users stays
independent).
- `deleteEntry` — removes the key so `getEntry`/`getAllDict` no longer
show it; a delete of a non-existent key is a no-op (no error).
- Key validation — `getEntry` / `setEntry` / `deleteEntry` reject a
blank key with a `BadRequestException`.
No production code was changed.
### Any related issues, documentation, discussions?
Closes #7176
### How was this PR tested?
New unit tests, run locally against embedded Postgres:
```
sbt "WorkflowExecutionService/testOnly
org.apache.texera.web.resource.UserConfigResourceSpec"
# Tests: succeeded 11, 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.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---
.../web/resource/UserConfigResourceSpec.scala | 153 +++++++++++++++++++++
1 file changed, 153 insertions(+)
diff --git
a/amber/src/test/scala/org/apache/texera/web/resource/UserConfigResourceSpec.scala
b/amber/src/test/scala/org/apache/texera/web/resource/UserConfigResourceSpec.scala
new file mode 100644
index 0000000000..0ee7158077
--- /dev/null
+++
b/amber/src/test/scala/org/apache/texera/web/resource/UserConfigResourceSpec.scala
@@ -0,0 +1,153 @@
+/*
+ * 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
+
+import org.apache.texera.auth.SessionUser
+import org.apache.texera.dao.MockTexeraDB
+import org.apache.texera.dao.jooq.generated.Tables.{USER, USER_CONFIG}
+import org.apache.texera.dao.jooq.generated.tables.daos.UserDao
+import org.apache.texera.dao.jooq.generated.tables.pojos.User
+import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
+import org.scalatest.flatspec.AnyFlatSpec
+
+import javax.ws.rs.BadRequestException
+
+class UserConfigResourceSpec
+ extends AnyFlatSpec
+ with BeforeAndAfterAll
+ with BeforeAndAfterEach
+ with MockTexeraDB {
+
+ private val testUid = 4000 + scala.util.Random.nextInt(1000)
+ private val otherUid = testUid + 1
+
+ private var userDao: UserDao = _
+ private var testUser: User = _
+ private var otherUser: User = _
+ private val resource = new UserConfigResource
+
+ override protected def beforeAll(): Unit = {
+ initializeDBAndReplaceDSLContext()
+ userDao = new UserDao(getDSLContext.configuration())
+ }
+
+ override protected def afterAll(): Unit = shutdownDB()
+
+ // Every test starts from a clean slate for the ids under test, then seeds
only
+ // the two users it needs, so tests are order-independent.
+ override protected def beforeEach(): Unit = resetFixtures()
+
+ private def resetFixtures(): Unit = {
+ getDSLContext.deleteFrom(USER_CONFIG).where(USER_CONFIG.UID.in(testUid,
otherUid)).execute()
+ getDSLContext.deleteFrom(USER).where(USER.UID.in(testUid,
otherUid)).execute()
+
+ testUser = newUser(testUid, "config_user", "[email protected]")
+ otherUser = newUser(otherUid, "other_user", "[email protected]")
+ }
+
+ private def newUser(uid: Int, name: String, email: String): User = {
+ val user = new User
+ user.setUid(uid)
+ user.setName(name)
+ user.setEmail(email)
+ user.setPassword("password")
+ userDao.insert(user)
+ user
+ }
+
+ private def session(user: User): SessionUser = new SessionUser(user)
+
+ // ─── getEntry / setEntry
───────────────────────────────────────────────────
+
+ "getEntry" should "return null for a key that was never set" in {
+ assert(resource.getEntry("missing", session(testUser)) == null)
+ }
+
+ "setEntry" should "store a value that getEntry then returns" in {
+ resource.setEntry("theme", "dark", session(testUser))
+ assert(resource.getEntry("theme", session(testUser)) == "dark")
+ }
+
+ it should "overwrite the value when the same key is set again" in {
+ resource.setEntry("theme", "dark", session(testUser))
+ resource.setEntry("theme", "light", session(testUser))
+ assert(resource.getEntry("theme", session(testUser)) == "light")
+ // still a single entry, not two
+ assert(resource.getAllDict(session(testUser)).size == 1)
+ }
+
+ // ─── getAllDict
────────────────────────────────────────────────────────────
+
+ "getAllDict" should "return an empty map when the user has no entries" in {
+ assert(resource.getAllDict(session(testUser)).isEmpty)
+ }
+
+ it should "return every key/value pair the user has set" in {
+ resource.setEntry("a", "1", session(testUser))
+ resource.setEntry("b", "2", session(testUser))
+ resource.setEntry("c", "3", session(testUser))
+
+ val dict = resource.getAllDict(session(testUser))
+ assert(dict == Map("a" -> "1", "b" -> "2", "c" -> "3"))
+ }
+
+ it should "be scoped to the requesting user (another user's entries are not
returned)" in {
+ resource.setEntry("shared", "mine", session(testUser))
+ resource.setEntry("shared", "theirs", session(otherUser))
+ resource.setEntry("other-only", "x", session(otherUser))
+
+ assert(resource.getAllDict(session(testUser)) == Map("shared" -> "mine"))
+ assert(resource.getEntry("other-only", session(testUser)) == null)
+ assert(
+ resource.getAllDict(session(otherUser)) == Map("shared" -> "theirs",
"other-only" -> "x")
+ )
+ }
+
+ // ─── deleteEntry
───────────────────────────────────────────────────────────
+
+ "deleteEntry" should "remove the key so getEntry and getAllDict no longer
show it" in {
+ resource.setEntry("k1", "v1", session(testUser))
+ resource.setEntry("k2", "v2", session(testUser))
+
+ resource.deleteEntry("k1", session(testUser))
+
+ assert(resource.getEntry("k1", session(testUser)) == null)
+ assert(resource.getAllDict(session(testUser)) == Map("k2" -> "v2"))
+ }
+
+ it should "be a no-op (no error) when the key does not exist" in {
+ resource.deleteEntry("never-set", session(testUser))
+ assert(resource.getAllDict(session(testUser)).isEmpty)
+ }
+
+ // ─── key validation
────────────────────────────────────────────────────────
+
+ "getEntry" should "reject a blank key with a BadRequestException" in {
+ assertThrows[BadRequestException](resource.getEntry(" ",
session(testUser)))
+ }
+
+ "setEntry" should "reject a blank key with a BadRequestException" in {
+ assertThrows[BadRequestException](resource.setEntry("", "v",
session(testUser)))
+ }
+
+ "deleteEntry" should "reject a blank key with a BadRequestException" in {
+ assertThrows[BadRequestException](resource.deleteEntry("",
session(testUser)))
+ }
+}