// Copyright 2026 OpenObserve Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { describe, expect, it } from "vitest"; import { raw, type TranslateFn } from "@/types/i18n"; import { chatErrorMessage, createPreview, filterMarkdownHeaders, formatLogEntryContent, getLanguageDisplay, parseLogEntries, processHtmlBlock, processMessageContent, processTextBlock, renderMarkdown, } from "@/components/O2AIChat.content"; describe("filterMarkdownHeaders", () => { it("turns h1 and h2 into bold-with-colon", () => { expect(filterMarkdownHeaders("# One\n## Two")).toBe("**One:**\n**Two:**"); }); it("leaves headers inside fenced code blocks alone", () => { const input = "# Real\n```\n# comment\n```\n"; expect(filterMarkdownHeaders(input)).toBe("**Real:**\n```\n# comment\n```\n"); }); it("restores several code blocks in order", () => { const input = "```a```\n# H\n```b```"; expect(filterMarkdownHeaders(input)).toBe("```a```\n**H:**\n```b```"); }); it("leaves h3 and deeper untouched", () => { expect(filterMarkdownHeaders("### Three")).toBe("### Three"); }); }); describe("processTextBlock", () => { it("is the same function as processMessageContent", () => { expect(processMessageContent).toBe(processTextBlock); }); it("returns a text block for prose", () => { const blocks = processTextBlock("hello"); expect(blocks).toHaveLength(1); expect(blocks[0].type).toBe("text"); expect(blocks[0].content).toContain("hello"); }); it("returns a highlighted code block with its language", () => { const blocks = processTextBlock("```sql\nSELECT 1\n```"); expect(blocks[0].type).toBe("code"); expect(blocks[0].language).toBe("sql"); expect(blocks[0].content).toBe("SELECT 1"); expect(blocks[0].highlightedContent).toContain("hljs-keyword"); }); // Stripping them removed real code, and the copy button then copied a broken snippet. it("keeps leading comment lines in a code block", () => { expect(processTextBlock("```sql\n-- a note\nSELECT 1\n```")[0].content).toBe( "-- a note\nSELECT 1", ); expect(processTextBlock("```bash\n#!/bin/bash\necho hi\n```")[0].content).toBe( "#!/bin/bash\necho hi", ); expect(processTextBlock("```js\n// setup\nconst a = 1;\n```")[0].content).toBe( "// setup\nconst a = 1;", ); }); it("still highlights a code block with an unknown language", () => { const blocks = processTextBlock("```notalang\nx = 1\n```"); expect(blocks[0].language).toBe("notalang"); expect(blocks[0].highlightedContent).toBeTypeOf("string"); }); it("interleaves text and code in source order", () => { const blocks = processTextBlock("before\n\n```js\nvar a = 1\n```\n\nafter"); const codeAt = blocks.findIndex((b) => b.type === "code"); expect(codeAt).toBeGreaterThan(0); expect(blocks[0].content).toContain("before"); expect(blocks[blocks.length - 1].content).toContain("after"); }); it("applies the header filter before lexing", () => { expect(processTextBlock("# Title")[0].content).toContain(""); }); }); describe("renderMarkdown", () => { it("honours the gfm line-break option set by the module", () => { expect(renderMarkdown("a\nb")).toContain("
"); }); }); describe("formatLogEntryContent", () => { it("pretty-prints and class-tags JSON", () => { const html = formatLogEntryContent('{"a":"x","b":1,"c":true,"d":null}'); expect(html).toContain('"a":'); expect(html).toContain('"x"'); expect(html).toContain('1'); expect(html).toContain('true'); expect(html).toContain('null'); }); it("escapes non-JSON text and converts newlines", () => { expect(formatLogEntryContent("&x\nnext")).toBe("<b>&x</b>
next"); }); }); describe("createPreview", () => { it("collapses whitespace in plain text", () => { expect(createPreview("a \n b")).toBe("a b"); }); it("truncates past the max length", () => { expect(createPreview("x".repeat(50), 10)).toBe("x".repeat(10) + "..."); }); it("summarises the first three keys of a JSON object", () => { expect(createPreview('{"a":"1","b":2,"c":3,"d":4}', 100)).toBe('{a: "1", b: 2, c: 3, ...}'); }); it("truncates long string values inside the key summary", () => { expect(createPreview('{"a":"0123456789abc"}', 100)).toBe('{a: "01234567..."}'); }); it("falls back to plain text for a JSON scalar", () => { expect(createPreview("123")).toBe("123"); }); }); describe("parseLogEntries", () => { it("returns the whole message as one text block when there are no delimiters", () => { expect(parseLogEntries("just text")).toEqual([{ type: "text", text: "just text" }]); }); it("returns nothing for empty content", () => { expect(parseLogEntries("")).toEqual([]); }); it("splits text around a log entry and keeps order", () => { const blocks = parseLogEntries("before\n--- f.log ---\nbody\n--- end ---\nafter"); expect(blocks.map((b) => b.type)).toEqual(["text", "log_entry", "text"]); expect(blocks[0].text).toBe("before"); expect(blocks[1]).toMatchObject({ filename: "f.log", content: "body", preview: "body" }); expect(blocks[1].lineStart).toBeUndefined(); expect(blocks[2].text).toBe("after"); }); it("captures the optional line range", () => { const blocks = parseLogEntries("--- f.log (lines 3-9) ---\nbody\n--- end ---"); expect(blocks[0]).toMatchObject({ lineStart: 3, lineEnd: 9 }); }); it("parses several entries in one message", () => { const one = "--- a ---\nA\n--- end ---"; const two = "--- b ---\nB\n--- end ---"; expect(parseLogEntries(`${one}\n${two}`).map((b) => b.type)).toEqual([ "log_entry", "log_entry", ]); }); }); describe("getLanguageDisplay", () => { it("maps known aliases case-insensitively", () => { expect(getLanguageDisplay("JS")).toBe("JavaScript"); expect(getLanguageDisplay("yml")).toBe("YAML"); }); it("upper-cases anything unmapped", () => { expect(getLanguageDisplay("rust")).toBe("RUST"); }); }); describe("chatErrorMessage", () => { const t = ((key: string) => raw(`t:${key}`)) as unknown as TranslateFn; const err = (message: string | undefined, status?: number) => Object.assign(new Error(message), status === undefined ? {} : { status }); it("maps status 403 to the unauthorized key, even when a message is present", () => { expect(chatErrorMessage(err("forbidden body"), t)).toBe("forbidden body"); expect(chatErrorMessage(err("forbidden body", 403), t)).toBe("t:common.unauthorizedAccess"); expect(chatErrorMessage({ status: 403 }, t)).toBe("t:common.unauthorizedAccess"); }); it("does not treat a string status of 403 as unauthorized", () => { expect(chatErrorMessage({ status: "403", message: "m" }, t)).toBe("m"); }); it("uses any other message verbatim, whatever the status", () => { expect(chatErrorMessage(err("Server error 500", 500), t)).toBe("Server error 500"); }); it("falls back to the generic key for the no-body sentinel or an empty message", () => { expect(chatErrorMessage(err("No response body"), t)).toBe( "t:aiAssistant.aiChat.serverResponseError", ); expect(chatErrorMessage(err(""), t)).toBe("t:aiAssistant.aiChat.serverResponseError"); expect(chatErrorMessage({}, t)).toBe("t:aiAssistant.aiChat.serverResponseError"); }); it("throws on a nullish error, as the inline catch did", () => { expect(() => chatErrorMessage(undefined, t)).toThrow(TypeError); expect(() => chatErrorMessage(null, t)).toThrow(TypeError); }); }); describe("processHtmlBlock", () => { it("swaps pre for the generated-code-block span, keeping attributes", () => { expect(processHtmlBlock('
y
')).toBe( 'y', ); }); it("strips script tags", () => { expect(processHtmlBlock("ok")).toBe("ok"); }); it("keeps the default profile for the live chat", () => { expect(processHtmlBlock('')).toContain("x.example"); }); describe("strict (read-only) profile", () => { it("drops remote images but keeps inline ones", () => { const html = processHtmlBlock( '', true, ); expect(html).not.toContain("x.example"); expect(html).toContain("data:image/png;base64,AAAA"); }); it("removes styles and style attributes", () => { const html = processHtmlBlock( '

hi

', true, ); expect(html).toBe("

hi

"); }); it("hardens links", () => { const html = processHtmlBlock('d', true); expect(html).toContain('rel="noopener noreferrer nofollow"'); expect(html).toContain('target="_blank"'); }); it("drops svg and media that could load remote content", () => { const html = processHtmlBlock( 'ok', true, ); expect(html).toBe("ok"); }); }); });