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 05:33:23 UTC

(texera) branch gh-readonly-queue/main/pr-7191-01071832cd2e64d45b54f46ffd193d1ffa359299 created (now 91a036bf4f)

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


      at 91a036bf4f test(amber): cover email notification dispatch (#7191)

This branch includes the following new commits:

     new 91a036bf4f test(amber): cover email notification dispatch (#7191)

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): cover email notification dispatch (#7191)

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

commit 91a036bf4f78d862b5f63b0360bcb5d769b6d2bb
Author: Xinyuan Lin <xi...@uci.edu>
AuthorDate: Fri Jul 31 22:33:02 2026 -0700

    test(amber): cover email notification dispatch (#7191)
    
    ### What changes were proposed in this PR?
    
    Adds focused unit coverage for email notification dispatch, suppression,
    and recovery from sender exceptions without requiring external
    infrastructure.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7188
    
    ### How was this PR tested?
    
    - `WorkflowExecutionService/testOnly
    org.apache.texera.web.service.EmailNotificationServiceSpec` — 3 passed.
    - `WorkflowExecutionService/Test/scalafmtAll` and
    `WorkflowExecutionService/Test/scalafix`
    - `WorkflowExecutionService/Test/scalafmtCheck` and
    `WorkflowExecutionService/Test/scalafix --check`
    
    Mutation proof (each production change was reverted):
    
    | Production mutation | Focused test result |
    | --- | --- |
    | Inverted the send predicate | 3 failed |
    | Replaced status-email dispatch with a no-op | 2 failed |
    | Removed exception recovery | 1 failed |
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Codex (GPT-5)
---
 .../web/service/EmailNotificationServiceSpec.scala | 99 ++++++++++++++++++++++
 1 file changed, 99 insertions(+)

diff --git a/amber/src/test/scala/org/apache/texera/web/service/EmailNotificationServiceSpec.scala b/amber/src/test/scala/org/apache/texera/web/service/EmailNotificationServiceSpec.scala
new file mode 100644
index 0000000000..5eff5eb325
--- /dev/null
+++ b/amber/src/test/scala/org/apache/texera/web/service/EmailNotificationServiceSpec.scala
@@ -0,0 +1,99 @@
+/*
+ * 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.service
+
+import org.apache.texera.amber.engine.architecture.rpc.controlreturns.WorkflowAggregatedState
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+import java.util.concurrent.ConcurrentLinkedQueue
+import scala.concurrent.Await
+import scala.concurrent.duration.DurationInt
+import scala.jdk.CollectionConverters._
+
+class EmailNotificationServiceSpec extends AnyFlatSpec with Matchers {
+
+  private val completedState = WorkflowAggregatedState.COMPLETED
+  private val awaitTimeout = 2.seconds
+
+  private class RecordingNotifier(
+      shouldSend: Boolean,
+      failureOnSend: Option[RuntimeException] = None
+  ) extends EmailNotifier {
+    val evaluatedStates = new ConcurrentLinkedQueue[WorkflowAggregatedState]()
+    val sentStates = new ConcurrentLinkedQueue[WorkflowAggregatedState]()
+
+    override def shouldSendEmail(workflowState: WorkflowAggregatedState): Boolean = {
+      evaluatedStates.add(workflowState)
+      shouldSend
+    }
+
+    override def sendStatusEmail(state: WorkflowAggregatedState): Unit = {
+      sentStates.add(state)
+      failureOnSend.foreach(throw _)
+    }
+  }
+
+  private def withService(notifier: EmailNotifier)(test: EmailNotificationService => Unit): Unit = {
+    val service = new EmailNotificationService(notifier)
+    try {
+      test(service)
+    } finally {
+      service.shutdown()
+    }
+  }
+
+  "processEmailNotificationIfNeeded" should "send the exact state when the notifier requests it" in {
+    val notifier = new RecordingNotifier(shouldSend = true)
+
+    withService(notifier) { service =>
+      Await.result(service.processEmailNotificationIfNeeded(completedState), awaitTimeout)
+
+      notifier.evaluatedStates.asScala.toList shouldBe List(completedState)
+      notifier.sentStates.asScala.toList shouldBe List(completedState)
+    }
+  }
+
+  it should "not send an email when the notifier declines it" in {
+    val notifier = new RecordingNotifier(shouldSend = false)
+
+    withService(notifier) { service =>
+      Await.result(service.processEmailNotificationIfNeeded(completedState), awaitTimeout)
+
+      notifier.evaluatedStates.asScala.toList shouldBe List(completedState)
+      notifier.sentStates.asScala.toList shouldBe empty
+    }
+  }
+
+  it should "recover from an exception raised while sending an email" in {
+    val notifier = new RecordingNotifier(
+      shouldSend = true,
+      failureOnSend = Some(new RuntimeException("email provider unavailable"))
+    )
+
+    withService(notifier) { service =>
+      noException should be thrownBy {
+        Await.result(service.processEmailNotificationIfNeeded(completedState), awaitTimeout)
+      }
+
+      notifier.sentStates.asScala.toList shouldBe List(completedState)
+    }
+  }
+}