Skip to content

Commit 7271ae2

Browse files
devin-ai-integration[bot]tanderson-ldsemgrep-code-launchdarkly[bot]
authored
ci: add nightly contract tests with long-running tests and Slack notification (#136)
**Requirements** - [x] I have followed the repository's [pull request submission guidelines](../blob/main/CONTRIBUTING.md#submitting-pull-requests) **Related issues** N/A **Describe the solution you've provided** Adds a new GitHub Actions workflow that runs the Java server SDK contract tests nightly (4am UTC) with the `-enable-long-running-tests` flag enabled. If the tests fail, a Slack notification is sent to alert the team. Key details: - **Schedule**: Runs daily at 4am UTC via cron, with `workflow_dispatch` for manual triggering (supports custom branch and Slack test notification inputs) - **Test execution**: Reuses the existing `ci` and `contract-tests` composite actions; the `contract-tests` action now accepts a `test_harness_params` input to pass `-enable-long-running-tests` - **Slack notification**: Uses `slackapi/slack-github-action` (pinned to v2.1.1 SHA) with an incoming webhook (`SLACK_WEBHOOK_URL` secret) to post to Slack on failure, including a link to the failed run - **Test Slack job**: A separate `test-slack-notification` job can be triggered manually via `workflow_dispatch` to verify the Slack integration is working - **Contract test service updates**: Added `server-side-polling` capability and polling parameter support to the test service entity - **FDv2 suppressions**: Three `streaming/fdv2` fallback/recovery tests are suppressed pending spec alignment between Go and Java implementations **Items for reviewer attention:** 1. **`SLACK_WEBHOOK_URL` secret** — must be configured in the repo settings with a webhook routed to `#sdks-java`. The channel is determined by the webhook configuration, not the workflow. 2. **No `timeout-minutes`** — long-running tests could potentially hang indefinitely. Consider adding a timeout. 3. **Notification scope** — the Slack job triggers on `failure` only, not `cancelled` or `timed_out`. Decide if those states should also notify. 4. **Contract test service changes** — verify the new polling support in `SdkClientEntity.java` is correct, particularly the conditional logic for `params.polling != null && params.dataSystem == null` and the base URI endpoint wiring. **Describe alternatives you've considered** - Could use `rtCamp/action-slack-notify` (used by `ldcli`) instead of `slackapi/slack-github-action`, but the official Slack action is more widely used across LD repos (`terraform-provider-launchdarkly`, `streamer`, `ld-docs-private`). **Updates since initial revision** - Extended `contract-tests` composite action with `test_harness_params` input instead of splitting out individual make targets (addresses original item #4) - Added `workflow_dispatch` inputs for branch selection and Slack notification testing - Added `test-slack-notification` job for verifying Slack integration - Added `server-side-polling` capability and polling params to the contract test service - Added 3 FDv2 streaming fallback/recovery test suppressions (pending spec alignment) - Pinned `slackapi/slack-github-action` to v2.1.1 SHA (`91efab103c0de0a537f72a35f6b8cda0ee76bf0a`) **Additional context** [Link to Devin run](https://app.devin.ai/sessions/9fb4e81a67fd47098803d5d7dabd008b) Requested by: tanderson@launchdarkly.com <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Primarily CI/workflow changes, but it also adjusts contract-test service configuration logic for polling which could alter how tests initialize and exercise the SDK. > > **Overview** > Adds a new `Nightly Contract Tests` GitHub Actions workflow that runs server SDK contract tests nightly (and on manual dispatch), with an optional branch override and an optional Slack test-notification path. > > Extends the `contract-tests` composite action to accept `test_harness_params` and pass them through to `make`, enabling the nightly workflow to run the harness with `-enable-long-running-tests`, and posts a Slack alert via an incoming webhook when the nightly job fails. > > Updates the contract test service to advertise `server-side-polling`, accept polling config (including a `filter`), and wire polling endpoints/data source when streaming isn’t used; also adds FDv2 suppressions for several streaming fallback/recovery tests. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 69b9f64. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tanderson@launchdarkly.com <tanderson@launchdarkly.com> Co-authored-by: Todd Anderson <127344469+tanderson-ld@users.noreply.github.com> Co-authored-by: semgrep-code-launchdarkly[bot] <167133144+semgrep-code-launchdarkly[bot]@users.noreply.github.com>
1 parent 2b68876 commit 7271ae2

File tree

6 files changed

+127
-3
lines changed

6 files changed

+127
-3
lines changed
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
name: Contract Tests
2-
description: Runs Contract Tests
2+
description: Runs Contract Tests (builds, starts service, runs SDK contract test harness)
33
inputs:
44
workspace_path:
5-
description: 'Path to the package.'
5+
description: 'Path to the package (e.g. lib/sdk/server).'
66
required: true
77
token:
88
description: 'Github token, used for contract tests'
99
required: false
1010
default: ''
11+
test_harness_params:
12+
description: 'Optional extra parameters for the SDK test harness (e.g. -enable-long-running-tests). Passed as TEST_HARNESS_PARAMS to make.'
13+
required: false
14+
default: ''
1115

1216
runs:
1317
using: composite
1418
steps:
1519
- name: Run contract tests
1620
shell: bash
17-
run: make contract-tests -C ${{ inputs.workspace_path }}
21+
run: make contract-tests -C ${{ inputs.workspace_path }} TEST_HARNESS_PARAMS="${{ inputs.test_harness_params }}"
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: Nightly Contract Tests
2+
3+
on:
4+
schedule:
5+
- cron: "0 4 * * *" # every day at 4am UTC
6+
workflow_dispatch:
7+
inputs:
8+
branch:
9+
description: 'Branch to run contract tests on'
10+
required: false
11+
default: ''
12+
test_slack_notification:
13+
description: 'Also send a test Slack notification (to verify Slack integration)'
14+
required: false
15+
type: boolean
16+
default: false
17+
18+
jobs:
19+
nightly-contract-tests:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v3
23+
with:
24+
ref: ${{ inputs.branch || github.ref }}
25+
26+
- name: Shared CI Steps
27+
uses: ./.github/actions/ci
28+
with:
29+
workspace_path: 'lib/sdk/server'
30+
java_version: 8
31+
32+
- name: Contract Tests (with long-running tests)
33+
uses: ./.github/actions/contract-tests
34+
with:
35+
workspace_path: 'lib/sdk/server'
36+
token: ${{ secrets.GITHUB_TOKEN }}
37+
test_harness_params: '-enable-long-running-tests'
38+
39+
notify-slack-on-failure:
40+
runs-on: ubuntu-latest
41+
if: ${{ always() && needs.nightly-contract-tests.result == 'failure' }}
42+
needs:
43+
- nightly-contract-tests
44+
steps:
45+
- name: Send Slack notification
46+
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
47+
with:
48+
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
49+
webhook-type: incoming-webhook
50+
payload: |
51+
{
52+
"blocks": [
53+
{
54+
"type": "section",
55+
"text": {
56+
"type": "mrkdwn",
57+
"text": ":warning: *Nightly Contract Tests Failed* :warning:\nThe nightly contract tests (with long-running tests enabled) failed on `${{ github.ref_name }}`."
58+
},
59+
"accessory": {
60+
"type": "button",
61+
"text": {
62+
"type": "plain_text",
63+
"text": "View failed run"
64+
},
65+
"url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
66+
}
67+
}
68+
]
69+
}
70+
71+
test-slack-notification:
72+
runs-on: ubuntu-latest
73+
if: ${{ inputs.test_slack_notification == true || inputs.test_slack_notification == 'true' }}
74+
steps:
75+
- name: Send test Slack notification
76+
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
77+
with:
78+
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
79+
webhook-type: incoming-webhook
80+
payload: |
81+
{
82+
"blocks": [
83+
{
84+
"type": "section",
85+
"text": {
86+
"type": "mrkdwn",
87+
"text": ":white_check_mark: *Nightly Contract Tests – Slack test*\nThis is a test notification to verify Slack integration is working. Triggered manually from the Nightly Contract Tests workflow."
88+
},
89+
"accessory": {
90+
"type": "button",
91+
"text": {
92+
"type": "plain_text",
93+
"text": "View workflow run"
94+
},
95+
"url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
96+
}
97+
}
98+
]
99+
}

lib/sdk/server/contract-tests/service/src/main/java/sdktest/Representations.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public static class SdkConfigParams {
2727
Long startWaitTimeMs;
2828
boolean initCanFail;
2929
SdkConfigStreamParams streaming;
30+
SdkConfigPollingParams polling;
3031
SdkConfigEventParams events;
3132
SdkConfigBigSegmentsParams bigSegments;
3233
SdkConfigTagParams tags;
@@ -170,6 +171,7 @@ public static class SdkConfigSynchronizerParams {
170171
public static class SdkConfigPollingParams {
171172
URI baseUri;
172173
Long pollIntervalMs;
174+
String filter;
173175
}
174176

175177
public static class SdkConfigStreamingParams {

lib/sdk/server/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,16 @@ private LDConfig buildSdkConfig(SdkConfigParams params, String tag) {
401401
}
402402
dataSource.payloadFilter(params.streaming.filter);
403403
builder.dataSource(dataSource);
404+
} else if (params.polling != null && params.dataSystem == null) {
405+
// v2 harness: top-level polling only (no dataSystem); use FDv1 polling data source
406+
PollingDataSourceBuilder pollingDataSource = Components.pollingDataSource();
407+
if (params.polling.pollIntervalMs != null) {
408+
pollingDataSource.pollInterval(Duration.ofMillis(params.polling.pollIntervalMs));
409+
}
410+
if (params.polling.filter != null && !params.polling.filter.isEmpty()) {
411+
pollingDataSource.payloadFilter(params.polling.filter);
412+
}
413+
builder.dataSource(pollingDataSource);
404414
}
405415

406416
if (params.events == null) {
@@ -463,6 +473,11 @@ private LDConfig buildSdkConfig(SdkConfigParams params, String tag) {
463473
endpoints.events(params.serviceEndpoints.events);
464474
}
465475
}
476+
477+
if (params.polling != null && params.polling.baseUri != null && !params.polling.baseUri.toString().trim().isEmpty()) {
478+
endpoints.polling(params.polling.baseUri);
479+
}
480+
466481
builder.serviceEndpoints(endpoints);
467482

468483
if (params.hooks != null && params.hooks.hooks != null) {

lib/sdk/server/contract-tests/service/src/main/java/sdktest/TestService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class TestService {
4141
"service-endpoints",
4242
"strongly-typed",
4343
"tags",
44+
"server-side-polling"
4445
};
4546

4647
static final Gson gson = new GsonBuilder().serializeNulls().create();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
streaming/fdv2/recoverable fallback to secondary synchronizer
2+
streaming/fdv2/recoverable fallback with recovery
3+
streaming/fdv2/permanent fallback with recovery

0 commit comments

Comments
 (0)