Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions apps/cli/src/domain/body.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import type { EventStreamPayload } from "@barestash/shared";
import { describe, expect, it } from "vitest";

import {
isBodyMetadata,
transformBody,
transformStreamBody,
transformStreamPayload,
} from "./body.js";

function encodeBase64(value: string | Uint8Array): string {
return Buffer.from(value).toString("base64");
}

function streamPayload(
contentType: string,
data: string,
headers: EventStreamPayload["request"]["headers"] = {},
): EventStreamPayload {
return {
id: "evt_body" as EventStreamPayload["id"],
endpoint_id: "ep_body" as EventStreamPayload["endpoint_id"],
received_at: "2026-07-12T12:00:00.000Z",
request: {
method: "POST",
path: "/webhook",
query: {},
headers: {
"content-type": contentType,
...headers,
},
body_size: Buffer.from(data, "base64").byteLength,
body_sha256: "test-sha256",
},
body: {
encoding: "base64",
data,
},
};
}

function expectBodyMetadata(
value: unknown,
contentType: string,
size: number,
): void {
expect(isBodyMetadata(value)).toBe(true);

if (!isBodyMetadata(value)) {
throw new Error("Expected synthetic body metadata.");
}

expect(value.content_type).toBe(contentType);
expect(value.size).toBe(size);
}

describe("transformBody", () => {
it.each([
"application/json",
"application/problem+json",
])("parses %s bodies as JSON", (contentType) => {
expect(
transformBody(
Buffer.from(JSON.stringify({ accepted: true })),
`${contentType}; charset=utf-8`,
),
).toEqual({ accepted: true });
});

it("falls back to text for malformed JSON", () => {
expect(transformBody(Buffer.from('{"event":'), "application/json")).toBe(
'{"event":',
);
});

it.each([
"text/plain",
"application/x-www-form-urlencoded",
])("decodes %s bodies as text", (contentType) => {
expect(transformBody(Buffer.from("hello=world"), contentType)).toBe(
"hello=world",
);
});

it.each([
"application/json",
"text/plain",
])("returns metadata for invalid UTF-8 in direct %s bodies", (contentType) => {
expectBodyMetadata(
transformBody(new Uint8Array([0xff, 0xfe]), contentType),
contentType,
2,
);
});

it("returns metadata for empty bodies", () => {
expectBodyMetadata(
transformBody(new Uint8Array(), "text/plain"),
"text/plain",
0,
);
});

it("returns metadata for multipart bodies", () => {
const contentType = "multipart/form-data; boundary=barestash";

expectBodyMetadata(
transformBody(Buffer.from("--barestash--"), contentType),
contentType,
13,
);
});

it("returns metadata for binary bodies", () => {
expectBodyMetadata(
transformBody(new Uint8Array([0, 1, 2, 255]), "application/octet-stream"),
"application/octet-stream",
4,
);
});
});

describe("stream body transformation", () => {
it("decodes and parses base64 JSON bodies", () => {
const payload = streamPayload(
"application/json",
encodeBase64(JSON.stringify({ streamed: true })),
);

expect(transformStreamBody(payload)).toEqual({ streamed: true });
});

it("falls back to the original base64 for invalid UTF-8 text", () => {
const data = encodeBase64(new Uint8Array([0xff, 0xfe]));
const payload = streamPayload("text/plain", data);

expect(transformStreamBody(payload)).toBe(data);
});

it("returns metadata for empty, multipart, and binary stream bodies", () => {
const cases = [
streamPayload("text/plain", ""),
streamPayload(
"multipart/form-data; boundary=barestash",
encodeBase64("--barestash--"),
),
streamPayload(
"application/octet-stream",
encodeBase64(new Uint8Array([0, 1, 2, 255])),
),
];

expectBodyMetadata(transformStreamBody(cases[0]), "text/plain", 0);
expectBodyMetadata(
transformStreamBody(cases[1]),
"multipart/form-data; boundary=barestash",
13,
);
expectBodyMetadata(
transformStreamBody(cases[2]),
"application/octet-stream",
4,
);
});

it("redacts sensitive headers while preserving safe headers", () => {
const payload = streamPayload(
"application/json",
encodeBase64(JSON.stringify({ ok: true })),
{
authorization: "Bearer test-secret",
"stripe-signature": "test-signature",
"x-barestash-secret": "test-endpoint-secret",
"x-request-id": "req_test",
},
);

expect(transformStreamPayload(payload)).toEqual({
...payload,
request: {
...payload.request,
headers: {
authorization: "[REDACTED]",
"content-type": "application/json",
"stripe-signature": "[REDACTED]",
"x-request-id": "req_test",
},
},
body: { ok: true },
});
});
});
54 changes: 22 additions & 32 deletions apps/cli/src/domain/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,42 @@ export function isBodyMetadata(body: unknown): body is BodyMetadata {
);
}

export function transformBody(bytes: Uint8Array, contentType: string): unknown {
function transformBodyBytes(
bytes: Uint8Array,
contentType: string,
invalidTextFallback: (metadata: BodyMetadata) => unknown,
): unknown {
const metadata = bodyMetadata(contentType, bytes.byteLength);

if (bytes.byteLength === 0 || isMultipartContentType(contentType)) {
return bodyMetadata(contentType, bytes.byteLength);
return metadata;
}

const text = decodeUtf8(bytes);
const isJson = isJsonContentType(contentType);
const isText = isTextContentType(contentType);

if (isJsonContentType(contentType) && text !== null) {
if (text === null) {
return isJson || isText ? invalidTextFallback(metadata) : metadata;
}

if (isJson) {
try {
return JSON.parse(text) as unknown;
} catch {
return text;
}
}

if (isTextContentType(contentType) && text !== null) {
if (isText) {
return text;
}

return bodyMetadata(contentType, bytes.byteLength);
return metadata;
}

export function transformBody(bytes: Uint8Array, contentType: string): unknown {
return transformBodyBytes(bytes, contentType, (metadata) => metadata);
}

function decodeBase64(value: string): Uint8Array {
Expand All @@ -86,33 +102,7 @@ export function transformStreamBody(payload: EventStreamPayload): unknown {
const contentType = payload.request.headers["content-type"] ?? "";
const bytes = decodeBase64(payload.body.data);

if (bytes.byteLength === 0 || isMultipartContentType(contentType)) {
return bodyMetadata(contentType, bytes.byteLength);
}

const text = decodeUtf8(bytes);

if (text === null) {
if (isJsonContentType(contentType) || isTextContentType(contentType)) {
return payload.body.data;
}

return bodyMetadata(contentType, bytes.byteLength);
}

if (isJsonContentType(contentType)) {
try {
return JSON.parse(text) as unknown;
} catch {
return text;
}
}

if (isTextContentType(contentType)) {
return text;
}

return bodyMetadata(contentType, bytes.byteLength);
return transformBodyBytes(bytes, contentType, () => payload.body.data);
}

export function transformStreamPayload(payload: EventStreamPayload): unknown {
Expand Down
83 changes: 83 additions & 0 deletions apps/cli/src/domain/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, expect, it } from "vitest";

import { parseConfig, resolveConfigPath, serializeConfig } from "./config.js";

describe("resolveConfigPath", () => {
it("prefers BARESTASH_CONFIG_FILE over every platform default", () => {
expect(
resolveConfigPath(
{
BARESTASH_CONFIG_FILE: "/override/barestash.json",
XDG_CONFIG_HOME: "/xdg",
APPDATA: "C:/AppData",
},
"win32",
"/home/tester",
),
).toBe("/override/barestash.json");
});

it("prefers a non-empty XDG_CONFIG_HOME over platform defaults", () => {
expect(
resolveConfigPath({ XDG_CONFIG_HOME: "/xdg" }, "darwin", "/Users/tester"),
).toBe("/xdg/barestash/config.json");
});

it("treats an empty XDG_CONFIG_HOME as unset", () => {
expect(
resolveConfigPath({ XDG_CONFIG_HOME: "" }, "darwin", "/Users/tester"),
).toBe("/Users/tester/Library/Application Support/barestash/config.json");
});

it("uses the macOS application support directory", () => {
expect(resolveConfigPath({}, "darwin", "/Users/tester")).toBe(
"/Users/tester/Library/Application Support/barestash/config.json",
);
});

it("uses APPDATA on Windows", () => {
expect(
resolveConfigPath(
{ APPDATA: "C:/Users/tester/AppData/Roaming" },
"win32",
"C:/Users/tester",
),
).toBe("C:/Users/tester/AppData/Roaming/barestash/config.json");
});

it("falls back to the user profile when APPDATA is unavailable on Windows", () => {
expect(resolveConfigPath({}, "win32", "C:/Users/tester")).toBe(
"C:/Users/tester/AppData/Roaming/barestash/config.json",
);
});

it("falls back to the conventional Linux config directory", () => {
expect(resolveConfigPath({}, "linux", "/home/tester")).toBe(
"/home/tester/.config/barestash/config.json",
);
});
});

describe("config serialization", () => {
it.each([
null,
"",
" ",
"{",
"null",
'"not-an-object"',
])("parses empty or malformed config as an empty object: %j", (text) => {
expect(parseConfig(text)).toEqual({});
});

it("round trips token and endpoint config with a trailing newline", () => {
const config = {
token: "test-token",
default_endpoint: "ep_test",
};
const serialized = serializeConfig(config);

expect(serialized.endsWith("\n")).toBe(true);
expect(parseConfig(serialized)).toEqual(config);
});
});
Loading
Loading