Files
OmniRoute/tests/unit/chatcore-headers.test.ts
Diego Rodrigues de Sa e Souza 5e9c11006b feat(compression): per-request x-omniroute-compression header (Phase 3) (#4645)
* docs(compression): Phase 3 per-request header design spec

Approved brainstorming output for the x-omniroute-compression header:
header-first precedence, name-first combo matching (Decision A), explicit
value bypasses auto-trigger (Decision B), DerivedPlan.source, and the
X-OmniRoute-Compression response header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(compression): Phase 3 per-request header implementation plan

4-task TDD plan (resolver header-first + source, parser, chatCore wiring +
response header, docs/file-size) with full code and exact commands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(compression): header-first resolver + plan source (Phase 3 core)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(compression): resolveCompressionHeader parser (Phase 3)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(compression): wire x-omniroute-compression header + response header (Phase 3)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(compression): extract plan-resolution leaf (planResolution.ts) under size cap (Phase 3)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(compression): document x-omniroute-compression header (Phase 3)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(compression): harden named-combo map + trim engine: header id (Phase 3 review)

Addresses gemini-code-assist review on #4645:
- Extract buildNamedComboLookup (pure) so a blank/whitespace/null combo name
  contributes only its id key (no '' key, no throw that disables all combos).
- Trim the engine:<id> header value so 'engine: rtk' resolves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <diego.souza@cdwasolutions.com.br>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <souzamiriamrodrigues790@gmail.com>
2026-06-22 17:30:45 -03:00

62 lines
3.1 KiB
TypeScript

import test, { describe, it } from "node:test";
import assert from "node:assert/strict";
import { getHeaderValueCaseInsensitive, resolveCompressionHeader } from "../../open-sse/handlers/chatCore/headers.ts";
test("getHeaderValueCaseInsensitive reads a Headers instance via .get()", () => {
const h = new Headers({ "Content-Type": "text/event-stream" });
// Headers.get is itself case-insensitive, so a lowercase lookup hits the value.
assert.equal(getHeaderValueCaseInsensitive(h, "content-type"), "text/event-stream");
assert.equal(getHeaderValueCaseInsensitive(h, "Content-Type"), "text/event-stream");
// Missing header on a Headers instance returns null (Headers.get contract).
assert.equal(getHeaderValueCaseInsensitive(h, "x-missing"), null);
});
test("getHeaderValueCaseInsensitive matches plain-object keys case-insensitively", () => {
const obj = { Accept: "text/event-stream", "X-Foo": "bar" };
assert.equal(getHeaderValueCaseInsensitive(obj, "accept"), "text/event-stream");
assert.equal(getHeaderValueCaseInsensitive(obj, "ACCEPT"), "text/event-stream");
assert.equal(getHeaderValueCaseInsensitive(obj, "x-foo"), "bar");
});
test("getHeaderValueCaseInsensitive trims plain-object string values", () => {
assert.equal(getHeaderValueCaseInsensitive({ Accept: " v " }, "accept"), "v");
});
test("getHeaderValueCaseInsensitive ignores blank and non-string plain-object values", () => {
// whitespace-only value: value.trim() is falsy -> skipped -> null
assert.equal(getHeaderValueCaseInsensitive({ Accept: " " }, "accept"), null);
// empty string -> skipped -> null
assert.equal(getHeaderValueCaseInsensitive({ Accept: "" }, "accept"), null);
// non-string values are not strings -> skipped -> null
assert.equal(getHeaderValueCaseInsensitive({ "Content-Length": 42 }, "content-length"), null);
assert.equal(getHeaderValueCaseInsensitive({ Flag: true }, "flag"), null);
});
test("getHeaderValueCaseInsensitive returns null for missing key on plain object", () => {
assert.equal(getHeaderValueCaseInsensitive({ Accept: "x" }, "missing"), null);
});
test("getHeaderValueCaseInsensitive returns null for null/undefined/non-object inputs", () => {
assert.equal(getHeaderValueCaseInsensitive(null, "accept"), null);
assert.equal(getHeaderValueCaseInsensitive(undefined, "accept"), null);
// a non-object (typeof !== "object") short-circuits to null
assert.equal(
getHeaderValueCaseInsensitive("text/event-stream" as unknown as Record<string, unknown>, "accept"),
null
);
});
describe("resolveCompressionHeader", () => {
it("reads the raw value case-insensitively and trims it", () => {
assert.equal(resolveCompressionHeader({ "x-omniroute-compression": " engine:rtk " }), "engine:rtk");
assert.equal(resolveCompressionHeader(new Headers({ "X-OmniRoute-Compression": "off" })), "off");
});
it("returns null when absent or blank", () => {
assert.equal(resolveCompressionHeader({}), null);
assert.equal(resolveCompressionHeader({ "x-omniroute-compression": " " }), null);
assert.equal(resolveCompressionHeader(null), null);
});
});