From 3365a3481d77e9cc6c6b261a188a6e16612d7a18 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Feb 2026 18:01:30 +0000 Subject: [PATCH 1/5] ci: add nightly contract tests with long-running tests and Slack notification Co-Authored-By: tanderson@launchdarkly.com --- .github/workflows/nightly-contract-tests.yml | 62 ++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/nightly-contract-tests.yml diff --git a/.github/workflows/nightly-contract-tests.yml b/.github/workflows/nightly-contract-tests.yml new file mode 100644 index 00000000..c76cbc20 --- /dev/null +++ b/.github/workflows/nightly-contract-tests.yml @@ -0,0 +1,62 @@ +name: Nightly Contract Tests + +on: + schedule: + - cron: "0 4 * * *" # every day at 4am UTC + workflow_dispatch: + +jobs: + nightly-contract-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Shared CI Steps + uses: ./.github/actions/ci + with: + workspace_path: 'lib/sdk/server' + java_version: 8 + + - name: Build Contract Tests + shell: bash + run: make build-contract-tests -C lib/sdk/server + + - name: Start Contract Test Service + shell: bash + run: make start-contract-test-service-bg -C lib/sdk/server + + - name: Run Contract Tests with Long-Running Tests + shell: bash + run: make run-contract-tests -C lib/sdk/server TEST_HARNESS_PARAMS="-enable-long-running-tests" + + notify-slack-on-failure: + runs-on: ubuntu-latest + if: ${{ always() && needs.nightly-contract-tests.result == 'failure' }} + needs: + - nightly-contract-tests + steps: + - name: Send Slack notification + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ":warning: *Nightly Contract Tests Failed* :warning:\nThe nightly contract tests (with long-running tests enabled) failed on `main`." + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "View failed run" + }, + "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + } + ] + } From 7e2ded74826a8edcffe4a97d161bd3108dd87a66 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Wed, 11 Feb 2026 15:54:29 -0500 Subject: [PATCH 2/5] tuning workflow and adding some test exceptions for the moment --- .github/actions/contract-tests/action.yml | 10 ++- .github/workflows/nightly-contract-tests.yml | 61 +++++++++++++++---- .../src/main/java/sdktest/TestService.java | 1 + .../contract-tests/test-suppressions-fdv2.txt | 3 + 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/.github/actions/contract-tests/action.yml b/.github/actions/contract-tests/action.yml index fc3a9829..00fce260 100644 --- a/.github/actions/contract-tests/action.yml +++ b/.github/actions/contract-tests/action.yml @@ -1,17 +1,21 @@ name: Contract Tests -description: Runs Contract Tests +description: Runs Contract Tests (builds, starts service, runs SDK contract test harness) inputs: workspace_path: - description: 'Path to the package.' + description: 'Path to the package (e.g. lib/sdk/server).' required: true token: description: 'Github token, used for contract tests' required: false default: '' + test_harness_params: + description: 'Optional extra parameters for the SDK test harness (e.g. -enable-long-running-tests). Passed as TEST_HARNESS_PARAMS to make.' + required: false + default: '' runs: using: composite steps: - name: Run contract tests shell: bash - run: make contract-tests -C ${{ inputs.workspace_path }} + run: make contract-tests -C ${{ inputs.workspace_path }} TEST_HARNESS_PARAMS="${{ inputs.test_harness_params }}" diff --git a/.github/workflows/nightly-contract-tests.yml b/.github/workflows/nightly-contract-tests.yml index c76cbc20..3fa7b811 100644 --- a/.github/workflows/nightly-contract-tests.yml +++ b/.github/workflows/nightly-contract-tests.yml @@ -4,12 +4,24 @@ on: schedule: - cron: "0 4 * * *" # every day at 4am UTC workflow_dispatch: + inputs: + branch: + description: 'Branch to run contract tests on' + required: false + default: '' + test_slack_notification: + description: 'Also send a test Slack notification (to verify Slack integration)' + required: false + type: boolean + default: false jobs: nightly-contract-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + ref: ${{ inputs.branch || github.ref }} - name: Shared CI Steps uses: ./.github/actions/ci @@ -17,17 +29,12 @@ jobs: workspace_path: 'lib/sdk/server' java_version: 8 - - name: Build Contract Tests - shell: bash - run: make build-contract-tests -C lib/sdk/server - - - name: Start Contract Test Service - shell: bash - run: make start-contract-test-service-bg -C lib/sdk/server - - - name: Run Contract Tests with Long-Running Tests - shell: bash - run: make run-contract-tests -C lib/sdk/server TEST_HARNESS_PARAMS="-enable-long-running-tests" + - name: Contract Tests (with long-running tests) + uses: ./.github/actions/contract-tests + with: + workspace_path: 'lib/sdk/server' + token: ${{ secrets.GITHUB_TOKEN }} + test_harness_params: '-enable-long-running-tests' notify-slack-on-failure: runs-on: ubuntu-latest @@ -47,7 +54,7 @@ jobs: "type": "section", "text": { "type": "mrkdwn", - "text": ":warning: *Nightly Contract Tests Failed* :warning:\nThe nightly contract tests (with long-running tests enabled) failed on `main`." + "text": ":warning: *Nightly Contract Tests Failed* :warning:\nThe nightly contract tests (with long-running tests enabled) failed on `${{ github.ref_name }}`." }, "accessory": { "type": "button", @@ -60,3 +67,33 @@ jobs: } ] } + + test-slack-notification: + runs-on: ubuntu-latest + if: ${{ inputs.test_slack_notification == true || inputs.test_slack_notification == 'true' }} + steps: + - name: Send test Slack notification + uses: slackapi/slack-github-action@v2.1.0 + with: + webhook: ${{ secrets.SLACK_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "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." + }, + "accessory": { + "type": "button", + "text": { + "type": "plain_text", + "text": "View workflow run" + }, + "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + } + ] + } diff --git a/lib/sdk/server/contract-tests/service/src/main/java/sdktest/TestService.java b/lib/sdk/server/contract-tests/service/src/main/java/sdktest/TestService.java index 84bbb3e5..9f16f387 100644 --- a/lib/sdk/server/contract-tests/service/src/main/java/sdktest/TestService.java +++ b/lib/sdk/server/contract-tests/service/src/main/java/sdktest/TestService.java @@ -41,6 +41,7 @@ public class TestService { "service-endpoints", "strongly-typed", "tags", + "server-side-polling" }; static final Gson gson = new GsonBuilder().serializeNulls().create(); diff --git a/lib/sdk/server/contract-tests/test-suppressions-fdv2.txt b/lib/sdk/server/contract-tests/test-suppressions-fdv2.txt index e69de29b..9765a098 100644 --- a/lib/sdk/server/contract-tests/test-suppressions-fdv2.txt +++ b/lib/sdk/server/contract-tests/test-suppressions-fdv2.txt @@ -0,0 +1,3 @@ +streaming/fdv2/recoverable fallback to secondary synchronizer +streaming/fdv2/recoverable fallback with recovery +streaming/fdv2/permanent fallback with recovery From af6eac9125583fb387963d717a4d2f330be12563 Mon Sep 17 00:00:00 2001 From: Todd Anderson Date: Thu, 12 Feb 2026 13:52:08 -0500 Subject: [PATCH 3/5] tweaking test service to support polling --- .../src/main/java/sdktest/Representations.java | 2 ++ .../src/main/java/sdktest/SdkClientEntity.java | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/lib/sdk/server/contract-tests/service/src/main/java/sdktest/Representations.java b/lib/sdk/server/contract-tests/service/src/main/java/sdktest/Representations.java index 99890e78..10bb9741 100644 --- a/lib/sdk/server/contract-tests/service/src/main/java/sdktest/Representations.java +++ b/lib/sdk/server/contract-tests/service/src/main/java/sdktest/Representations.java @@ -27,6 +27,7 @@ public static class SdkConfigParams { Long startWaitTimeMs; boolean initCanFail; SdkConfigStreamParams streaming; + SdkConfigPollingParams polling; SdkConfigEventParams events; SdkConfigBigSegmentsParams bigSegments; SdkConfigTagParams tags; @@ -170,6 +171,7 @@ public static class SdkConfigSynchronizerParams { public static class SdkConfigPollingParams { URI baseUri; Long pollIntervalMs; + String filter; } public static class SdkConfigStreamingParams { diff --git a/lib/sdk/server/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java b/lib/sdk/server/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java index ced6e3fb..70280ed2 100644 --- a/lib/sdk/server/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java +++ b/lib/sdk/server/contract-tests/service/src/main/java/sdktest/SdkClientEntity.java @@ -401,6 +401,16 @@ private LDConfig buildSdkConfig(SdkConfigParams params, String tag) { } dataSource.payloadFilter(params.streaming.filter); builder.dataSource(dataSource); + } else if (params.polling != null && params.dataSystem == null) { + // v2 harness: top-level polling only (no dataSystem); use FDv1 polling data source + PollingDataSourceBuilder pollingDataSource = Components.pollingDataSource(); + if (params.polling.pollIntervalMs != null) { + pollingDataSource.pollInterval(Duration.ofMillis(params.polling.pollIntervalMs)); + } + if (params.polling.filter != null && !params.polling.filter.isEmpty()) { + pollingDataSource.payloadFilter(params.polling.filter); + } + builder.dataSource(pollingDataSource); } if (params.events == null) { @@ -463,6 +473,11 @@ private LDConfig buildSdkConfig(SdkConfigParams params, String tag) { endpoints.events(params.serviceEndpoints.events); } } + + if (params.polling != null && params.polling.baseUri != null && !params.polling.baseUri.toString().trim().isEmpty()) { + endpoints.polling(params.polling.baseUri); + } + builder.serviceEndpoints(endpoints); if (params.hooks != null && params.hooks.hooks != null) { From 4b0ac23312b64563a98dc02e1f1c01a2d55649cd Mon Sep 17 00:00:00 2001 From: Todd Anderson <127344469+tanderson-ld@users.noreply.github.com> Date: Thu, 12 Feb 2026 13:53:01 -0500 Subject: [PATCH 4/5] Update .github/workflows/nightly-contract-tests.yml Co-authored-by: semgrep-code-launchdarkly[bot] <167133144+semgrep-code-launchdarkly[bot]@users.noreply.github.com> --- .github/workflows/nightly-contract-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly-contract-tests.yml b/.github/workflows/nightly-contract-tests.yml index 3fa7b811..28270fb1 100644 --- a/.github/workflows/nightly-contract-tests.yml +++ b/.github/workflows/nightly-contract-tests.yml @@ -43,7 +43,7 @@ jobs: - nightly-contract-tests steps: - name: Send Slack notification - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@8eec7b3b943e15be5302493bb90a15bafa6973c6 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} webhook-type: incoming-webhook From efe624536f4b9cea5e3772761950b60245855ea0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:57:05 +0000 Subject: [PATCH 5/5] ci: pin slackapi/slack-github-action to v2.1.1 SHA Co-Authored-By: tanderson@launchdarkly.com --- .github/workflows/nightly-contract-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly-contract-tests.yml b/.github/workflows/nightly-contract-tests.yml index 28270fb1..aac2977a 100644 --- a/.github/workflows/nightly-contract-tests.yml +++ b/.github/workflows/nightly-contract-tests.yml @@ -43,7 +43,7 @@ jobs: - nightly-contract-tests steps: - name: Send Slack notification - uses: slackapi/slack-github-action@8eec7b3b943e15be5302493bb90a15bafa6973c6 # v2.1.0 + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} webhook-type: incoming-webhook @@ -73,7 +73,7 @@ jobs: if: ${{ inputs.test_slack_notification == true || inputs.test_slack_notification == 'true' }} steps: - name: Send test Slack notification - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} webhook-type: incoming-webhook