mirror of
https://github.com/aaronsb/obsidian-mcp-plugin.git
synced 2026-07-22 06:45:14 +00:00
Graph operations (neighbors/backlinks/forwardlinks/traverse/path/ statistics) read metadataCache.resolvedLinks and vault.getFiles() directly and never consulted the MCPIgnoreManager. With path exclusions enabled, .mcpignore-ignored paths surfaced as neighbors/ edges of non-ignored queries, and querying an ignored note directly disclosed its relationships. - GraphSearchTool rejects an excluded query root (treated as "File not found", matching ObsidianAPI's read-path behavior). - GraphTraversal filters excluded paths out of the link primitives and every file enumeration (neighbors, root traversal, tag connections, vault statistics, all-nodes). - Add ObsidianAPI.getIgnoreManager() accessor + regression tests. No behavior change when path exclusions are disabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
2.1 KiB
TypeScript
70 lines
2.1 KiB
TypeScript
import { App, TFile } from 'obsidian';
|
|
import { GraphSearchTool } from '../src/tools/graph-search';
|
|
import { ObsidianAPI } from '../src/utils/obsidian-api';
|
|
|
|
function makeFile(path: string): TFile {
|
|
const file = new TFile();
|
|
file.path = path;
|
|
file.name = path.split('/').pop() ?? path;
|
|
// getNodeTitle (added in #207, merged before this PR landed) reads
|
|
// basename — make the mock match the real TFile API.
|
|
file.basename = file.name.replace(/\.md$/, '');
|
|
file.extension = 'md';
|
|
return file;
|
|
}
|
|
|
|
describe('GraphSearchTool', () => {
|
|
let app: App;
|
|
let tool: GraphSearchTool;
|
|
|
|
beforeEach(() => {
|
|
const resolvedTarget = makeFile('resolved.md');
|
|
|
|
app = new App();
|
|
(app as any).metadataCache = {
|
|
resolvedLinks: {
|
|
'source.md': { 'resolved.md': 1 }
|
|
},
|
|
unresolvedLinks: {
|
|
'source.md': { 'Missing Note': 1 }
|
|
},
|
|
getFileCache: jest.fn().mockReturnValue({ tags: [] })
|
|
};
|
|
app.vault.getAbstractFileByPath = jest.fn((path: string) =>
|
|
path === 'resolved.md' ? resolvedTarget : null
|
|
);
|
|
|
|
tool = new GraphSearchTool({ getIgnoreManager: () => undefined } as unknown as ObsidianAPI, app);
|
|
});
|
|
|
|
it('omits unresolved forward links by default', () => {
|
|
const result = tool.search({
|
|
operation: 'forwardlinks',
|
|
sourcePath: 'source.md'
|
|
});
|
|
|
|
expect(result.edges).toEqual([
|
|
{ source: 'source.md', target: 'resolved.md', type: 'link', count: 1 }
|
|
]);
|
|
expect(result.nodes).toEqual([
|
|
expect.objectContaining({ path: 'resolved.md', title: 'resolved' })
|
|
]);
|
|
});
|
|
|
|
it('includes unresolved forward links when requested', () => {
|
|
const result = tool.search({
|
|
operation: 'forwardlinks',
|
|
sourcePath: 'source.md',
|
|
includeUnresolved: true
|
|
});
|
|
|
|
expect(result.edges).toEqual([
|
|
{ source: 'source.md', target: 'resolved.md', type: 'link', count: 1 },
|
|
{ source: 'source.md', target: 'Missing Note', type: 'link', count: 1 }
|
|
]);
|
|
expect(result.nodes).toEqual([
|
|
expect.objectContaining({ path: 'resolved.md', title: 'resolved' })
|
|
]);
|
|
expect(result.message).toBe('Found 2 files linked from this file');
|
|
});
|
|
});
|