From 0193bf1447c8380ef01af2adae30b0148ea20ea8 Mon Sep 17 00:00:00 2001 From: Robert Stupp Date: Thu, 16 Jul 2026 13:03:37 +0200 Subject: [PATCH] Add standalone production smoke coverage The standalone artifact had no consumer-style JVM coverage for the supported Jackson and generated-protobuf combinations, and the native smoke only covered the base ScriptHost path. Add Gradle JVM test suites that compile and execute against the produced standalone jar with Jackson 2 or 3 and either generated protobuf artifact. Extend the standalone Quarkus smoke to exercise the Jackson 2 registry in both JVM and native runs. --- .../smoke/standalone/SmokeResource.java | 50 +++++++++++++- .../smoke/standalone/SmokeResourceTest.java | 3 +- standalone/build.gradle.kts | 63 ++++++++++++++++++ .../smoke/StandaloneJackson2SmokeTest.java | 64 ++++++++++++++++++ .../smoke/StandaloneJackson3SmokeTest.java | 65 +++++++++++++++++++ 5 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 standalone/src/standaloneJackson2Smoke/java/org/projectnessie/cel/standalone/smoke/StandaloneJackson2SmokeTest.java create mode 100644 standalone/src/standaloneJackson3Smoke/java/org/projectnessie/cel/standalone/smoke/StandaloneJackson3SmokeTest.java diff --git a/quarkus-smoke-standalone/src/main/java/org/projectnessie/cel/quarkus/smoke/standalone/SmokeResource.java b/quarkus-smoke-standalone/src/main/java/org/projectnessie/cel/quarkus/smoke/standalone/SmokeResource.java index 1ccf0bae..aebc242a 100644 --- a/quarkus-smoke-standalone/src/main/java/org/projectnessie/cel/quarkus/smoke/standalone/SmokeResource.java +++ b/quarkus-smoke-standalone/src/main/java/org/projectnessie/cel/quarkus/smoke/standalone/SmokeResource.java @@ -17,14 +17,17 @@ import static java.util.Map.of; +import io.quarkus.runtime.annotations.RegisterForReflection; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; import java.time.Instant; +import java.util.List; import org.projectnessie.cel.checker.Decls; import org.projectnessie.cel.tools.Script; import org.projectnessie.cel.tools.ScriptHost; +import org.projectnessie.cel.types.jackson.JacksonRegistry; @Path("/cel/native-smoke") public class SmokeResource { @@ -32,9 +35,16 @@ public class SmokeResource { "resource.name.startsWith(\"projects/_/buckets/example/objects/reports/\")" + " && request.time < timestamp(\"2026-08-01T00:00:00Z\")"; + private static final String JACKSON_EXPRESSION = + "input.name == \"reports\" && input.labels.exists(label, label == \"finance\")"; + @GET @Produces(MediaType.APPLICATION_JSON) public SmokeResponse smoke() throws Exception { + return new SmokeResponse("cel-standalone", evaluateBaseScript(), evaluateJacksonScript()); + } + + private boolean evaluateBaseScript() throws Exception { Script script = ScriptHost.newBuilder() .build() @@ -53,8 +63,44 @@ public SmokeResponse smoke() throws Exception { "request.time", Instant.parse("2026-07-31T23:59:59Z"))); - return new SmokeResponse("cel-standalone", allowed); + return allowed; + } + + private boolean evaluateJacksonScript() throws Exception { + Script script = + ScriptHost.newBuilder() + .registry(JacksonRegistry.newRegistry()) + .build() + .buildScript(JACKSON_EXPRESSION) + .withDeclarations(Decls.newVar("input", Decls.newObjectType(Input.class.getName()))) + .withTypes(Input.class) + .build(); + + Boolean allowed = + script.execute( + Boolean.class, of("input", new Input("reports", List.of("finance", "quarterly")))); + + return allowed; } - public record SmokeResponse(String engine, boolean allowed) {} + public record SmokeResponse(String engine, boolean base, boolean jackson) {} + + @RegisterForReflection + public static final class Input { + private final String name; + private final List labels; + + public Input(String name, List labels) { + this.name = name; + this.labels = labels; + } + + public String getName() { + return name; + } + + public List getLabels() { + return labels; + } + } } diff --git a/quarkus-smoke-standalone/src/test/java/org/projectnessie/cel/quarkus/smoke/standalone/SmokeResourceTest.java b/quarkus-smoke-standalone/src/test/java/org/projectnessie/cel/quarkus/smoke/standalone/SmokeResourceTest.java index 3463c071..ddb6e485 100644 --- a/quarkus-smoke-standalone/src/test/java/org/projectnessie/cel/quarkus/smoke/standalone/SmokeResourceTest.java +++ b/quarkus-smoke-standalone/src/test/java/org/projectnessie/cel/quarkus/smoke/standalone/SmokeResourceTest.java @@ -31,6 +31,7 @@ void evaluatesCelExpression() { .then() .statusCode(200) .body("engine", equalTo("cel-standalone")) - .body("allowed", equalTo(true)); + .body("base", equalTo(true)) + .body("jackson", equalTo(true)); } } diff --git a/standalone/build.gradle.kts b/standalone/build.gradle.kts index 05544406..bb4a05d9 100644 --- a/standalone/build.gradle.kts +++ b/standalone/build.gradle.kts @@ -15,9 +15,11 @@ */ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.plugins.jvm.JvmTestSuite plugins { `java-library` + `jvm-test-suite` `maven-publish` signing id("com.gradleup.shadow") @@ -85,6 +87,67 @@ tasks.named("jar").configure { tasks.withType().configureEach { exclude("META-INF/jandex.idx") } +val standaloneJar = files(shadowJar.flatMap { it.archiveFile }).builtBy(shadowJar) +val projectDependencies = dependencies + +fun JvmTestSuite.configureStandaloneSmokeSuite( + pbProjectPath: String, + jacksonDependencies: JvmComponentDependencies.() -> Unit, +) { + useJUnitJupiter(libs.versions.junit.get()) + dependencies { + implementation(standaloneJar) + implementation(projectDependencies.project(mapOf("path" to pbProjectPath))) + implementation(libs.assertj.core) + jacksonDependencies() + } + targets.configureEach { testTask.configure { shouldRunAfter(tasks.named("test")) } } +} + +testing { + suites { + val standaloneJackson2Pb = + register("standaloneJackson2Pb") { + sources { java { setSrcDirs(listOf("src/standaloneJackson2Smoke/java")) } } + configureStandaloneSmokeSuite(":cel-generated-pb") { + implementation(platform(libs.jackson2.bom)) + implementation("com.fasterxml.jackson.core:jackson-databind") + } + } + val standaloneJackson2Pb3 = + register("standaloneJackson2Pb3") { + sources { java { setSrcDirs(listOf("src/standaloneJackson2Smoke/java")) } } + configureStandaloneSmokeSuite(":cel-generated-pb3") { + implementation(platform(libs.jackson2.bom)) + implementation("com.fasterxml.jackson.core:jackson-databind") + } + } + val standaloneJackson3Pb = + register("standaloneJackson3Pb") { + sources { java { setSrcDirs(listOf("src/standaloneJackson3Smoke/java")) } } + configureStandaloneSmokeSuite(":cel-generated-pb") { + implementation(platform(libs.jackson3.bom)) + implementation("tools.jackson.core:jackson-databind") + } + } + val standaloneJackson3Pb3 = + register("standaloneJackson3Pb3") { + sources { java { setSrcDirs(listOf("src/standaloneJackson3Smoke/java")) } } + configureStandaloneSmokeSuite(":cel-generated-pb3") { + implementation(platform(libs.jackson3.bom)) + implementation("tools.jackson.core:jackson-databind") + } + } + + tasks.named("check") { + dependsOn(standaloneJackson2Pb) + dependsOn(standaloneJackson2Pb3) + dependsOn(standaloneJackson3Pb) + dependsOn(standaloneJackson3Pb3) + } + } +} + // The following makes :cel-standalone consumable from an including build shadow { diff --git a/standalone/src/standaloneJackson2Smoke/java/org/projectnessie/cel/standalone/smoke/StandaloneJackson2SmokeTest.java b/standalone/src/standaloneJackson2Smoke/java/org/projectnessie/cel/standalone/smoke/StandaloneJackson2SmokeTest.java new file mode 100644 index 00000000..771e668e --- /dev/null +++ b/standalone/src/standaloneJackson2Smoke/java/org/projectnessie/cel/standalone/smoke/StandaloneJackson2SmokeTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2026 The Authors of CEL-Java + * + * Licensed 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.projectnessie.cel.standalone.smoke; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.projectnessie.cel.checker.Decls; +import org.projectnessie.cel.tools.Script; +import org.projectnessie.cel.tools.ScriptHost; +import org.projectnessie.cel.types.jackson.JacksonRegistry; + +class StandaloneJackson2SmokeTest { + @Test + void evaluatesScriptWithExplicitJackson2AndGeneratedProtobufDependency() throws Exception { + assertThat(Class.forName("com.fasterxml.jackson.databind.ObjectMapper")).isNotNull(); + assertThat(Class.forName("com.google.api.expr.v1alpha1.Decl")).isNotNull(); + + ScriptHost scriptHost = ScriptHost.newBuilder().registry(JacksonRegistry.newRegistry()).build(); + Script script = + scriptHost + .buildScript( + "input.name == 'reports' && input.labels.exists(label, label == 'finance')") + .withDeclarations(Decls.newVar("input", Decls.newObjectType(Input.class.getName()))) + .withTypes(Input.class) + .build(); + + assertThat(script.execute(Boolean.class, singletonMap("input", new Input("reports")))).isTrue(); + } + + public static final class Input { + private final String name; + private final List labels; + + public Input(String name) { + this.name = name; + this.labels = asList("finance", "quarterly"); + } + + public String getName() { + return name; + } + + public List getLabels() { + return labels; + } + } +} diff --git a/standalone/src/standaloneJackson3Smoke/java/org/projectnessie/cel/standalone/smoke/StandaloneJackson3SmokeTest.java b/standalone/src/standaloneJackson3Smoke/java/org/projectnessie/cel/standalone/smoke/StandaloneJackson3SmokeTest.java new file mode 100644 index 00000000..d48161fe --- /dev/null +++ b/standalone/src/standaloneJackson3Smoke/java/org/projectnessie/cel/standalone/smoke/StandaloneJackson3SmokeTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2026 The Authors of CEL-Java + * + * Licensed 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.projectnessie.cel.standalone.smoke; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.projectnessie.cel.checker.Decls; +import org.projectnessie.cel.tools.Script; +import org.projectnessie.cel.tools.ScriptHost; +import org.projectnessie.cel.types.jackson3.Jackson3Registry; + +class StandaloneJackson3SmokeTest { + @Test + void evaluatesScriptWithExplicitJackson3AndGeneratedProtobufDependency() throws Exception { + assertThat(Class.forName("tools.jackson.databind.ObjectMapper")).isNotNull(); + assertThat(Class.forName("com.google.api.expr.v1alpha1.Decl")).isNotNull(); + + ScriptHost scriptHost = + ScriptHost.newBuilder().registry(Jackson3Registry.newRegistry()).build(); + Script script = + scriptHost + .buildScript( + "input.name == 'reports' && input.labels.exists(label, label == 'finance')") + .withDeclarations(Decls.newVar("input", Decls.newObjectType(Input.class.getName()))) + .withTypes(Input.class) + .build(); + + assertThat(script.execute(Boolean.class, singletonMap("input", new Input("reports")))).isTrue(); + } + + public static final class Input { + private final String name; + private final List labels; + + public Input(String name) { + this.name = name; + this.labels = asList("finance", "quarterly"); + } + + public String getName() { + return name; + } + + public List getLabels() { + return labels; + } + } +}