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

commit 408b33a98a73177ec5175806cef539603dcdb8ed
Author: Meng Wang <[email protected]>
AuthorDate: Tue Aug 11 20:58:13 2026 -0700

    test(config-service): cover ConfigService.run in its RunSpec (#7560)
    
    ### What changes were proposed in this PR?
    
    `ConfigServiceRunSpec` only asserted role annotations on the resource
    classes, so
    `ConfigService` itself was never instantiated and the file sat at 0%.
    Adds four tests
    that call `run()` against a mocked Dropwizard `Environment`, following
    `AccessControlServiceRunSpec`. The existing role-annotation assertion is
    kept.
    
    | counter | before | after |
    | --- | --- | --- |
    | line | 0/30 | 13/30 |
    | instruction | 0/171 | 99/171 |
    | branch | 0/6 | 4/6 |
    | method | 0/8 | 4/8 |
    
    `run()` itself is now fully covered by line; every remaining missed line
    is in
    `initialize()` (39-51) or `object ConfigService.main` (95-108), which
    the issue puts out
    of scope because #5983 moves that boilerplate into a shared
    `ServiceBootstrap`.
    
    What the tests pin:
    
    - the `/api/*` url pattern, the session handler on both the Jersey and
    servlet
    environments, and the `HealthCheckResource` / `ConfigResource`
    registrations;
    - the auth stack `AuthFeatures.register` installs —
    `AuthDynamicFeature`,
    `UnauthorizedExceptionMapper` and `RolesAllowedDynamicFeature` — without
    which `@Auth`
    parameters do not resolve and `@RolesAllowed` on the settings endpoints
    is ignored;
    - `RequestLoggingFilter.register(environment.getApplicationContext)`,
    verified as the
      `addFilter(FilterHolder, "/*", …)` it performs;
    - the default-settings preload, checked against the database rather than
    a mock: every
    entry of `DefaultsConfig.allDefaults` must be present in `site_settings`
    with its value;
    - that a preload failure is rethrown rather than swallowed — a service
    that came up with
      no settings would look healthy while serving none of them.
    
    `run()` ends by writing `default.conf` into `site_settings`, so it needs
    a live
    `SqlServer`; mocking the `Environment` alone cannot reach the
    request-logging filter that
    follows. The suite therefore mixes in `MockTexeraDB`, which gives it its
    own embedded
    database and points `SqlServer` at it — `config-service` already
    declares
    `.dependsOn(DAO % "test->test")` for exactly this, and
    `ConfigResourceSpec` in the same
    module already does it. The failure case swaps in a `ConnectionProvider`
    that cannot
    acquire a connection; `MockTexeraDB`'s fixture reinstalls the healthy
    context before the
    next test, so it stays local (the fixture does not truncate tables, so
    dropping one would
    not have).
    
    The two branches still missed are not application logic: one on the
    class declaration
    (`with LazyLogging`, 3/4 arms covered) and the implicit non-`Exception`
    arm of
    `case ex: Exception`.
    
    No production code was changed.
    
    ### Any related issues, documentation, discussions?
    
    Closes #7558.
    
    ### How was this PR tested?
    
    `sbt "ConfigService/testOnly *ConfigServiceRunSpec"` — 5 tests pass, run
    repeatedly with
    the same result; `sbt ConfigService/jacoco` over the module is green (38
    tests) and gives
    the table above. The failure path was verified by breaking the
    url-pattern assertion (red,
    non-zero exit) and restoring it. `ConfigService/Test/scalafmtCheck` and
    `ConfigService/Test/scalafix --check` are clean.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code (Opus 4.8 [1M context])
---
 .../texera/service/ConfigServiceRunSpec.scala      | 132 ++++++++++++++++++++-
 1 file changed, 130 insertions(+), 2 deletions(-)

diff --git 
a/config-service/src/test/scala/org/apache/texera/service/ConfigServiceRunSpec.scala
 
b/config-service/src/test/scala/org/apache/texera/service/ConfigServiceRunSpec.scala
index 1481b311e6..a5c14da61f 100644
--- 
a/config-service/src/test/scala/org/apache/texera/service/ConfigServiceRunSpec.scala
+++ 
b/config-service/src/test/scala/org/apache/texera/service/ConfigServiceRunSpec.scala
@@ -19,12 +19,140 @@
 
 package org.apache.texera.service
 
-import org.apache.texera.auth.RoleAnnotationEnforcer
+import io.dropwizard.auth.AuthDynamicFeature
+import io.dropwizard.core.setup.Environment
+import io.dropwizard.jersey.DropwizardResourceConfig
+import io.dropwizard.jersey.setup.JerseyEnvironment
+import io.dropwizard.jetty.MutableServletContextHandler
+import io.dropwizard.jetty.setup.ServletEnvironment
+import org.apache.texera.auth.{RoleAnnotationEnforcer, 
UnauthorizedExceptionMapper}
+import org.apache.texera.common.config.DefaultsConfig
+import org.apache.texera.dao.{MockTexeraDB, SqlServer}
+import org.apache.texera.dao.jooq.generated.Tables.SITE_SETTINGS
 import org.apache.texera.service.resource.{ConfigResource, HealthCheckResource}
+import org.eclipse.jetty.server.session.SessionHandler
+import org.eclipse.jetty.servlet.FilterHolder
+import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature
+import org.jooq.{ConnectionProvider, SQLDialect}
+import org.jooq.impl.{DSL, DefaultConfiguration}
+import org.mockito.ArgumentMatchers.{any, eq => eqTo, isA}
+import org.mockito.Mockito.{mock, verify, when}
+import org.scalatest.BeforeAndAfterAll
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.matchers.should.Matchers
 
-class ConfigServiceRunSpec extends AnyFlatSpec with Matchers {
+import java.sql.{Connection, SQLException}
+
+// `run` ends by preloading default.conf into site_settings, so it needs a live
+// SqlServer: MockTexeraDB gives this suite its own embedded database and 
points
+// SqlServer at it, which lets the whole method — including the request-logging
+// filter installed after the preload — run against mocked Dropwizard wiring.
+class ConfigServiceRunSpec
+    extends AnyFlatSpec
+    with Matchers
+    with BeforeAndAfterAll
+    with MockTexeraDB {
+
+  override protected def beforeAll(): Unit = initializeDBAndReplaceDSLContext()
+
+  override protected def afterAll(): Unit = shutdownDB()
+
+  "ConfigService.run" should "install the API prefix, session handling and its 
resources" in {
+    val jersey = mock(classOf[JerseyEnvironment])
+    val servlets = mock(classOf[ServletEnvironment])
+    val context = mock(classOf[MutableServletContextHandler])
+    val env = mock(classOf[Environment])
+    when(env.jersey).thenReturn(jersey)
+    when(env.servlets).thenReturn(servlets)
+    when(env.getApplicationContext).thenReturn(context)
+    
when(jersey.getResourceConfig).thenReturn(DropwizardResourceConfig.forTesting())
+
+    new ConfigService().run(mock(classOf[ConfigServiceConfiguration]), env)
+
+    // Everything the service serves lives under /api; losing this silently 
moves every
+    // endpoint to the root.
+    verify(jersey).setUrlPattern("/api/*")
+    verify(jersey).register(classOf[SessionHandler])
+    verify(servlets).setSessionHandler(isA(classOf[SessionHandler]))
+    verify(jersey).register(classOf[HealthCheckResource])
+    verify(jersey).register(isA(classOf[ConfigResource]))
+  }
+
+  it should "install the auth stack and the request logging filter" in {
+    val jersey = mock(classOf[JerseyEnvironment])
+    val servlets = mock(classOf[ServletEnvironment])
+    val context = mock(classOf[MutableServletContextHandler])
+    val env = mock(classOf[Environment])
+    when(env.jersey).thenReturn(jersey)
+    when(env.servlets).thenReturn(servlets)
+    when(env.getApplicationContext).thenReturn(context)
+    
when(jersey.getResourceConfig).thenReturn(DropwizardResourceConfig.forTesting())
+
+    new ConfigService().run(mock(classOf[ConfigServiceConfiguration]), env)
+
+    // AuthFeatures.register: without these, @Auth parameters do not resolve 
and
+    // @RolesAllowed on the settings endpoints is ignored.
+    verify(jersey).register(isA(classOf[AuthDynamicFeature]))
+    verify(jersey).register(classOf[UnauthorizedExceptionMapper])
+    verify(jersey).register(classOf[RolesAllowedDynamicFeature])
+
+    // RequestLoggingFilter.register, which runs only after the preload below 
succeeds.
+    verify(context).addFilter(isA(classOf[FilterHolder]), eqTo("/*"), any())
+  }
+
+  it should "preload the default settings into site_settings" in {
+    val jersey = mock(classOf[JerseyEnvironment])
+    val env = mock(classOf[Environment])
+    when(env.jersey).thenReturn(jersey)
+    when(env.servlets).thenReturn(mock(classOf[ServletEnvironment]))
+    
when(env.getApplicationContext).thenReturn(mock(classOf[MutableServletContextHandler]))
+    
when(jersey.getResourceConfig).thenReturn(DropwizardResourceConfig.forTesting())
+
+    // The fixture does not truncate between tests and the runs above already 
seeded the
+    // table, so start from empty: otherwise this passes even for a run() that 
skipped the
+    // preload entirely.
+    getDSLContext.deleteFrom(SITE_SETTINGS).execute()
+
+    new ConfigService().run(mock(classOf[ConfigServiceConfiguration]), env)
+
+    DefaultsConfig.allDefaults should not be empty
+    DefaultsConfig.allDefaults.foreach {
+      case (key, value) =>
+        val stored = getDSLContext
+          .select(SITE_SETTINGS.VALUE)
+          .from(SITE_SETTINGS)
+          .where(SITE_SETTINGS.KEY.eq(key))
+          .fetchOne()
+        withClue(s"site_settings row for '$key': ") {
+          stored should not be null
+          stored.value1() shouldBe value
+        }
+    }
+  }
+
+  it should "surface a failed preload instead of starting without the 
defaults" in {
+    val jersey = mock(classOf[JerseyEnvironment])
+    val env = mock(classOf[Environment])
+    when(env.jersey).thenReturn(jersey)
+    when(env.servlets).thenReturn(mock(classOf[ServletEnvironment]))
+    
when(env.getApplicationContext).thenReturn(mock(classOf[MutableServletContextHandler]))
+    
when(jersey.getResourceConfig).thenReturn(DropwizardResourceConfig.forTesting())
+
+    // Point SqlServer at a context that cannot acquire a connection. 
MockTexeraDB's fixture
+    // reinstalls the suite's healthy context before the next test, so this 
stays local.
+    val unusable = new DefaultConfiguration()
+    unusable.set(SQLDialect.POSTGRES)
+    unusable.set(new ConnectionProvider {
+      override def acquire(): Connection = throw new SQLException("database 
unavailable")
+      override def release(connection: Connection): Unit = ()
+    })
+    SqlServer.getInstance().replaceDSLContext(DSL.using(unusable))
+
+    // Rethrown rather than swallowed: a service that came up with no settings 
would look
+    // healthy while serving none of them.
+    a[RuntimeException] should be thrownBy new ConfigService()
+      .run(mock(classOf[ConfigServiceConfiguration]), env)
+  }
 
   // Every endpoint this service registers declares 
@RolesAllowed/@PermitAll/@DenyAll.
   "ConfigService's registered resources" should "all declare access control" 
in {

Reply via email to