BRAIDGROUP
RESEARCH & DEV
56. Framework Docs

WebSockets and Testing

Junction includes native WebSocket support with upgrade handshake, message framing, ping/pong keepalive, and structured close codes. The testing module provides mock request builders, response inspectors, and assertion helpers for integration testing without a running server.

WebSocket Support

WebSockets are handled via an upgrade mechanism. Register a WebSocket handler for a path, and the framework performs the HTTP upgrade handshake (Sec-WebSocket-Accept, status 101) automatically.

Registering a WebSocket Handler

import lib.frameworks.junction.websocket;

junction.register_websocket(app, "/ws/chat", fn(ws_msg: object) {
    let msg_type = ws_msg["type"];
    let conn = ws_msg["conn"];

    if (msg_type == "message") {
        let data = ws_msg["data"];
        websocket.send_message(conn, "Echo: " + data);
    } else if (msg_type == "ping") {
        websocket.send_pong(conn);
    } else if (msg_type == "close") {
        print("Connection closed: " + conn["id"]);
    }
});

Manual Upgrade

For full control, use websocket.upgrade(handler, req) or websocket.upgrade_with_config(handler, req, config) inside a route handler:

junction.route(app, "GET", "/ws", fn(req: Request) -> Response {
    if (!websocket.is_websocket_request(req)) {
        return junction.error("WS_REQUIRED", "WebSocket upgrade required", 426);
    }
    return websocket.upgrade(fn(ws_msg: object) { /* handler */ }, req);
});

WebSocket Configuration

let ws_config = {
    "max_message_size": 2097152,  // 2 MB
    "ping_interval_ms": 15000,    // 15s between pings
    "close_timeout_ms": 3000      // 3s close handshake timeout
};
let res = websocket.upgrade_with_config(handler, req, ws_config);

WebSocket API Reference

FunctionDescription
websocket.upgrade(handler, req)Perform upgrade handshake, returns 101 response
websocket.upgrade_with_config(handler, req, config)Upgrade with custom config
websocket.send_message(conn, data)Send a text/binary message
websocket.send_json(conn, data)Serialize and send JSON message
websocket.send_ping(conn)Send a ping frame
websocket.send_pong(conn)Send a pong frame
websocket.close(conn, code, reason)Initiate close handshake
websocket.close_normal(conn)Close with code 1000
websocket.close_going_away(conn)Close with code 1001
websocket.close_protocol_error(conn)Close with code 1002
websocket.close_unsupported(conn)Close with code 1003
websocket.close_policy_violation(conn)Close with code 1008
websocket.close_too_large(conn)Close with code 1009
websocket.close_internal_error(conn)Close with code 1011
websocket.is_websocket_request(req)Check for valid upgrade headers
websocket.mask_data(data, key)XOR mask frame payload
websocket.build_frame(opcode, data)Construct a raw WebSocket frame

Chat Example

let clients = [];

junction.register_websocket(app, "/chat", fn(msg: object) {
    let conn = msg["conn"];
    if (msg["type"] == "message") {
        let data = msg["data"];
        let i = 0;
        while (i < std.collections.length(clients)) {
            if (clients[i]["id"] != conn["id"]) {
                websocket.send_message(clients[i], data);
            }
            i = i + 1;
        }
    } else if (msg["type"] == "close") {
        let i = 0;
        while (i < std.collections.length(clients)) {
            if (clients[i]["id"] == conn["id"]) {
                clients.splice(i, 1);
                break;
            }
            i = i + 1;
        }
    }
});

Testing Framework

The testing module provides mock request builders, assertion helpers, and a test runner for integration-testing Junction apps without starting an HTTP server. Use create_test_app() and test_request() to exercise the full middleware pipeline and router.

Test Lifecycle

import lib.frameworks.junction.testing;

// 1. Create a test app
let app = junction.create_test_app();

// 2. Register routes and middleware
junction.route(app, "GET", "/ping", fn(req: Request) -&gt; Response {
    return junction.json({"pong": true}, 200);
});

// 3. Build a mock request and dispatch it
let req = testing.mock_get("/ping");
let res = junction.test_request(app, req);

// 4. Assert on the response
testing.assert_equal(result, res.status, 200, "status should be 200");

Mock Request Builders

// GET request
let req = testing.mock_get("/users");

// POST with JSON body
let req = testing.mock_json_post("/users", {"name": "Alice"});

// POST with raw body and content type
let req = testing.mock_post("/upload", "raw data", "text/plain");

// PUT with JSON body
let req = testing.mock_put("/users/1", json_body, "application/json");

// PATCH with JSON body
let req = testing.mock_patch("/users/1", json_body, "application/json");

// DELETE request
let req = testing.mock_delete("/users/1");

Mock Request Modifiers

testing.mock_with_auth(req, "api-key-123");
testing.mock_with_bearer(req, "bearer-token-xyz");
testing.mock_with_header(req, "X-Custom", "value");
testing.mock_with_query(req, "page", "2");
testing.mock_with_cookie(req, "session_id", "sess_abc");

Assertions

let result = testing.new_test_result("my test");

testing.assert_equal(result, actual, expected, "description");
testing.assert_not_equal(result, actual, expected, "description");
testing.assert_true(result, value, "description");
testing.assert_false(result, value, "description");
testing.assert_null(result, value, "description");
testing.assert_not_null(result, value, "description");

// Response-specific assertions
testing.assert_status(result, response, 200, "status ok");
testing.assert_json_contains(result, response, "user_id", "has user_id");
testing.assert_json_value(result, response, "role", "admin", "role is admin");
testing.assert_header(result, response, "Content-Type", "application/json", "content type");
testing.assert_header_exists(result, response, "X-Request-Id", "has request id");
testing.assert_content_type(result, response, "application/json", "content type");
testing.assert_body_contains(result, response, "success", "body contains success");
testing.assert_redirect(result, response, "/login", "redirects to login");

Running Tests

fn test_health(t: object) {
    let app = junction.create_test_app();
    my_routes.setup(app);
    let req = testing.mock_get("/health");
    let res = junction.test_request(app, req);
    testing.assert_status(t, res, 200, "health endpoint ok");
}

fn test_create_user(t: object) {
    let app = junction.create_test_app();
    my_routes.setup(app);
    let req = testing.mock_json_post("/users", {"name": "Alice"});
    let res = junction.test_request(app, req);
    testing.assert_status(t, res, 201, "user created");
    testing.assert_json_value(t, res, "name", "Alice", "name matches");
}

fn main() {
    let results = testing.run_tests({
        "health": test_health,
        "create_user": test_create_user
    });
    // Output: 2 tests, 2 passed, 0 failed
}

Test Results Structure

Each TestResult contains the test name, an array of assertions, pass/fail status, failed/total counts, and duration in milliseconds. The run_tests() function prints a summary to stdout and returns a summary object with total, passed, and failed counts.

CLI Test Runner

Run tests from the command line:

braidc junction test