75 lines
2.7 KiB
JavaScript
75 lines
2.7 KiB
JavaScript
import http from "k6/http";
|
|||
|
|
import crypto from "k6/crypto";
|
||
|
|
import exec from "k6/execution";
|
||
|
|
import { check } from "k6";
|
||
|
|
|
||
|
|
const baseURL = __ENV.CALLBACK_URL || "http://host.docker.internal:58081";
|
||
|
|
const secret = __ENV.PROVIDER_CALLBACK_SECRET;
|
||
|
|
const fixtureText = __ENV.PROVIDER_FIXTURES || `${__ENV.CONVERSATION_ID || ""}|${__ENV.PROVIDER_TASK_REF || ""}`;
|
||
|
|
const fixtures = fixtureText.split(",").filter(Boolean).map((item) => {
|
||
|
|
const [conversationID, taskRef] = item.split("|");
|
||
|
|
return { conversationID, taskRef };
|
||
|
|
});
|
||
|
|
const smoke = (__ENV.PROFILE || "acceptance").toLowerCase() === "smoke";
|
||
|
|
const targetRate = Number(__ENV.RATE || (smoke ? 25 : 500));
|
||
|
|
const duration = __ENV.DURATION || (smoke ? "10s" : "10m");
|
||
|
|
const duplicateWindow = Number(__ENV.DUPLICATE_WINDOW || (smoke ? 100 : 300000));
|
||
|
|
const occurredAt = __ENV.OCCURRED_AT || new Date().toISOString();
|
||
|
|
|
||
|
|
if (!secret || !fixtures.length || fixtures.some((item) => !item.conversationID || !item.taskRef)) {
|
||
|
|
throw new Error("PROVIDER_CALLBACK_SECRET and valid PROVIDER_FIXTURES are required");
|
||
|
|
}
|
||
|
|
|
||
|
|
export const options = {
|
||
|
|
scenarios: {
|
||
|
|
provider_notifications: {
|
||
|
|
executor: "constant-arrival-rate",
|
||
|
|
rate: targetRate,
|
||
|
|
timeUnit: "1s",
|
||
|
|
duration,
|
||
|
|
preAllocatedVUs: smoke ? 50 : 600,
|
||
|
|
maxVUs: smoke ? 150 : 1500,
|
||
|
|
gracefulStop: "30s",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
thresholds: {
|
||
|
|
http_req_failed: [smoke ? "rate<0.01" : "rate<0.001"],
|
||
|
|
http_req_duration: ["p(95)<200", "p(99)<1000"],
|
||
|
|
checks: ["rate>0.999"],
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
function base64URL(value) {
|
||
|
|
return value.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function () {
|
||
|
|
const eventNumber = exec.scenario.iterationInTest % duplicateWindow;
|
||
|
|
const fixture = fixtures[eventNumber % fixtures.length];
|
||
|
|
const body = JSON.stringify({
|
||
|
|
provider: "mock",
|
||
|
|
event_id: `load-started-${eventNumber}`,
|
||
|
|
task_ref: fixture.taskRef,
|
||
|
|
conversation_id: fixture.conversationID,
|
||
|
|
event_type: "voice_chat.started",
|
||
|
|
status: "ACTIVE",
|
||
|
|
occurred_at: occurredAt,
|
||
|
|
});
|
||
|
|
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
||
|
|
const digest = crypto.sha256(body, "hex");
|
||
|
|
const signature = base64URL(crypto.hmac("sha256", secret, `${timestamp}\n${digest}`, "base64"));
|
||
|
|
const response = http.post(`${baseURL}/callbacks/v1/vertc/voice-chat`, body, {
|
||
|
|
headers: {
|
||
|
|
"Content-Type": "application/json",
|
||
|
|
"X-Provider-Timestamp": timestamp,
|
||
|
|
"X-Provider-Signature": signature,
|
||
|
|
},
|
||
|
|
tags: { operation: "provider_callback" },
|
||
|
|
timeout: "3s",
|
||
|
|
});
|
||
|
|
check(response, {
|
||
|
|
"callback acknowledged": (item) => item.status === 200,
|
||
|
|
"callback response is correlated": (item) => item.status === 200 && item.body.includes('"request_id"'),
|
||
|
|
});
|
||
|
|
}
|