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-7399-a38bc6659d314b5cbbc32a2233934bb3e0d4ce37
in repository https://gitbox.apache.org/repos/asf/texera.git

commit c5c2c6f8c2aeb746929a5f56b9d36eaa0373848e
Author: Eugene Gu <[email protected]>
AuthorDate: Sat Aug 8 00:06:27 2026 -0700

    test(amber): add unit tests cover ProjectSearchQueryBuilder (#7399)
    
    ### What changes were proposed in this PR?
    
    Adds `ProjectSearchQueryBuilderSpec`, a connection-free unit spec for
    the project arm of the unified dashboard search.
    `ProjectSearchQueryBuilder` was the only search query builder without
    tests — `WorkflowSearchQueryBuilderSpec` already exists, and
    `DatasetSearchQueryBuilder` is driven from `DatasetResourceSpec`.
    
    The spec follows the same conventions as the sibling
    `WorkflowSearchQueryBuilderSpec` (in-memory jOOQ records rendered with
    the Postgres dialect, no database connection) and covers the two members
    the object widens from the trait's `protected` to public:
    
    **`toEntryImpl` — the record-to-DTO mapping (4 tests):** copies every
    `PROJECT` column into the `Project` POJO with distinct fixture values so
    a wrong-column read cannot pass; passes NULL description/color through
    (the only two nullable project columns); tags the entry `resourceType ==
    "project"` with the `workflow`/`dataset` payload slots `None` (the
    dashboard dispatch matches on this value with no default branch, so
    drift is a runtime `MatchError`); and produces an identical entry
    regardless of the caller's uid (the project arm computes no ownership
    flag).
    
    **`mappedResourceSchema` — the projection the union and dispatch depend
    on (5 tests):** pins the inline `'project'` resourceType literal; pins
    that `PROJECT.CREATION_TIME` is aliased as both the creation and
    last-modified time — a deliberate alias (the project table has no
    last-modified column) that keeps projects sortable by edit time instead
    of NULL-sinking; and pins the color, pid/ownerId, and name/description
    projection slots.
    
    `constructFromClause`, `constructWhereClause`, and `getGroupByFields`
    stay `override protected` (Scala `protected` grants no same-package
    access), so they are intentionally out of scope.
    
    Every test was mutation-checked: 8 distinct hand-applied mutations of
    the production object (literal changes, column swaps, dropped alias,
    empty-POJO copy, `project = None`) each fail the intended test, with
    production sources restored afterwards. No production file is touched.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7398
    
    ### How was this PR tested?
    
    Nine new tests; the spec is the only change:
    
    ```
    sbt "WorkflowExecutionService/testOnly 
org.apache.texera.web.resource.dashboard.ProjectSearchQueryBuilderSpec"
    ```
    
    ```
    [info] Tests: succeeded 9, failed 0, canceled 0, ignored 0, pending 0
    [info] All tests passed.
    ```
    
    The file is formatted with the project's scalafmt (no diff on re-run).
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Fable 5)
---
 .../dashboard/ProjectSearchQueryBuilderSpec.scala  | 141 +++++++++++++++++++++
 1 file changed, 141 insertions(+)

diff --git 
a/amber/src/test/scala/org/apache/texera/web/resource/dashboard/ProjectSearchQueryBuilderSpec.scala
 
b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/ProjectSearchQueryBuilderSpec.scala
new file mode 100644
index 0000000000..0a05846dda
--- /dev/null
+++ 
b/amber/src/test/scala/org/apache/texera/web/resource/dashboard/ProjectSearchQueryBuilderSpec.scala
@@ -0,0 +1,141 @@
+/*
+ * 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
+
+import org.apache.texera.dao.jooq.generated.Tables.PROJECT
+import org.apache.texera.dao.jooq.generated.tables.pojos.Project
+import org.jooq.impl.{DSL => JDSL}
+import org.jooq.{Record, SQLDialect}
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+import java.sql.Timestamp
+
+class ProjectSearchQueryBuilderSpec extends AnyFlatSpec with Matchers {
+
+  private val ctx = JDSL.using(SQLDialect.POSTGRES)
+
+  private val ownerUid: Integer = Integer.valueOf(42)
+  private val callerUid: Integer = Integer.valueOf(43)
+  private val pid: Integer = Integer.valueOf(7)
+  private val createdAt = new Timestamp(1700000000123L)
+
+  // In-memory record shaped like the one toEntryImpl receives (keyed by the
+  // original PROJECT fields). Values are distinct so a wrong-column read 
fails.
+  private def translatedRecord(
+      description: String = "proj-description",
+      color: String = "aabbcc"
+  ): Record = {
+    val record = ctx.newRecord(
+      PROJECT.PID,
+      PROJECT.NAME,
+      PROJECT.DESCRIPTION,
+      PROJECT.OWNER_ID,
+      PROJECT.CREATION_TIME,
+      PROJECT.COLOR
+    )
+    record.set(PROJECT.PID, pid)
+    record.set(PROJECT.NAME, "proj-name")
+    record.set(PROJECT.DESCRIPTION, description)
+    record.set(PROJECT.OWNER_ID, ownerUid)
+    record.set(PROJECT.CREATION_TIME, createdAt)
+    record.set(PROJECT.COLOR, color)
+    record
+  }
+
+  private def projectOf(record: Record, uid: Integer): Project =
+    ProjectSearchQueryBuilder.toEntryImpl(uid, record).project.get
+
+  "toEntryImpl" should "copy every project column into the POJO" in {
+    val p = projectOf(translatedRecord(), ownerUid)
+    p.getPid shouldBe pid
+    p.getName shouldBe "proj-name"
+    p.getDescription shouldBe "proj-description"
+    p.getOwnerId shouldBe ownerUid
+    p.getCreationTime shouldBe createdAt
+    p.getColor shouldBe "aabbcc"
+  }
+
+  it should "pass a NULL description and a NULL color through as null" in {
+    // description and color are the only nullable project columns.
+    val p = projectOf(translatedRecord(description = null, color = null), 
ownerUid)
+    p.getDescription shouldBe null
+    p.getColor shouldBe null
+    p.getPid shouldBe pid
+  }
+
+  it should "tag the entry as a project and leave the other payload slots 
empty" in {
+    // searchAllResources matches on resourceType with no default branch, so a
+    // wrong tag is a runtime MatchError.
+    val entry = ProjectSearchQueryBuilder.toEntryImpl(ownerUid, 
translatedRecord())
+    entry.resourceType shouldBe "project"
+    entry.project should not be None
+    entry.workflow shouldBe None
+    entry.dataset shouldBe None
+  }
+
+  it should "produce the same entry regardless of the caller's uid" in {
+    // Unlike the workflow arm there is no ownership flag to compute.
+    val record = translatedRecord()
+    ProjectSearchQueryBuilder.toEntryImpl(ownerUid, record) shouldBe
+      ProjectSearchQueryBuilder.toEntryImpl(callerUid, record)
+  }
+
+  // An aliased field renders as the bare alias on its own, so render a SELECT.
+  private lazy val renderedSchema: String = ctx.renderInlined(
+    JDSL.select(ProjectSearchQueryBuilder.mappedResourceSchema.allFields: _*)
+  )
+
+  "mappedResourceSchema" should "project the literal 'project' as the 
resourceType column" in {
+    renderedSchema should include("'project' as \"resourceType\"")
+  }
+
+  it should "alias PROJECT.CREATION_TIME as both the creation and 
last-modified time" in {
+    // Deliberate: the project table has no last-modified column. Without this
+    // alias projects would NULL-sink in every sort by edit time.
+    val creationTime = ctx.renderInlined(PROJECT.CREATION_TIME)
+    renderedSchema should include(s"""$creationTime as 
"resourceCreationTime"""")
+    renderedSchema should include(s"""$creationTime as 
"resourceLastModifiedTime"""")
+  }
+
+  it should "project PROJECT.COLOR as the color column" in {
+    // The frontend reads the project colour swatch from this alias.
+    val color = ctx.renderInlined(PROJECT.COLOR)
+    renderedSchema should include(s"""$color as "color"""")
+  }
+
+  it should "project the project id and owner through the shared slots" in {
+    // The owner rides the generic resourceOwnerId slot; the project-specific
+    // "owner_uid" slot stays NULL and is deliberately not pinned here.
+    renderedSchema should include(s"""${ctx.renderInlined(PROJECT.PID)} as 
"pid"""")
+    renderedSchema should include(
+      s"""${ctx.renderInlined(PROJECT.OWNER_ID)} as "resourceOwnerId""""
+    )
+  }
+
+  it should "project the name and description through the shared full-text 
slots" in {
+    // Also the two columns the keyword filter targets.
+    renderedSchema should include(s"""${ctx.renderInlined(PROJECT.NAME)} as 
"resourceName"""")
+    renderedSchema should include(
+      s"""${ctx.renderInlined(PROJECT.DESCRIPTION)} as "resourceDescription""""
+    )
+  }
+}

Reply via email to