uce/site/tests/websockets.ws.uce

127 lines
4.9 KiB
Plaintext

#include "testlib.h"
DValue websocket_suite_event(Request& context, String type, String nonce)
{
DValue event;
event["type"] = type;
event["nonce"] = nonce;
event["connection_id"] = ws_connection_id();
event["scope"] = first(context.params["DOCUMENT_URI"], ws_scope());
event["online"] = (f64)ws_connection_count();
event["opcode"] = (f64)ws_opcode();
event["binary"] = ws_is_binary() ? "true" : "false";
return(event);
}
RENDER(Request& context)
{
String ws_url = context.params["DOCUMENT_URI"];
site_tests_page_start("WebSockets", "Browser-driven handshake and broadcast checks for ws_* helpers on the current .ws.uce endpoint.");
?><div class="tests-note">This page self-tests in the browser: it opens a socket to itself, waits for a targeted hello acknowledgment, then verifies a broadcast acknowledgment from the same endpoint.</div>
<section class="tests-section">
<div class="tests-cases">
<section class="tests-case"><div class="tests-case-header"><strong>hello acknowledgment</strong><span id="hello-status" class="status-badge status-warn">WAIT</span></div><pre id="hello-detail">connecting...</pre></section>
<section class="tests-case"><div class="tests-case-header"><strong>broadcast acknowledgment</strong><span id="broadcast-status" class="status-badge status-warn">WAIT</span></div><pre id="broadcast-detail">waiting...</pre></section>
<section class="tests-case"><div class="tests-case-header"><strong>connection state</strong><span id="socket-status" class="status-badge status-warn">WAIT</span></div><pre id="socket-detail">opening websocket...</pre></section>
</div>
</section>
<script>
const wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}<?= ws_url ?>`;
const nonce = `site-tests-${Date.now()}`;
const helloStatus = document.getElementById('hello-status');
const helloDetail = document.getElementById('hello-detail');
const broadcastStatus = document.getElementById('broadcast-status');
const broadcastDetail = document.getElementById('broadcast-detail');
const socketStatus = document.getElementById('socket-status');
const socketDetail = document.getElementById('socket-detail');
let sawHello = false;
let sawBroadcast = false;
function mark(el, detailEl, ok, text) {
el.textContent = ok ? 'PASS' : 'FAIL';
el.className = `status-badge ${ok ? 'status-ok' : 'status-error'}`;
detailEl.textContent = text;
}
const ws = new WebSocket(wsUrl);
socketDetail.textContent = `connecting to ${wsUrl}`;
ws.addEventListener('open', () => {
socketStatus.textContent = 'OPEN';
socketStatus.className = 'status-badge status-ok';
socketDetail.textContent = `connected to ${wsUrl}`;
ws.send(JSON.stringify({ type: 'hello', nonce }));
});
ws.addEventListener('message', (event) => {
let payload;
try {
payload = JSON.parse(event.data);
} catch (error) {
mark(socketStatus, socketDetail, false, String(error));
return;
}
if (payload.nonce !== nonce) return;
if (payload.type === 'hello-ack') {
sawHello = true;
mark(helloStatus, helloDetail, true, JSON.stringify(payload, null, 2));
ws.send(JSON.stringify({ type: 'broadcast', nonce }));
return;
}
if (payload.type === 'broadcast-ack') {
sawBroadcast = true;
mark(broadcastStatus, broadcastDetail, true, JSON.stringify(payload, null, 2));
return;
}
mark(socketStatus, socketDetail, false, `unexpected payload: ${JSON.stringify(payload)}`);
});
ws.addEventListener('error', () => {
mark(socketStatus, socketDetail, false, 'websocket error event fired');
});
ws.addEventListener('close', () => {
if (!sawHello) mark(helloStatus, helloDetail, false, 'socket closed before hello acknowledgment');
if (!sawBroadcast) mark(broadcastStatus, broadcastDetail, false, 'socket closed before broadcast acknowledgment');
if (!sawHello || !sawBroadcast) mark(socketStatus, socketDetail, false, 'socket closed before suite completed');
});
window.setTimeout(() => {
if (!sawHello) mark(helloStatus, helloDetail, false, 'timeout waiting for hello acknowledgment');
if (!sawBroadcast) mark(broadcastStatus, broadcastDetail, false, 'timeout waiting for broadcast acknowledgment');
}, 5000);
</script><?
site_tests_page_end();
}
WS(Request& context)
{
if(ws_is_binary())
{
ws_send_to(ws_connection_id(), json_encode(websocket_suite_event(context, "binary-not-supported", "")));
return;
}
DValue payload = json_decode(ws_message());
String type = trim(payload["type"].to_string());
String nonce = trim(payload["nonce"].to_string());
if(type == "hello")
{
ws_send_to(ws_connection_id(), json_encode(websocket_suite_event(context, "hello-ack", nonce)));
return;
}
if(type == "broadcast")
{
ws_send(json_encode(websocket_suite_event(context, "broadcast-ack", nonce)));
return;
}
ws_send_to(ws_connection_id(), json_encode(websocket_suite_event(context, "unknown", nonce)));
}