test: escape expected XML paths the way the registry does

The anchored `<output-file>` assertions built their expected value by hand —
one replaced `&` only, the other nothing at all. `tmpdir()` may legally
contain XML metacharacters (`&` on Windows, `<` on POSIX), so those cases
became environment-dependent the moment they moved off the fixed `/tmp`
path.

Run the expected path through the same `escapeXml(stripDisplayControlChars())`
the registry uses. Verified with `TMPDIR=/tmp/qwen-xml-probe/a&b<c`: the
helper passes all three focused cases, while the hand-rolled version fails
two.
This commit is contained in:
wenshao 2026-08-10 01:00:34 +08:00
parent e3268f9d01
commit 5549db2348

View file

@ -27,6 +27,18 @@ import {
type ShellTaskRegistration,
} from './backgroundShellRegistry.js';
import { todoWorkChainContext } from '../utils/promptIdContext.js';
import { escapeXml } from '../utils/xml.js';
import { stripDisplayControlChars } from '../utils/terminalSafe.js';
/**
* Mirror of how the registry renders a path into the notification XML. The
* expected paths below are built from `tmpdir()`, which can legally contain
* XML metacharacters (`&` on Windows, `<` on POSIX), so hand-rolling the
* escaping here would make these cases depend on the host's TMPDIR.
*/
function expectedOutputFileElement(path: string): string {
return `<output-file>${escapeXml(stripDisplayControlChars(path))}</output-file>`;
}
let tmpDirs: string[] = [];
@ -355,9 +367,7 @@ describe('BackgroundShellRegistry', () => {
expect(modelText).toContain('<result>bad &lt;thing&gt;[31m</result>');
// Assert the whole element, not just the tail: the temp prefix is
// random but the escaping is what this test is about.
expect(modelText).toContain(
`<output-file>${outputPath.replaceAll('&', '&amp;')}</output-file>`,
);
expect(modelText).toContain(expectedOutputFileElement(outputPath));
});
it('limits output-tail to the retained byte budget', () => {
@ -425,7 +435,7 @@ describe('BackgroundShellRegistry', () => {
// Whole element: pins that only the control byte is stripped and the
// rest of the path survives intact.
expect(modelText).toContain(
`<output-file>${join(dir, 'out.log')}</output-file>`,
expectedOutputFileElement(join(dir, 'out.log')),
);
expect(modelText).not.toContain('\x01');
expect(modelText).not.toContain('\x02');