Skip to content

Commit 129c9aa

Browse files
committed
chore(tests): three-way Better Stack split — SDK / Platform API / applications
Tests are now routed to three separate Better Stack heartbeat monitors: - SDK (no monitors marker): token management, service wiring - Platform API (monitors_platform_api): auth, listing, connectivity - test_cli_health_json (system) - test_cli_application_list_verbose (application) - test_cli_run_list_limit_10 (application) - HE-TME / applications (monitors): application processing - existing he-tme and test-app tests (unchanged) Adds monitors_platform_api pytest marker alongside the existing monitors("platform-api") string for pytest -m expression filtering. Adds BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_* secrets (optional, heartbeat step skips gracefully when absent).
1 parent a00d925 commit 129c9aa

6 files changed

Lines changed: 94 additions & 8 deletions

File tree

.github/workflows/_scheduled-test-hourly.yml

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ on:
2525
BETTERSTACK_HEARTBEAT_URL_HE_TME_STAGING:
2626
# Optional until the HE-TME Better Stack monitor is created; the heartbeat step skips gracefully when absent.
2727
required: false
28+
BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_STAGING:
29+
# Optional until the Platform API Better Stack monitor is created; the heartbeat step skips gracefully when absent.
30+
required: false
2831
AIGNOSTICS_CLIENT_ID_DEVICE_PRODUCTION:
2932
required: true
3033
AIGNOSTICS_REFRESH_TOKEN_PRODUCTION:
@@ -36,6 +39,9 @@ on:
3639
BETTERSTACK_HEARTBEAT_URL_HE_TME_PRODUCTION:
3740
# Optional until the HE-TME Better Stack monitor is created; the heartbeat step skips gracefully when absent.
3841
required: false
42+
BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_PRODUCTION:
43+
# Optional until the Platform API Better Stack monitor is created; the heartbeat step skips gracefully when absent.
44+
required: false
3945
SENTRY_DSN:
4046
required: true
4147

@@ -96,10 +102,12 @@ jobs:
96102
echo "$GCP_CREDENTIALS" | base64 -d > credentials.json
97103
echo "GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/credentials.json" >> $GITHUB_ENV
98104
99-
# Tests are split into two runs so failures can be routed to the correct Better Stack monitor:
100-
# SDK-layer failures (auth, listing, connectivity) go to the SDK monitor; HE-TME application
101-
# failures go to the HE-TME monitor. Tests declare which system they monitor via
102-
# @pytest.mark.monitors("he-tme"); tests without that marker are SDK health checks.
105+
# Tests are split into three runs so failures can be routed to the correct Better Stack monitor:
106+
# - SDK-layer failures (token management, service wiring): no monitors marker → SDK monitor
107+
# - Platform API failures (auth, listing, connectivity): monitors_platform_api → Platform API monitor
108+
# - Application failures (HE-TME, test-app processing): monitors("he-tme"/"test-app") → HE-TME monitor
109+
# Tests declare which system they monitor via @pytest.mark.monitors("he-tme") /
110+
# @pytest.mark.monitors_platform_api; tests with neither marker are SDK health checks.
103111
- name: Test / scheduled / sdk
104112
id: test_sdk
105113
env:
@@ -110,10 +118,24 @@ jobs:
110118
# manually and send it to Better Stack regardless of outcome.
111119
set +e
112120
XDIST_WORKER_FACTOR=1 uv run --all-extras nox -s test -- \
113-
-m "(scheduled or scheduled_only) and not monitors and not stress_only" \
121+
-m "(scheduled or scheduled_only) and not monitors and not monitors_platform_api and not stress_only" \
114122
--junit-xml=reports/junit_sdk.xml
115123
echo "exit_code=$?" >> $GITHUB_OUTPUT
116124
125+
- name: Test / scheduled / platform-api
126+
id: test_platform_api
127+
env:
128+
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
129+
shell: bash
130+
run: |
131+
# set +e so a test failure does not abort the step — we capture the exit code
132+
# manually and send it to Better Stack regardless of outcome.
133+
set +e
134+
XDIST_WORKER_FACTOR=1 uv run --all-extras nox -s test -- \
135+
-m "(scheduled or scheduled_only) and monitors_platform_api and not stress_only" \
136+
--junit-xml=reports/junit_platform_api.xml
137+
echo "exit_code=$?" >> $GITHUB_OUTPUT
138+
117139
- name: Test / scheduled / he-tme
118140
id: test_he_tme
119141
env:
@@ -124,7 +146,7 @@ jobs:
124146
# manually and send it to Better Stack regardless of outcome.
125147
set +e
126148
XDIST_WORKER_FACTOR=1 uv run --all-extras nox -s test -- \
127-
-m "(scheduled or scheduled_only) and monitors and not stress_only" \
149+
-m "(scheduled or scheduled_only) and monitors and not monitors_platform_api and not stress_only" \
128150
--junit-xml=reports/junit_he_tme.xml
129151
echo "exit_code=$?" >> $GITHUB_OUTPUT
130152
@@ -135,14 +157,16 @@ jobs:
135157
# Default to 1 (failure) if a step never wrote its output — e.g. a setup step
136158
# errored before tests ran. Prevents sending a false "healthy" heartbeat.
137159
SDK_EXIT=${{ steps.test_sdk.outputs.exit_code || '1' }}
160+
PLATFORM_API_EXIT=${{ steps.test_platform_api.outputs.exit_code || '1' }}
138161
HE_TME_EXIT=${{ steps.test_he_tme.outputs.exit_code || '1' }}
139-
# Combined exit code: non-zero if either run failed
140-
if [ "$SDK_EXIT" != "0" ] || [ "$HE_TME_EXIT" != "0" ]; then
162+
# Combined exit code: non-zero if any run failed
163+
if [ "$SDK_EXIT" != "0" ] || [ "$PLATFORM_API_EXIT" != "0" ] || [ "$HE_TME_EXIT" != "0" ]; then
141164
COMBINED_EXIT=1
142165
else
143166
COMBINED_EXIT=0
144167
fi
145168
echo "sdk_exit=${SDK_EXIT}" >> $GITHUB_OUTPUT
169+
echo "platform_api_exit=${PLATFORM_API_EXIT}" >> $GITHUB_OUTPUT
146170
echo "he_tme_exit=${HE_TME_EXIT}" >> $GITHUB_OUTPUT
147171
echo "combined_exit=${COMBINED_EXIT}" >> $GITHUB_OUTPUT
148172
@@ -229,6 +253,56 @@ jobs:
229253
"${BETTERSTACK_HEARTBEAT_URL}/${SDK_EXIT}"
230254
echo "INFO: Sent SDK heartbeat to BetterStack with exit code '${SDK_EXIT}'"
231255
256+
- name: Heartbeat / BetterStack / Platform API
257+
if: always()
258+
shell: bash
259+
env:
260+
BETTERSTACK_HEARTBEAT_URL_PLATFORM_API: "${{ inputs.platform_environment == 'staging' && secrets.BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_STAGING || secrets.BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_PRODUCTION }}"
261+
PLATFORM_API_EXIT: ${{ steps.collect.outputs.platform_api_exit }}
262+
run: |
263+
if [ -z "$BETTERSTACK_HEARTBEAT_URL_PLATFORM_API" ]; then
264+
echo "INFO: No BetterStack Platform API heartbeat URL configured, skipped."
265+
exit 0
266+
fi
267+
BETTERSTACK_METADATA_PAYLOAD=$(jq -n \
268+
--arg github_workflow "${{ github.workflow }}" \
269+
--arg github_run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
270+
--arg github_run_id "${{ github.run_id }}" \
271+
--arg github_job "${{ github.job }}" \
272+
--arg github_sha "${{ github.sha }}" \
273+
--arg github_actor "${{ github.actor }}" \
274+
--arg github_repository "${{ github.repository }}" \
275+
--arg github_ref "${{ github.ref }}" \
276+
--arg job_status "${{ job.status }}" \
277+
--arg github_event_name "${{ github.event_name }}" \
278+
--arg timestamp "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
279+
'{
280+
github: {
281+
workflow: $github_workflow,
282+
run_url: $github_run_url,
283+
run_id: $github_run_id,
284+
job: $github_job,
285+
sha: $github_sha,
286+
actor: $github_actor,
287+
repository: $github_repository,
288+
ref: $github_ref,
289+
event_name: $github_event_name
290+
},
291+
job: {
292+
status: $job_status,
293+
},
294+
timestamp: $timestamp,
295+
}'
296+
)
297+
curl \
298+
--fail-with-body \
299+
--silent \
300+
--request POST \
301+
--header "Content-Type: application/json" \
302+
--data-binary "${BETTERSTACK_METADATA_PAYLOAD}" \
303+
"${BETTERSTACK_HEARTBEAT_URL_PLATFORM_API}/${PLATFORM_API_EXIT}"
304+
echo "INFO: Sent Platform API heartbeat to BetterStack with exit code '${PLATFORM_API_EXIT}'"
305+
232306
- name: Heartbeat / BetterStack / HE-TME
233307
if: always()
234308
shell: bash
@@ -286,6 +360,7 @@ jobs:
286360
name: test-results-scheduled
287361
path: |
288362
reports/junit_sdk.xml
363+
reports/junit_platform_api.xml
289364
reports/junit_he_tme.xml
290365
reports/coverage.xml
291366
reports/coverage.md

.github/workflows/scheduled-testing-production-hourly.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ jobs:
2525
GCP_CREDENTIALS_STAGING: ${{ secrets.GCP_CREDENTIALS_STAGING }}
2626
BETTERSTACK_HEARTBEAT_URL_STAGING: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_STAGING }}
2727
BETTERSTACK_HEARTBEAT_URL_HE_TME_STAGING: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_HE_TME_STAGING }}
28+
BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_STAGING: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_STAGING }}
2829
AIGNOSTICS_CLIENT_ID_DEVICE_PRODUCTION: ${{ secrets.AIGNOSTICS_CLIENT_ID_DEVICE_PRODUCTION }}
2930
AIGNOSTICS_REFRESH_TOKEN_PRODUCTION: ${{ secrets.AIGNOSTICS_REFRESH_TOKEN_PRODUCTION }}
3031
GCP_CREDENTIALS_PRODUCTION: ${{ secrets.GCP_CREDENTIALS_PRODUCTION }}
3132
BETTERSTACK_HEARTBEAT_URL_PRODUCTION: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_PRODUCTION }}
3233
BETTERSTACK_HEARTBEAT_URL_HE_TME_PRODUCTION: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_HE_TME_PRODUCTION }}
34+
BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_PRODUCTION: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_PRODUCTION }}
3335
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} # For metrics and heartbeat

.github/workflows/scheduled-testing-staging-hourly.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ jobs:
2525
GCP_CREDENTIALS_STAGING: ${{ secrets.GCP_CREDENTIALS_STAGING }}
2626
BETTERSTACK_HEARTBEAT_URL_STAGING: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_STAGING }}
2727
BETTERSTACK_HEARTBEAT_URL_HE_TME_STAGING: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_HE_TME_STAGING }}
28+
BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_STAGING: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_STAGING }}
2829
AIGNOSTICS_CLIENT_ID_DEVICE_PRODUCTION: ${{ secrets.AIGNOSTICS_CLIENT_ID_DEVICE_PRODUCTION }}
2930
AIGNOSTICS_REFRESH_TOKEN_PRODUCTION: ${{ secrets.AIGNOSTICS_REFRESH_TOKEN_PRODUCTION }}
3031
GCP_CREDENTIALS_PRODUCTION: ${{ secrets.GCP_CREDENTIALS_PRODUCTION }}
3132
BETTERSTACK_HEARTBEAT_URL_PRODUCTION: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_PRODUCTION }}
3233
BETTERSTACK_HEARTBEAT_URL_HE_TME_PRODUCTION: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_HE_TME_PRODUCTION }}
34+
BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_PRODUCTION: ${{ secrets.BETTERSTACK_HEARTBEAT_URL_PLATFORM_API_PRODUCTION }}
3335
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} # For metrics and heartbeat

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ markers = [
384384
"integration: Sociable integration tests - test interactions across architectural layers (e.g. CLI/GUI→Service, Service→Utils) or between modules (e.g. Application→Platform), using real SDK collaborators, real file I/O, real subprocesses, and real Docker containers. Integration test must be able to pass offline, i.e. mock external services (Aignostics Platform API, Auth0, S3/GCS buckets, IDC). The timeout should not be bigger than the default 10s, and must be <5 min.",
385385
"e2e: End-to-end tests - test complete workflows with real external network services (Aignostics Platform API, cloud storage, IDC, etc). If the test timeout is >= 5 min and < 60 min, additionally mark as `long_running`, if >= 60min mark as 'very_long_running'.",
386386
"monitors: Tag a scheduled test with the application it monitors, e.g. @pytest.mark.monitors('he-tme'). Tests without this marker are considered SDK-layer health checks. Used to route Better Stack heartbeats to the correct monitor.",
387+
"monitors_platform_api: Tag a scheduled test that monitors the Platform API layer (auth, listing, connectivity). Used to route Platform API Better Stack heartbeats separately from SDK and application monitors.",
387388
]
388389
md_report = true
389390
md_report_output = "reports/pytest.md"

tests/aignostics/application/cli_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ def test_cli_application_list_non_verbose(runner: CliRunner, record_property) ->
164164

165165
@pytest.mark.e2e
166166
@pytest.mark.scheduled
167+
@pytest.mark.monitors("platform-api")
168+
@pytest.mark.monitors_platform_api
167169
@pytest.mark.timeout(timeout=60)
168170
def test_cli_application_list_verbose(runner: CliRunner, record_property) -> None:
169171
"""Check application list command runs successfully."""
@@ -795,6 +797,8 @@ def test_cli_run_submit_and_describe_and_cancel_and_download_and_delete( # noqa
795797

796798
@pytest.mark.e2e
797799
@pytest.mark.scheduled
800+
@pytest.mark.monitors("platform-api")
801+
@pytest.mark.monitors_platform_api
798802
@pytest.mark.timeout(timeout=60)
799803
def test_cli_run_list_limit_10(runner: CliRunner, record_property) -> None:
800804
"""Check run list command runs successfully."""

tests/aignostics/system/cli_test.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ def test_cli_health_yaml_format(mock_service: MagicMock, runner: CliRunner, reco
4343

4444
@pytest.mark.e2e
4545
@pytest.mark.scheduled
46+
@pytest.mark.monitors("platform-api")
47+
@pytest.mark.monitors_platform_api
4648
@pytest.mark.timeout(timeout=60)
4749
def test_cli_health_json(runner: CliRunner) -> None:
4850
"""Check health CLI returns valid JSON with a valid status value."""

0 commit comments

Comments
 (0)