Skip to content

Commit a3fe7ef

Browse files
anandgupta42claude
andcommitted
fix: resolve 5 bugs in formatBytes and truncateQuery
`formatBytes`: - Guard `NaN`/`Infinity` with `Number.isFinite()` → returns "0 B" - Use `Math.abs()` for log calculation to handle negative byte deltas - Clamp unit index with `Math.max(0, Math.min(...))` to prevent `units[-1]` (fractional bytes) and overflow beyond PB `truncateQuery`: - Check post-trim result for empty string → return "(empty)" - Guard `maxLen <= 0` to prevent `slice(0, negative)` leaking content - Guard `maxLen < 4` to hard-truncate without ellipsis Tests updated from documenting bugs to asserting correct behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 29e8c9d commit a3fe7ef

2 files changed

Lines changed: 30 additions & 26 deletions

File tree

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
export function formatBytes(bytes: number): string {
22
if (bytes === 0) return "0 B"
3+
if (!Number.isFinite(bytes)) return "0 B"
4+
const abs = Math.abs(bytes)
35
const units = ["B", "KB", "MB", "GB", "TB", "PB"]
4-
const i = Math.floor(Math.log(bytes) / Math.log(1024))
6+
const i = Math.max(0, Math.min(Math.floor(Math.log(abs) / Math.log(1024)), units.length - 1))
57
const value = bytes / Math.pow(1024, i)
68
return `${value.toFixed(i === 0 ? 0 : 2)} ${units[i]}`
79
}
810

911
export function truncateQuery(text: string, maxLen: number): string {
1012
if (!text) return "(empty)"
1113
const oneLine = text.replace(/\s+/g, " ").trim()
14+
if (!oneLine) return "(empty)"
15+
if (maxLen <= 0) return ""
16+
if (maxLen < 4) return oneLine.slice(0, maxLen)
1217
if (oneLine.length <= maxLen) return oneLine
1318
return oneLine.slice(0, maxLen - 3) + "..."
1419
}

packages/opencode/test/altimate/tools/finops-formatting.test.ts

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,23 @@ describe("formatBytes: normal cases", () => {
1919
})
2020
})
2121

22-
describe("formatBytes: edge cases that expose bugs", () => {
23-
test("negative bytes produces NaN/undefined (known bug)", () => {
24-
// Users see "NaN undefined" when finops tools compute negative deltas
25-
// (e.g. comparing two periods where usage decreased)
26-
const result = formatBytes(-100)
27-
expect(result).toContain("NaN")
22+
describe("formatBytes: edge cases", () => {
23+
test("negative bytes displays with sign", () => {
24+
expect(formatBytes(-100)).toBe("-100 B")
25+
expect(formatBytes(-1536)).toBe("-1.50 KB")
2826
})
2927

30-
test("fractional bytes produces undefined unit (known bug)", () => {
31-
// Math.floor(Math.log(0.5) / Math.log(1024)) = -1, units[-1] is undefined
32-
const result = formatBytes(0.5)
33-
expect(result).toContain("undefined")
28+
test("fractional bytes clamps to B unit", () => {
29+
expect(formatBytes(0.5)).toBe("1 B")
3430
})
3531

36-
test("NaN input produces NaN output (known bug)", () => {
37-
const result = formatBytes(NaN)
38-
expect(result).toContain("NaN")
32+
test("NaN input returns 0 B", () => {
33+
expect(formatBytes(NaN)).toBe("0 B")
34+
})
35+
36+
test("Infinity input returns 0 B", () => {
37+
expect(formatBytes(Infinity)).toBe("0 B")
38+
expect(formatBytes(-Infinity)).toBe("0 B")
3939
})
4040
})
4141

@@ -61,19 +61,18 @@ describe("truncateQuery: normal cases", () => {
6161
})
6262
})
6363

64-
describe("truncateQuery: edge cases that expose bugs", () => {
65-
test("whitespace-only returns empty string instead of (empty) (known bug)", () => {
66-
// " " is truthy so the `if (!text)` guard is skipped.
67-
// After `.replace(/\s+/g, " ").trim()` it becomes "".
68-
// The length check `0 <= 10` passes, returning the empty string directly.
69-
expect(truncateQuery(" ", 10)).toBe("")
64+
describe("truncateQuery: edge cases", () => {
65+
test("whitespace-only returns (empty)", () => {
66+
expect(truncateQuery(" ", 10)).toBe("(empty)")
67+
})
68+
69+
test("maxLen smaller than 4 hard-truncates without ellipsis", () => {
70+
expect(truncateQuery("hello world", 2)).toBe("he")
71+
expect(truncateQuery("hello world", 3)).toBe("hel")
7072
})
7173

72-
test("maxLen smaller than 3 produces string longer than maxLen (known bug)", () => {
73-
// slice(0, 2-3) = slice(0, -1) keeps most of the string, then "..." is appended
74-
const result = truncateQuery("hello world", 2)
75-
expect(result.length).toBeGreaterThan(2)
76-
// Actual output is "hello worl..." (13 chars) — far exceeds the 2-char limit
77-
expect(result).toBe("hello worl...")
74+
test("maxLen zero or negative returns empty string", () => {
75+
expect(truncateQuery("hello", 0)).toBe("")
76+
expect(truncateQuery("hello", -5)).toBe("")
7877
})
7978
})

0 commit comments

Comments
 (0)