Files
nexus/scripts/qa/task-board.k6.js
AzuTear f5552218bc
CI - Build & Test / Backend (.NET) (push) Successful in 42s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m46s
CI - Build & Test / Security Check (push) Successful in 3s
CI - Build & Test / Deploy Nexus (push) Successful in 56s
feat: ship agent-first mission control v0.2.57
2026-07-31 22:39:47 +02:00

174 lines
5.4 KiB
JavaScript

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)
}