Skip to content

Commit 1dedc0c

Browse files
pmbrullclaude
andauthored
Add k8s-operator unit tests to PR CI (#27387)
* Add k8s-operator unit tests to PR CI pipeline The k8s operator tests only ran during manual release builds. Add a path-filtered job so they run on PRs touching openmetadata-k8s-operator/**, following the same Detect Changes pattern used by the service unit tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove -DfailIfNoTests=false — we want to catch missing tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix k8s-operator tests: add surefire includes and remove unnecessary stub Parent POM surefire includes only match org.openmetadata.service.*, so operator tests under org.openmetadata.operator.* were silently skipped. Override with **/*Test.java in the operator pom.xml. Also remove unused KubernetesClient mock stub from CronOMJobReconcilerTest.setUp — no test reaches the code path that calls context.getClient(), causing UnnecessaryStubbingException. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Rename k8s-operator to k8s_operator in workflow outputs Hyphens in output names are parsed as subtraction in GitHub Actions expressions dot notation, so the job condition would never trigger. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix filesystem paths — underscore rename only applies to output keys The replace_all incorrectly changed directory names from openmetadata-k8s-operator to openmetadata-k8s_operator. Only the GitHub Actions output key needs the underscore; all file paths must use the actual hyphenated directory name. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Drop -am flag from k8s-operator test command openmetadata-service is a provided-scope dependency, so -am tries to compile it including shaded ES/OS jars that aren't available in a clean CI environment. The operator module compiles fine on its own. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Fix invalid YAML in conf/openmetadata.yaml The CSP policy line has unescaped colons inside the value which the YAML parser interprets as mapping indicators. Use a folded block scalar (>-) so the value is parsed as a plain string. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Build k8s-operator deps before running tests The operator depends on openmetadata-service (provided scope) which won't be in the Maven cache on a cold CI runner. Build with -am -DskipTests first, then run operator tests separately — same pattern as docker-k8s-operator.yml. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Reintroduce lenient client mock to prevent flaky NPE The reconcile flow is time-dependent — tests using "0 * * * *" can reach context.getClient() near the top of the hour. Stub the full client.resources().inNamespace().resource().create() chain as lenient so early-return tests aren't penalized but happy-path tests won't NPE. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert conf/openmetadata.yaml — fix belongs in a separate PR Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f4c9398 commit 1dedc0c

3 files changed

Lines changed: 74 additions & 2 deletions

File tree

.github/workflows/openmetadata-service-unit-tests.yml

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ jobs:
3939
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
4040
outputs:
4141
java: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.filter.outputs.java }}
42+
k8s_operator: ${{ github.event_name == 'workflow_dispatch' && 'true' || steps.filter.outputs.k8s_operator }}
4243
steps:
4344
- name: Checkout
4445
uses: actions/checkout@v4
@@ -59,6 +60,8 @@ jobs:
5960
- 'pom.xml'
6061
- 'Makefile'
6162
- 'bootstrap/**'
63+
k8s_operator:
64+
- 'openmetadata-k8s-operator/**'
6265
6366
openmetadata-service-unit-tests:
6467
runs-on: ubuntu-latest
@@ -121,13 +124,62 @@ jobs:
121124
report_paths: "openmetadata-service/target/surefire-reports/TEST-*.xml"
122125
check_name: "Test Report (${{ matrix.database }})"
123126

127+
k8s_operator-unit-tests:
128+
runs-on: ubuntu-latest
129+
timeout-minutes: 30
130+
needs: changes
131+
if: ${{ needs.changes.outputs.k8s_operator == 'true' }}
132+
steps:
133+
- name: Checkout
134+
uses: actions/checkout@v4
135+
with:
136+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
137+
138+
- name: Cache Maven dependencies
139+
uses: actions/cache@v4
140+
with:
141+
path: ~/.m2
142+
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
143+
restore-keys: |
144+
${{ runner.os }}-maven-
145+
146+
- name: Set up JDK 21
147+
uses: actions/setup-java@v4
148+
with:
149+
java-version: "21"
150+
distribution: "temurin"
151+
152+
- name: Build k8s-operator dependencies
153+
run: |
154+
mvn -B clean install -pl openmetadata-k8s-operator -am -DskipTests
155+
156+
- name: Run k8s-operator unit tests
157+
run: |
158+
mvn -B test -pl openmetadata-k8s-operator
159+
160+
- name: Upload surefire reports
161+
if: ${{ failure() && hashFiles('openmetadata-k8s-operator/target/surefire-reports/TEST-*.xml') != '' }}
162+
uses: actions/upload-artifact@v4
163+
with:
164+
name: k8s-operator-surefire-reports
165+
path: openmetadata-k8s-operator/target/surefire-reports/
166+
167+
- name: Publish Test Report
168+
if: ${{ always() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) && hashFiles('openmetadata-k8s-operator/target/surefire-reports/TEST-*.xml') != '' }}
169+
uses: scacap/action-surefire-report@v1
170+
with:
171+
github_token: ${{ secrets.GITHUB_TOKEN }}
172+
fail_on_test_failures: true
173+
report_paths: "openmetadata-k8s-operator/target/surefire-reports/TEST-*.xml"
174+
check_name: "K8s Operator Test Report"
175+
124176
# Single required-check gate for branch protection.
125177
# Skipped (= "Success") when all test jobs pass or are legitimately skipped.
126178
# Runs and exits 1 only when a test job fails or is cancelled.
127179
# Set "OpenMetadata Service Unit Tests / openmetadata-service-unit-tests-status" as the sole required check for this workflow.
128180
openmetadata-service-unit-tests-status:
129181
name: openmetadata-service-unit-tests-status
130-
needs: [changes, openmetadata-service-unit-tests]
182+
needs: [changes, openmetadata-service-unit-tests, k8s_operator-unit-tests]
131183
if: ${{ failure() || cancelled() }}
132184
runs-on: ubuntu-latest
133185
steps:

openmetadata-k8s-operator/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@
187187
<groupId>org.apache.maven.plugins</groupId>
188188
<artifactId>maven-surefire-plugin</artifactId>
189189
<version>3.2.5</version>
190+
<configuration>
191+
<includes>
192+
<include>**/*Test.java</include>
193+
</includes>
194+
</configuration>
190195
</plugin>
191196

192197
<!-- Failsafe plugin for integration tests -->

openmetadata-k8s-operator/src/test/java/org/openmetadata/operator/unit/CronOMJobReconcilerTest.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818

1919
import io.fabric8.kubernetes.api.model.ObjectMeta;
2020
import io.fabric8.kubernetes.client.KubernetesClient;
21+
import io.fabric8.kubernetes.client.dsl.MixedOperation;
22+
import io.fabric8.kubernetes.client.dsl.NamespaceableResource;
23+
import io.fabric8.kubernetes.client.dsl.Resource;
2124
import io.javaoperatorsdk.operator.api.reconciler.Context;
2225
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
2326
import java.time.Instant;
@@ -38,15 +41,27 @@ class CronOMJobReconcilerTest {
3841

3942
@Mock private Context<CronOMJobResource> context;
4043
@Mock private KubernetesClient client;
44+
@Mock private MixedOperation mixedOp;
45+
@Mock private NamespaceableResource namespaceable;
46+
@Mock private Resource resource;
4147

4248
private CronOMJobReconciler reconciler;
4349
private CronOMJobResource cronOMJob;
4450

51+
@SuppressWarnings("unchecked")
4552
@BeforeEach
4653
void setUp() {
4754
reconciler = new CronOMJobReconciler();
4855
cronOMJob = createTestCronOMJob("test-cronjob", "0 * * * *");
49-
when(context.getClient()).thenReturn(client);
56+
57+
// Stub the client chain so tests that reach the happy path
58+
// (time-dependent) don't NPE. Lenient because most tests
59+
// return early before calling context.getClient().
60+
lenient().when(context.getClient()).thenReturn(client);
61+
lenient().when(client.resources(any(Class.class))).thenReturn(mixedOp);
62+
lenient().when(mixedOp.inNamespace(any())).thenReturn(mixedOp);
63+
lenient().when(mixedOp.resource(any())).thenReturn(namespaceable);
64+
lenient().when(namespaceable.create()).thenReturn(null);
5065
}
5166

5267
@Test

0 commit comments

Comments
 (0)