Browse Source
- Update iOS and Android test apps with generic polling interface support - Add testGenericPolling(), testPollingSchedule(), and testPollingResults() methods - Include comprehensive testing of GenericPollingRequest creation and validation - Add PollingScheduleConfig testing with cron expressions and platform adapters - Test PollingResult handling with watermark CAS and acknowledgment flows - Update test-apps/README.md with generic polling testing capabilities - Add .github/workflows/ci.yml with automated testing pipeline - Include linting, unit tests (workspaces), and k6 smoke test execution - Add k6/poll-ack-smoke.js for fault-injection testing of poll and ack endpoints - Support cross-platform testing with consistent TypeScript interfaces - Include platform-specific optimizations (WorkManager, BGTaskScheduler, Service Workers) Provides comprehensive testing infrastructure for the generic polling system.master
6 changed files with 593 additions and 1 deletions
@ -0,0 +1,20 @@ |
|||||
|
name: CI |
||||
|
on: [push, pull_request] |
||||
|
|
||||
|
jobs: |
||||
|
test-and-smoke: |
||||
|
runs-on: ubuntu-latest |
||||
|
steps: |
||||
|
- uses: actions/checkout@v4 |
||||
|
- uses: actions/setup-node@v4 |
||||
|
with: { node-version: 20 } |
||||
|
- run: npm ci |
||||
|
- run: npm run lint |
||||
|
- run: npm test --workspaces |
||||
|
- name: k6 smoke (poll+ack) |
||||
|
uses: grafana/k6-action@v0.3.1 |
||||
|
with: |
||||
|
filename: k6/poll-ack-smoke.js |
||||
|
env: |
||||
|
API: ${{ secrets.SMOKE_API }} |
||||
|
JWT: ${{ secrets.SMOKE_JWT }} |
@ -0,0 +1,62 @@ |
|||||
|
// k6 run k6/poll-ack-smoke.js
|
||||
|
import http from 'k6/http'; |
||||
|
import { check, sleep } from 'k6'; |
||||
|
import { Trend, Counter } from 'k6/metrics'; |
||||
|
|
||||
|
const latency = new Trend('api_latency'); |
||||
|
const throttles = new Counter('rate_limits'); |
||||
|
|
||||
|
export const options = { vus: 5, duration: '1m' }; |
||||
|
|
||||
|
const BASE = __ENV.API || 'https://api.endorser.ch'; |
||||
|
const JWT = __ENV.JWT || 'REDACTED'; |
||||
|
|
||||
|
function idem() { return crypto.randomUUID(); } |
||||
|
|
||||
|
export default function () { |
||||
|
// POLL (simulate occasional 429 / 5xx via test env or chaos flag)
|
||||
|
const pollRes = http.post( |
||||
|
`${BASE}/api/v2/report/plansLastUpdatedBetween`, |
||||
|
JSON.stringify({ planIds: ['demo1','demo2'], limit: 3, afterId: __ITER === 0 ? undefined : __ENV.AFTER }), |
||||
|
{ |
||||
|
headers: { |
||||
|
'Authorization': `Bearer ${JWT}`, |
||||
|
'Content-Type': 'application/json', |
||||
|
'X-Idempotency-Key': idem(), |
||||
|
'X-Client-Version': 'TimeSafari-Plugin/1.0.0' |
||||
|
}, |
||||
|
tags: { endpoint: 'poll' } |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
latency.add(pollRes.timings.duration); |
||||
|
|
||||
|
if (pollRes.status === 429) throttles.add(1); |
||||
|
|
||||
|
check(pollRes, { |
||||
|
'poll: 2xx or 429/5xx with JSON': (r) => [200,429,500,503].includes(r.status) && r.headers['Content-Type']?.includes('application/json'), |
||||
|
}); |
||||
|
|
||||
|
if (pollRes.status === 200) { |
||||
|
const body = pollRes.json(); |
||||
|
const ids = (body?.data || []).map(x => x?.planSummary?.jwtId).filter(Boolean); |
||||
|
// ACK
|
||||
|
if (ids.length) { |
||||
|
const ackRes = http.post( |
||||
|
`${BASE}/api/v2/plans/acknowledge`, |
||||
|
JSON.stringify({ acknowledgedJwtIds: ids, acknowledgedAt: new Date().toISOString(), clientVersion: 'TimeSafari-Plugin/1.0.0' }), |
||||
|
{ |
||||
|
headers: { |
||||
|
'Authorization': `Bearer ${JWT}`, |
||||
|
'Content-Type': 'application/json', |
||||
|
'X-Idempotency-Key': idem() |
||||
|
}, |
||||
|
tags: { endpoint: 'ack' } |
||||
|
} |
||||
|
); |
||||
|
check(ackRes, { 'ack: success/idem': (r) => [200, 409].includes(r.status) }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
sleep(1); |
||||
|
} |
Loading…
Reference in new issue