@blamejs/core 0.5.12 → 0.5.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,7 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.5.x
10
10
 
11
+ - **0.5.12** (2026-04-30) — b.middleware.requestLog: HTTP access-log middleware
11
12
  - **0.5.11** (2026-04-30) — b.config: schema-validated environment configuration
12
13
  - **0.5.10** (2026-04-30) — b.middleware.sse: Server-Sent Events
13
14
  - **0.5.9** (2026-04-30) — b.csv: RFC 4180 parser + serializer
package/lib/testing.js CHANGED
@@ -547,6 +547,165 @@ function listenOnRandomPort(server, host) {
547
547
  // Standalone fake of @opentelemetry/api's minimal subset that
548
548
  // b.tracing actually consumes. No framework primitive owns this.
549
549
 
550
+ // ---- request(target) — supertest-style chainable HTTP test helper ----
551
+ //
552
+ // var res = await b.testing.request(router)
553
+ // .post("/api/widget")
554
+ // .set("X-Request-Id", "abc")
555
+ // .send({ name: "alpha" })
556
+ // .expect(200);
557
+ //
558
+ // res.status, res.headers, res.body (Buffer), res.json (parsed if applicable)
559
+ //
560
+ // Accepts:
561
+ // - a b.router instance (uses .handle(req, res))
562
+ // - a request listener function (req, res) => void
563
+ // - an http.Server / https.Server (used as-is)
564
+ //
565
+ // The framework spins up a real http.Server on an ephemeral port so
566
+ // the request flows through the full Node http stack — same code path
567
+ // production traffic takes. Server is closed automatically when the
568
+ // promise resolves or rejects.
569
+ function request(target) {
570
+ var http = require("node:http");
571
+ // Resolve target → request listener
572
+ var server;
573
+ var ownsServer = false;
574
+ if (target && typeof target.handle === "function") {
575
+ server = http.createServer(function (req, res) {
576
+ Promise.resolve(target.handle(req, res)).catch(function (err) {
577
+ if (!res.headersSent) res.writeHead(500, { "Content-Type": "text/plain" });
578
+ try { res.end((err && err.message) || "Internal Server Error"); } catch (_e) { /* response may already be ended */ }
579
+ });
580
+ });
581
+ ownsServer = true;
582
+ } else if (typeof target === "function") {
583
+ server = http.createServer(target);
584
+ ownsServer = true;
585
+ } else if (target && typeof target.listen === "function" && typeof target.close === "function") {
586
+ server = target;
587
+ ownsServer = false;
588
+ } else {
589
+ throw new Error("b.testing.request: target must be a b.router, a (req,res)=>void function, or an http.Server");
590
+ }
591
+
592
+ function _start(method, path) {
593
+ var headers = {};
594
+ var body = null;
595
+ var expectations = [];
596
+
597
+ var chain = {
598
+ set: function (k, v) {
599
+ if (typeof k === "object" && k !== null) Object.assign(headers, k);
600
+ else headers[k] = v;
601
+ return chain;
602
+ },
603
+ send: function (b) {
604
+ if (b == null) { body = null; return chain; }
605
+ if (Buffer.isBuffer(b) || typeof b === "string") {
606
+ body = b;
607
+ } else {
608
+ body = JSON.stringify(b);
609
+ if (!headers["Content-Type"] && !headers["content-type"]) {
610
+ headers["Content-Type"] = "application/json";
611
+ }
612
+ }
613
+ return chain;
614
+ },
615
+ expect: function (statusOrAssertion) {
616
+ expectations.push(statusOrAssertion);
617
+ return chain;
618
+ },
619
+ then: function (onFulfilled, onRejected) {
620
+ return _execute().then(onFulfilled, onRejected);
621
+ },
622
+ catch: function (onRejected) {
623
+ return _execute().catch(onRejected);
624
+ },
625
+ };
626
+
627
+ function _execute() {
628
+ return new Promise(function (resolve, reject) {
629
+ var listenP;
630
+ if (ownsServer) listenP = listenOnRandomPort(server);
631
+ else listenP = Promise.resolve(server.address() ? server.address().port : null);
632
+
633
+ listenP.then(function (port) {
634
+ var reqOpts = {
635
+ host: "127.0.0.1",
636
+ port: port,
637
+ method: method,
638
+ path: path,
639
+ headers: headers,
640
+ };
641
+ var nodeReq = http.request(reqOpts, function (nodeRes) {
642
+ var chunks = [];
643
+ nodeRes.on("data", function (c) { chunks.push(c); });
644
+ nodeRes.on("end", function () {
645
+ var bodyBuf = Buffer.concat(chunks);
646
+ var bodyText = bodyBuf.toString("utf8");
647
+ var json = null;
648
+ var ct = nodeRes.headers["content-type"] || "";
649
+ if (ct.indexOf("application/json") !== -1) {
650
+ try { json = JSON.parse(bodyText); } catch (_e) { /* leave json null */ }
651
+ }
652
+ var result = {
653
+ status: nodeRes.statusCode,
654
+ headers: nodeRes.headers,
655
+ body: bodyBuf,
656
+ text: bodyText,
657
+ json: json,
658
+ };
659
+ try {
660
+ for (var i = 0; i < expectations.length; i++) {
661
+ var exp = expectations[i];
662
+ if (typeof exp === "number") {
663
+ if (result.status !== exp) {
664
+ throw new Error("expect(" + exp + ") got status " + result.status +
665
+ " body: " + bodyText.slice(0, 200));
666
+ }
667
+ } else if (typeof exp === "function") {
668
+ exp(result);
669
+ } else {
670
+ throw new Error("expect: argument must be a number or function");
671
+ }
672
+ }
673
+ resolve(result);
674
+ } catch (e) {
675
+ reject(e);
676
+ } finally {
677
+ if (ownsServer) try { server.close(); } catch (_e) { /* server may already be closed */ }
678
+ }
679
+ });
680
+ nodeRes.on("error", function (err) {
681
+ if (ownsServer) try { server.close(); } catch (_e) { /* */ }
682
+ reject(err);
683
+ });
684
+ });
685
+ nodeReq.on("error", function (err) {
686
+ if (ownsServer) try { server.close(); } catch (_e) { /* */ }
687
+ reject(err);
688
+ });
689
+ if (body != null) nodeReq.write(body);
690
+ nodeReq.end();
691
+ }, reject);
692
+ });
693
+ }
694
+
695
+ return chain;
696
+ }
697
+
698
+ return {
699
+ get: function (p) { return _start("GET", p); },
700
+ post: function (p) { return _start("POST", p); },
701
+ put: function (p) { return _start("PUT", p); },
702
+ patch: function (p) { return _start("PATCH", p); },
703
+ delete: function (p) { return _start("DELETE", p); },
704
+ head: function (p) { return _start("HEAD", p); },
705
+ options:function (p) { return _start("OPTIONS",p); },
706
+ };
707
+ }
708
+
550
709
  function makeFakeOtelApi() {
551
710
  var spans = [];
552
711
  var activeSpan = null;
@@ -615,6 +774,8 @@ module.exports = {
615
774
  // Async test helpers
616
775
  runMiddleware: runMiddleware,
617
776
  waitFor: waitFor,
777
+ // Chainable HTTP request helper (supertest-style)
778
+ request: request,
618
779
  // Class + constants
619
780
  TestingError: TestingError,
620
781
  DEFAULTS: DEFAULTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.5.12",
3
+ "version": "0.5.13",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",