test: add contract tests for @-mention extractor boundary
Pins the contract between `extractAtMentionedFiles` and `extractMcpResourceMentions` so the MCP regex can't silently swallow quoted file-path mentions. These tests fail on current HEAD — 3 of 11 cases expose the regression pointed out in the review on #382: `extractMcpResourceMentions`'s trailing `\b` backtracks past the closing `"` of a quoted mention and produces a ghost match for `@"C:\Users\..."`, `@C:\Users\...`, and `@"/tmp/weird:name.txt"`. The remaining 8 cases lock in the behaviour that must not change (legitimate `server:resource` mentions and plain file-path mentions). Committed failing on purpose as the first half of a test-then-fix pair; the regex fix follows in a subsequent commit.
This commit is contained in:
85
src/utils/attachments.extractors.test.ts
Normal file
85
src/utils/attachments.extractors.test.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import {
|
||||
extractAtMentionedFiles,
|
||||
extractMcpResourceMentions,
|
||||
} from './attachments.js'
|
||||
|
||||
// Contract tests for the two @-mention extractors.
|
||||
//
|
||||
// Scope: the narrow contract between `extractAtMentionedFiles` and
|
||||
// `extractMcpResourceMentions` where both are called on the same input
|
||||
// and must not both claim the same token. The motivating bug is that
|
||||
// `extractMcpResourceMentions`'s `\b` anchor lets it backtrack over the
|
||||
// closing quote of a quoted file mention, producing a ghost match for
|
||||
// `@"C:\Users\..."`. These tests pin the boundary so any regression in
|
||||
// the MCP regex is caught immediately.
|
||||
describe('extractor contract', () => {
|
||||
describe('extractMcpResourceMentions must return empty for', () => {
|
||||
const cases: Array<[string, string]> = [
|
||||
// Primary bug: the quoted form that PromptInput emits for Windows
|
||||
// paths today. `\b` backtracks past the trailing `"` and produces
|
||||
// a ghost MCP match on current HEAD.
|
||||
['a quoted Windows drive-letter path', '@"C:\\Users\\me\\file.txt"'],
|
||||
// Even if the quote layer were stripped, a bare drive letter
|
||||
// followed by a path separator is never an MCP resource.
|
||||
['an unquoted Windows drive-letter path', '@C:\\Users\\me\\file.txt'],
|
||||
// Sanity: quoted POSIX paths with no `:` at all never matched the
|
||||
// MCP regex and must keep not matching after the fix.
|
||||
['a quoted POSIX path with a space', '@"/Users/foo/my file.ts"'],
|
||||
['an unquoted POSIX path', '@/Users/foo/bar.ts'],
|
||||
// Quoted POSIX path that embeds a `:` in the filename — the quote
|
||||
// layer must shield it from MCP matching, same as the Windows case.
|
||||
['a quoted POSIX path with a colon in the name', '@"/tmp/weird:name.txt"'],
|
||||
]
|
||||
test.each(cases)('%s', (_label, input) => {
|
||||
expect(extractMcpResourceMentions(input)).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractMcpResourceMentions still matches legitimate MCP mentions', () => {
|
||||
// Regression guard for the fix. If someone tightens the MCP regex
|
||||
// too aggressively, these break and the intent is clear.
|
||||
const cases: Array<[string, string, string[]]> = [
|
||||
[
|
||||
'a simple server:resource token',
|
||||
'@server:resource/path',
|
||||
['server:resource/path'],
|
||||
],
|
||||
[
|
||||
'a plugin-scoped server name with a dash',
|
||||
'@asana-plugin:project-status/123',
|
||||
['asana-plugin:project-status/123'],
|
||||
],
|
||||
[
|
||||
'an MCP mention inline in prose',
|
||||
'please check @server:res here',
|
||||
['server:res'],
|
||||
],
|
||||
]
|
||||
test.each(cases)('%s', (_label, input, expected) => {
|
||||
expect(extractMcpResourceMentions(input)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractAtMentionedFiles extracts the file paths it should', () => {
|
||||
// Asserted separately from the MCP side: the bug is purely in the
|
||||
// MCP extractor over-matching, so these assertions are the
|
||||
// "baseline still works" half of the contract.
|
||||
const cases: Array<[string, string, string[]]> = [
|
||||
[
|
||||
'a quoted Windows drive-letter path',
|
||||
'@"C:\\Users\\me\\file.txt"',
|
||||
['C:\\Users\\me\\file.txt'],
|
||||
],
|
||||
[
|
||||
'a quoted POSIX path with a space',
|
||||
'@"/Users/foo/my file.ts"',
|
||||
['/Users/foo/my file.ts'],
|
||||
],
|
||||
['an unquoted POSIX path', '@/Users/foo/bar.ts', ['/Users/foo/bar.ts']],
|
||||
]
|
||||
test.each(cases)('%s', (_label, input, expected) => {
|
||||
expect(extractAtMentionedFiles(input)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user