feat: ship agent-first mission control v0.2.57
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
import http from 'k6/http'
|
||||
import { check, sleep } from 'k6'
|
||||
import { Rate, Trend } from 'k6/metrics'
|
||||
|
||||
const BASE_URL = (__ENV.NEXUS_BASE_URL || 'http://127.0.0.1:18880').replace(/\/+$/, '')
|
||||
const BEARER_TOKEN = (__ENV.NEXUS_BEARER_TOKEN || '').trim()
|
||||
const API_KEY = (__ENV.NEXUS_API_KEY || '').trim()
|
||||
const IS_SMOKE = (__ENV.NEXUS_K6_SMOKE || '').trim() === '1'
|
||||
const TARGET = new URL(BASE_URL)
|
||||
const IS_LOOPBACK = ['127.0.0.1', 'localhost', '::1', '[::1]'].includes(TARGET.hostname)
|
||||
const INSECURE_TLS = (__ENV.NEXUS_INSECURE_TLS || '').trim() === '1'
|
||||
const ALLOW_REMOTE = (__ENV.NEXUS_K6_ALLOW_REMOTE || '').trim() === '1'
|
||||
const REQUIRE_DONE_CURSOR = (__ENV.NEXUS_K6_REQUIRE_DONE_CURSOR || '').trim()
|
||||
? (__ENV.NEXUS_K6_REQUIRE_DONE_CURSOR || '').trim() === '1'
|
||||
: !IS_SMOKE
|
||||
|
||||
if (!BEARER_TOKEN && !API_KEY) {
|
||||
throw new Error(
|
||||
'Set NEXUS_BEARER_TOKEN or NEXUS_API_KEY. Credentials are read from the environment and are never printed.',
|
||||
)
|
||||
}
|
||||
if (!['http:', 'https:'].includes(TARGET.protocol)) {
|
||||
throw new Error('NEXUS_BASE_URL must use HTTP or HTTPS.')
|
||||
}
|
||||
if (
|
||||
TARGET.username
|
||||
|| TARGET.password
|
||||
|| TARGET.search
|
||||
|| TARGET.hash
|
||||
|| !['', '/'].includes(TARGET.pathname)
|
||||
) {
|
||||
throw new Error(
|
||||
'NEXUS_BASE_URL must be an origin without embedded credentials, path, query, or fragment.',
|
||||
)
|
||||
}
|
||||
if (!IS_LOOPBACK && !ALLOW_REMOTE) {
|
||||
throw new Error(
|
||||
'Remote k6 targets are blocked. Set NEXUS_K6_ALLOW_REMOTE=1 only for an isolated non-production environment.',
|
||||
)
|
||||
}
|
||||
if (!IS_LOOPBACK && TARGET.protocol !== 'https:') {
|
||||
throw new Error(
|
||||
'Remote k6 targets must use HTTPS because the test sends an authenticated credential.',
|
||||
)
|
||||
}
|
||||
if (!IS_LOOPBACK && INSECURE_TLS) {
|
||||
throw new Error('NEXUS_INSECURE_TLS is allowed only for loopback diagnostics.')
|
||||
}
|
||||
|
||||
const boardRequestDuration = new Trend('nexus_board_request_duration', true)
|
||||
const boardInitialDuration = new Trend('nexus_board_initial_duration', true)
|
||||
const boardDoneDuration = new Trend('nexus_board_done_duration', true)
|
||||
const boardFailures = new Rate('nexus_board_failures')
|
||||
const boardContractFailures = new Rate('nexus_board_contract_failures')
|
||||
const boardDatasetFailures = new Rate('nexus_board_dataset_failures')
|
||||
|
||||
export const options = {
|
||||
scenarios: {
|
||||
task_board: IS_SMOKE
|
||||
? {
|
||||
executor: 'constant-vus',
|
||||
vus: 1,
|
||||
duration: '10s',
|
||||
gracefulStop: '5s',
|
||||
}
|
||||
: {
|
||||
executor: 'constant-vus',
|
||||
vus: 10,
|
||||
duration: '2m',
|
||||
gracefulStop: '15s',
|
||||
},
|
||||
},
|
||||
thresholds: IS_SMOKE
|
||||
? {
|
||||
nexus_board_failures: ['rate<0.05'],
|
||||
nexus_board_contract_failures: ['rate<0.05'],
|
||||
nexus_board_dataset_failures: ['rate<0.05'],
|
||||
}
|
||||
: {
|
||||
'http_req_duration{endpoint:task-board,page:initial}': ['p(95)<500'],
|
||||
'http_req_duration{endpoint:task-board,page:done}': ['p(95)<300'],
|
||||
'http_req_failed{endpoint:task-board,page:initial}': ['rate<0.01'],
|
||||
'http_req_failed{endpoint:task-board,page:done}': ['rate<0.01'],
|
||||
nexus_board_initial_duration: ['p(95)<500'],
|
||||
nexus_board_done_duration: ['p(95)<300'],
|
||||
nexus_board_failures: ['rate<0.01'],
|
||||
nexus_board_contract_failures: ['rate<0.01'],
|
||||
nexus_board_dataset_failures: ['rate<0.01'],
|
||||
checks: ['rate>0.99'],
|
||||
},
|
||||
userAgent: 'nexus-task-board-k6/1.0',
|
||||
insecureSkipTLSVerify: INSECURE_TLS,
|
||||
noConnectionReuse: false,
|
||||
}
|
||||
|
||||
function authHeaders() {
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
}
|
||||
|
||||
if (BEARER_TOKEN) headers.Authorization = `Bearer ${BEARER_TOKEN}`
|
||||
if (API_KEY) headers['X-Nexus-Api-Key'] = API_KEY
|
||||
return headers
|
||||
}
|
||||
|
||||
function readBoardPage(url, page) {
|
||||
const response = http.get(url, {
|
||||
headers: authHeaders(),
|
||||
tags: {
|
||||
endpoint: 'task-board',
|
||||
page,
|
||||
},
|
||||
timeout: '10s',
|
||||
})
|
||||
|
||||
boardRequestDuration.add(response.timings.duration, { page })
|
||||
if (page === 'initial') boardInitialDuration.add(response.timings.duration)
|
||||
if (page === 'done') boardDoneDuration.add(response.timings.duration)
|
||||
const httpOk = response.status === 200
|
||||
boardFailures.add(!httpOk, { page })
|
||||
|
||||
let body = null
|
||||
if (httpOk) {
|
||||
try {
|
||||
body = response.json()
|
||||
} catch {
|
||||
body = null
|
||||
}
|
||||
}
|
||||
|
||||
const contractOk = Boolean(
|
||||
body
|
||||
&& typeof body.revision === 'string'
|
||||
&& Array.isArray(body.offen)
|
||||
&& Array.isArray(body.inProgress)
|
||||
&& Array.isArray(body.review)
|
||||
&& Array.isArray(body.blocked)
|
||||
&& Array.isArray(body.done)
|
||||
&& typeof body.hasMoreDone === 'boolean',
|
||||
)
|
||||
boardContractFailures.add(!contractOk, { page })
|
||||
|
||||
check(response, {
|
||||
[`${page}: returns 200`]: () => httpOk,
|
||||
[`${page}: preserves the board contract`]: () => contractOk,
|
||||
[`${page}: emits Server-Timing`]: res => Boolean(res.headers['Server-Timing']),
|
||||
})
|
||||
|
||||
return contractOk ? body : null
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const firstPage = readBoardPage(
|
||||
`${BASE_URL}/api/v1/tasks/board?doneLimit=50`,
|
||||
'initial',
|
||||
)
|
||||
|
||||
const hasDoneCursor = Boolean(firstPage?.hasMoreDone && firstPage.nextDoneCursor)
|
||||
const datasetOk = !REQUIRE_DONE_CURSOR || hasDoneCursor
|
||||
boardDatasetFailures.add(!datasetOk)
|
||||
check(firstPage, {
|
||||
'acceptance dataset exposes a Done continuation cursor': () => datasetOk,
|
||||
})
|
||||
|
||||
if (hasDoneCursor) {
|
||||
readBoardPage(
|
||||
`${BASE_URL}/api/v1/tasks/board?doneLimit=50&doneCursor=${encodeURIComponent(firstPage.nextDoneCursor)}`,
|
||||
'done',
|
||||
)
|
||||
}
|
||||
|
||||
sleep(0.75 + Math.random() * 0.5)
|
||||
}
|
||||
Reference in New Issue
Block a user