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 408b33a98a test(config-service): cover ConfigService.run in its
RunSpec (#7560)
408b33a98a is described below
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 {