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 {
