refactor: migrate NoteTools to use adapter pattern
Implements Task 10 from the implementation plan: 1. Updated NoteTools constructor to accept IVaultAdapter and IFileManagerAdapter 2. Created note-tools-factory.ts with factory function 3. Migrated all CRUD methods to use adapters: - readNote: uses vault.read() - createNote: uses vault.create(), vault.delete() - createParentFolders: uses vault.createFolder() - updateNote: uses vault.read(), vault.modify() - deleteNote: uses vault.trash(), vault.delete() - renameFile: uses fileManager.renameFile() - readExcalidraw: uses vault.read() - updateFrontmatter: uses vault.read(), vault.modify() - updateSections: uses vault.read(), vault.modify() 4. Extended IVaultAdapter interface with modify(), delete(), and trash() methods 5. Implemented new methods in VaultAdapter 6. Updated ToolRegistry to use factory function 7. Kept app parameter temporarily for PathUtils dependency 8. All methods now use adapters instead of direct Obsidian API calls 9. Code compiles successfully This change enables 100% test coverage by allowing full mocking of vault operations.
This commit is contained in:
@@ -25,6 +25,13 @@ export interface IVaultAdapter {
|
||||
|
||||
// File creation
|
||||
create(path: string, data: string): Promise<TFile>;
|
||||
|
||||
// File modification
|
||||
modify(file: TFile, data: string): Promise<void>;
|
||||
|
||||
// File deletion
|
||||
delete(file: TAbstractFile): Promise<void>;
|
||||
trash(file: TAbstractFile, system: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,4 +38,16 @@ export class VaultAdapter implements IVaultAdapter {
|
||||
async create(path: string, data: string): Promise<TFile> {
|
||||
return this.vault.create(path, data);
|
||||
}
|
||||
|
||||
async modify(file: TFile, data: string): Promise<void> {
|
||||
await this.vault.modify(file, data);
|
||||
}
|
||||
|
||||
async delete(file: TAbstractFile): Promise<void> {
|
||||
await this.vault.delete(file);
|
||||
}
|
||||
|
||||
async trash(file: TAbstractFile, system: boolean): Promise<void> {
|
||||
await this.vault.trash(file, system);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { App } from 'obsidian';
|
||||
import { Tool, CallToolResult } from '../types/mcp-types';
|
||||
import { NoteTools } from './note-tools';
|
||||
import { VaultTools } from './vault-tools';
|
||||
import { createNoteTools } from './note-tools-factory';
|
||||
import { createVaultTools } from './vault-tools-factory';
|
||||
import { NotificationManager } from '../ui/notifications';
|
||||
|
||||
@@ -11,7 +12,7 @@ export class ToolRegistry {
|
||||
private notificationManager: NotificationManager | null = null;
|
||||
|
||||
constructor(app: App) {
|
||||
this.noteTools = new NoteTools(app);
|
||||
this.noteTools = createNoteTools(app);
|
||||
this.vaultTools = createVaultTools(app);
|
||||
}
|
||||
|
||||
|
||||
15
src/tools/note-tools-factory.ts
Normal file
15
src/tools/note-tools-factory.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { App } from 'obsidian';
|
||||
import { NoteTools } from './note-tools';
|
||||
import { VaultAdapter } from '../adapters/vault-adapter';
|
||||
import { FileManagerAdapter } from '../adapters/file-manager-adapter';
|
||||
|
||||
/**
|
||||
* Factory function to create NoteTools with concrete adapters
|
||||
*/
|
||||
export function createNoteTools(app: App): NoteTools {
|
||||
return new NoteTools(
|
||||
new VaultAdapter(app.vault),
|
||||
new FileManagerAdapter(app.fileManager),
|
||||
app
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { App, TFile } from 'obsidian';
|
||||
import {
|
||||
CallToolResult,
|
||||
ParsedNote,
|
||||
import {
|
||||
CallToolResult,
|
||||
ParsedNote,
|
||||
ExcalidrawMetadata,
|
||||
UpdateFrontmatterResult,
|
||||
UpdateSectionsResult,
|
||||
@@ -16,9 +16,14 @@ import { ErrorMessages } from '../utils/error-messages';
|
||||
import { FrontmatterUtils } from '../utils/frontmatter-utils';
|
||||
import { WaypointUtils } from '../utils/waypoint-utils';
|
||||
import { VersionUtils } from '../utils/version-utils';
|
||||
import { IVaultAdapter, IFileManagerAdapter } from '../adapters/interfaces';
|
||||
|
||||
export class NoteTools {
|
||||
constructor(private app: App) {}
|
||||
constructor(
|
||||
private vault: IVaultAdapter,
|
||||
private fileManager: IFileManagerAdapter,
|
||||
private app: App // Keep temporarily for methods not yet migrated
|
||||
) {}
|
||||
|
||||
async readNote(
|
||||
path: string,
|
||||
@@ -67,7 +72,7 @@ export class NoteTools {
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await this.app.vault.read(file);
|
||||
const content = await this.vault.read(file);
|
||||
|
||||
// If no special options, return simple content
|
||||
if (!parseFrontmatter) {
|
||||
@@ -145,7 +150,7 @@ export class NoteTools {
|
||||
// Delete existing file before creating
|
||||
const existingFile = PathUtils.resolveFile(this.app, normalizedPath);
|
||||
if (existingFile) {
|
||||
await this.app.vault.delete(existingFile);
|
||||
await this.vault.delete(existingFile);
|
||||
}
|
||||
} else if (onConflict === 'rename') {
|
||||
// Generate a unique name
|
||||
@@ -198,7 +203,7 @@ export class NoteTools {
|
||||
|
||||
// Proceed with file creation
|
||||
try {
|
||||
const file = await this.app.vault.create(finalPath, content);
|
||||
const file = await this.vault.create(finalPath, content);
|
||||
|
||||
const result: CreateNoteResult = {
|
||||
success: true,
|
||||
@@ -252,7 +257,7 @@ export class NoteTools {
|
||||
|
||||
// Create the current folder if it doesn't exist
|
||||
if (!PathUtils.pathExists(this.app, path)) {
|
||||
await this.app.vault.createFolder(path);
|
||||
await this.vault.createFolder(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +297,7 @@ export class NoteTools {
|
||||
|
||||
try {
|
||||
// Check for waypoint edit protection
|
||||
const currentContent = await this.app.vault.read(file);
|
||||
const currentContent = await this.vault.read(file);
|
||||
const waypointCheck = WaypointUtils.wouldAffectWaypoint(currentContent, content);
|
||||
|
||||
if (waypointCheck.affected) {
|
||||
@@ -313,7 +318,7 @@ export class NoteTools {
|
||||
};
|
||||
}
|
||||
|
||||
await this.app.vault.modify(file, content);
|
||||
await this.vault.modify(file, content);
|
||||
return {
|
||||
content: [{ type: "text", text: `Note updated successfully: ${file.path}` }]
|
||||
};
|
||||
@@ -424,7 +429,7 @@ export class NoteTools {
|
||||
|
||||
// Use Obsidian's FileManager to rename (automatically updates links)
|
||||
// Note: Obsidian's renameFile automatically updates all wikilinks
|
||||
await this.app.fileManager.renameFile(file, normalizedNewPath);
|
||||
await this.fileManager.renameFile(file, normalizedNewPath);
|
||||
|
||||
// Get the renamed file to get version info
|
||||
const renamedFile = PathUtils.resolveFile(this.app, normalizedNewPath);
|
||||
@@ -524,11 +529,11 @@ export class NoteTools {
|
||||
// Perform actual deletion
|
||||
if (soft) {
|
||||
// Move to trash using Obsidian's trash method
|
||||
await this.app.vault.trash(file, true);
|
||||
await this.vault.trash(file, true);
|
||||
destination = `.trash/${file.name}`;
|
||||
} else {
|
||||
// Permanent deletion
|
||||
await this.app.vault.delete(file);
|
||||
await this.vault.delete(file);
|
||||
}
|
||||
|
||||
const result: DeleteNoteResult = {
|
||||
@@ -595,7 +600,7 @@ export class NoteTools {
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await this.app.vault.read(file);
|
||||
const content = await this.vault.read(file);
|
||||
|
||||
// Parse Excalidraw metadata (gracefully handles malformed files)
|
||||
const metadata = FrontmatterUtils.parseExcalidrawMetadata(content);
|
||||
@@ -725,7 +730,7 @@ export class NoteTools {
|
||||
}
|
||||
|
||||
// Read current content
|
||||
const content = await this.app.vault.read(file);
|
||||
const content = await this.vault.read(file);
|
||||
const extracted = FrontmatterUtils.extractFrontmatter(content);
|
||||
|
||||
// Get current frontmatter or create new
|
||||
@@ -767,7 +772,7 @@ export class NoteTools {
|
||||
}
|
||||
|
||||
// Write back
|
||||
await this.app.vault.modify(file, newContent);
|
||||
await this.vault.modify(file, newContent);
|
||||
|
||||
// Generate response with version info
|
||||
const result: UpdateFrontmatterResult = {
|
||||
@@ -851,7 +856,7 @@ export class NoteTools {
|
||||
}
|
||||
|
||||
// Read current content
|
||||
const content = await this.app.vault.read(file);
|
||||
const content = await this.vault.read(file);
|
||||
const lines = content.split('\n');
|
||||
|
||||
// Sort edits by startLine in descending order to apply from bottom to top
|
||||
@@ -891,7 +896,7 @@ export class NoteTools {
|
||||
const newContent = lines.join('\n');
|
||||
|
||||
// Write back
|
||||
await this.app.vault.modify(file, newContent);
|
||||
await this.vault.modify(file, newContent);
|
||||
|
||||
// Generate response with version info
|
||||
const result: UpdateSectionsResult = {
|
||||
|
||||
Reference in New Issue
Block a user