Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/*
* 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.admin.user

import org.apache.texera.dao.MockTexeraDB
import org.apache.texera.dao.jooq.generated.Tables._
import org.apache.texera.dao.jooq.generated.enums.{PrivilegeEnum, UserRoleEnum}
import org.apache.texera.dao.jooq.generated.tables.daos.{
UserDao,
WorkflowDao,
WorkflowExecutionsDao,
WorkflowOfUserDao,
WorkflowUserAccessDao,
WorkflowVersionDao
}
import org.apache.texera.dao.jooq.generated.tables.pojos.{
User,
Workflow,
WorkflowExecutions,
WorkflowOfUser,
WorkflowUserAccess,
WorkflowVersion
}
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import java.sql.Timestamp
import java.util.UUID
import javax.ws.rs.WebApplicationException
import scala.jdk.CollectionConverters._

class AdminUserResourceSpec
extends AnyFlatSpec
with Matchers
with BeforeAndAfterAll
with BeforeAndAfterEach
with MockTexeraDB {

private val primaryUid = 90000 + scala.util.Random.nextInt(5000)
private val secondaryUid = primaryUid + 1
private val testWid = 90000 + scala.util.Random.nextInt(5000)

private var userDao: UserDao = _
private var workflowDao: WorkflowDao = _
private var workflowVersionDao: WorkflowVersionDao = _
private var workflowExecutionsDao: WorkflowExecutionsDao = _
private var workflowOfUserDao: WorkflowOfUserDao = _
private var workflowUserAccessDao: WorkflowUserAccessDao = _

private val resource = new AdminUserResource

override protected def beforeAll(): Unit = {
initializeDBAndReplaceDSLContext()
userDao = new UserDao(getDSLContext.configuration())
workflowDao = new WorkflowDao(getDSLContext.configuration())
workflowVersionDao = new WorkflowVersionDao(getDSLContext.configuration())
workflowExecutionsDao = new WorkflowExecutionsDao(getDSLContext.configuration())
workflowOfUserDao = new WorkflowOfUserDao(getDSLContext.configuration())
workflowUserAccessDao = new WorkflowUserAccessDao(getDSLContext.configuration())
}

override protected def afterAll(): Unit = closeConnectionPool()

// Wipe everything this spec seeds, children before parents, after each test.
private def cleanup(): Unit = {
getDSLContext
.deleteFrom(WORKFLOW_EXECUTIONS)
.where(WORKFLOW_EXECUTIONS.UID.in(primaryUid, secondaryUid))
.execute()
getDSLContext.deleteFrom(WORKFLOW_VERSION).where(WORKFLOW_VERSION.WID.eq(testWid)).execute()
getDSLContext
.deleteFrom(WORKFLOW_USER_ACCESS)
.where(WORKFLOW_USER_ACCESS.WID.eq(testWid))
.execute()
getDSLContext.deleteFrom(WORKFLOW_OF_USER).where(WORKFLOW_OF_USER.WID.eq(testWid)).execute()
getDSLContext.deleteFrom(WORKFLOW).where(WORKFLOW.WID.eq(testWid)).execute()
getDSLContext.deleteFrom(USER).where(USER.UID.in(primaryUid, secondaryUid)).execute()
// addUser() inserts an INACTIVE user with an auto-generated uid and a "User<millis>" name.
getDSLContext
.deleteFrom(USER)
.where(USER.ROLE.eq(UserRoleEnum.INACTIVE).and(USER.NAME.like("User%")))
.execute()
}

private def makeUser(uid: Int, name: String, role: UserRoleEnum = UserRoleEnum.REGULAR): User = {
val user = new User
user.setUid(uid)
user.setName(name)
user.setEmail(
s"admin_user_spec_${uid}_${UUID.randomUUID().toString.substring(0, 8)}@example.com"
)
user.setPassword("password")
user.setRole(role)
user
}

private def seedWorkflow(): Workflow = {
val workflow = new Workflow
workflow.setWid(testWid)
workflow.setName("admin_user_spec_wf_" + UUID.randomUUID().toString.substring(0, 8))
workflow.setContent("{}")
workflow.setDescription("desc")
workflow.setCreationTime(new Timestamp(System.currentTimeMillis()))
workflow.setLastModifiedTime(new Timestamp(System.currentTimeMillis()))
workflowDao.insert(workflow)
workflow
}

private def seedExecution(uid: Int): WorkflowExecutions = {
seedWorkflow()
val version = new WorkflowVersion
version.setWid(testWid)
version.setContent("{}")
version.setCreationTime(new Timestamp(System.currentTimeMillis()))
workflowVersionDao.insert(version)

val execution = new WorkflowExecutions
execution.setVid(version.getVid)
execution.setUid(uid)
execution.setStatus(0.toByte)
execution.setResult("")
execution.setLogLocation("")
execution.setStartingTime(new Timestamp(System.currentTimeMillis()))
execution.setBookmarked(false)
execution.setName("admin_user_spec_exec")
execution.setEnvironmentVersion("test-env-1.0")
workflowExecutionsDao.insert(execution)
execution
}

override protected def afterEach(): Unit = cleanup()

// ─── list ───────────────────────────────────────────────────────────────

"list" should "return the seeded users with their name, email, and role" in {
val a = makeUser(primaryUid, "alice", UserRoleEnum.ADMIN)
val b = makeUser(secondaryUid, "bob", UserRoleEnum.REGULAR)
userDao.insert(a)
userDao.insert(b)

val listed = resource.list().asScala
val alice = listed.find(_.uid == primaryUid)
val bob = listed.find(_.uid == secondaryUid)

alice.map(u => (u.name, u.email, u.role)) shouldBe Some(
("alice", a.getEmail, UserRoleEnum.ADMIN)
)
bob.map(u => (u.name, u.email, u.role)) shouldBe Some(("bob", b.getEmail, UserRoleEnum.REGULAR))
}

it should "not return a user that has not been seeded" in {
resource.list().asScala.exists(_.uid == primaryUid) shouldBe false
}

// ─── addUser ────────────────────────────────────────────────────────────

"addUser" should "persist a new INACTIVE user" in {
val before = userDao.fetchByRole(UserRoleEnum.INACTIVE).size()

resource.addUser()

val after = userDao.fetchByRole(UserRoleEnum.INACTIVE)
after.size() shouldBe before + 1
// The newly added user has a generated non-empty name and no password left blank.
after.asScala.exists(u => u.getName.startsWith("User") && u.getPassword != null) shouldBe true
}

// ─── updateUser ─────────────────────────────────────────────────────────

"updateUser" should "update a user's editable fields and round-trip via a re-read" in {
val user = makeUser(primaryUid, "original", UserRoleEnum.REGULAR)
userDao.insert(user)

val edit = new User
edit.setUid(primaryUid)
edit.setName("renamed")
edit.setEmail(user.getEmail) // unchanged email → no conflict
edit.setRole(UserRoleEnum.REGULAR) // unchanged role → no e-mail side effect
edit.setComment("a new comment")
resource.updateUser(edit)

val reread = userDao.fetchOneByUid(primaryUid)
reread.getName shouldBe "renamed"
reread.getComment shouldBe "a new comment"
reread.getRole shouldBe UserRoleEnum.REGULAR
}

it should "reject an update whose email already belongs to another user" in {
val alice = makeUser(primaryUid, "alice", UserRoleEnum.REGULAR)
val bob = makeUser(secondaryUid, "bob", UserRoleEnum.REGULAR)
userDao.insert(alice)
userDao.insert(bob)

val edit = new User
edit.setUid(secondaryUid) // updating bob…
edit.setName("bob")
edit.setEmail(alice.getEmail) // …to alice's email → conflict
edit.setRole(UserRoleEnum.REGULAR)

a[WebApplicationException] should be thrownBy resource.updateUser(edit)
}

// ─── getCreatedWorkflow ───────────────────────────────────────────────────

"getCreatedWorkflow" should "return an empty list for a user with no created workflows" in {
userDao.insert(makeUser(primaryUid, "creator"))
resource.getCreatedWorkflow(primaryUid) shouldBe empty
}

it should "return the workflows the user created" in {
userDao.insert(makeUser(primaryUid, "creator"))
val workflow = seedWorkflow()
val ownership = new WorkflowOfUser
ownership.setUid(primaryUid)
ownership.setWid(testWid)
workflowOfUserDao.insert(ownership)

val created = resource.getCreatedWorkflow(primaryUid)
created.map(_.workflowId) shouldBe List(Integer.valueOf(testWid))
created.head.workflowName shouldBe workflow.getName
}

// ─── getAccessedWorkflow ──────────────────────────────────────────────────

"getAccessedWorkflow" should "return an empty list for a user with no workflow access" in {
userDao.insert(makeUser(primaryUid, "viewer"))
resource.getAccessedWorkflow(primaryUid).asScala shouldBe empty
}

it should "return the workflow ids the user can access" in {
userDao.insert(makeUser(primaryUid, "viewer"))
seedWorkflow()
val access = new WorkflowUserAccess
access.setUid(primaryUid)
access.setWid(testWid)
access.setPrivilege(PrivilegeEnum.READ)
workflowUserAccessDao.insert(access)

resource.getAccessedWorkflow(primaryUid).asScala should contain(Integer.valueOf(testWid))
}

// ─── getUserQuota ─────────────────────────────────────────────────────────

"getUserQuota" should "return an empty array for a user with no executions" in {
userDao.insert(makeUser(primaryUid, "quota_user"))
resource.getUserQuota(primaryUid) shouldBe empty
}

it should "return one quota entry per execution the user owns" in {
userDao.insert(makeUser(primaryUid, "quota_user"))
val execution = seedExecution(primaryUid)

val quota = resource.getUserQuota(primaryUid)
quota should have length 1
quota.head.eid shouldBe execution.getEid
quota.head.workflowId shouldBe Integer.valueOf(testWid)
// No operator_port/operator rows seeded → result and log sizes are zero.
quota.head.resultBytes shouldBe 0L
quota.head.logBytes shouldBe 0L
}

// ─── deleteCollection ─────────────────────────────────────────────────────

"deleteCollection" should "delete the target execution row" in {
userDao.insert(makeUser(primaryUid, "collection_user"))
val execution = seedExecution(primaryUid)
workflowExecutionsDao.fetchOneByEid(execution.getEid) should not be null

resource.deleteCollection(execution.getEid)

workflowExecutionsDao.fetchOneByEid(execution.getEid) shouldBe null
}
}
Loading