Skip to content

Commit 182853c

Browse files
committed
chore: miscellaneous fixes and updates
1 parent 582c0cb commit 182853c

File tree

12 files changed

+1065
-566
lines changed

12 files changed

+1065
-566
lines changed

.eslintcache

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

__tests__/handlers/createDirectories.test.ts

Lines changed: 185 additions & 103 deletions
Large diffs are not rendered by default.

__tests__/handlers/listFiles.test.ts

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,5 +441,168 @@ describe('listFiles Handler (Integration)', () => {
441441
});
442442
});
443443

444-
// Add more tests...
444+
it('should handle stat errors gracefully during non-recursive list', async () => {
445+
if (!tempTestDir) throw new Error('Temp directory not created');
446+
const testDirPathRelative = path.relative(process.cwd(), tempTestDir!);
447+
448+
// Create a file and a potentially problematic entry (like a broken symlink simulation)
449+
await fsPromises.writeFile(
450+
path.join(tempTestDir, 'goodFile.txt'),
451+
'content',
452+
);
453+
// We'll mock readdir to return an entry, and stat to fail for that entry
454+
const mockReaddir = mockDependencies.readdir as Mock;
455+
mockReaddir.mockResolvedValue([
456+
{ name: 'goodFile.txt', isDirectory: () => false, isFile: () => true },
457+
{
458+
name: 'badEntry',
459+
isDirectory: () => false,
460+
isFile: () => false,
461+
isSymbolicLink: () => true,
462+
}, // Simulate needing stat
463+
]);
464+
465+
const mockStat = mockDependencies.stat as Mock;
466+
mockStat.mockImplementation(async (p: PathLike) => {
467+
if (p.toString().endsWith('badEntry')) {
468+
throw new Error('Mocked stat failure for bad entry');
469+
}
470+
// Use actual stat for the good file
471+
const actualFsPromises =
472+
await vi.importActual<typeof fsPromises>('fs/promises');
473+
return actualFsPromises.stat(p);
474+
});
475+
476+
const args = {
477+
path: testDirPathRelative,
478+
recursive: false,
479+
include_stats: false,
480+
};
481+
const result = await handleListFilesFunc(mockDependencies, args);
482+
const resultData = JSON.parse(result.content[0].text);
483+
484+
// Should still list the good file, and the bad entry (assuming not a dir)
485+
expect(resultData).toHaveLength(2);
486+
expect(resultData).toEqual(
487+
expect.arrayContaining([
488+
`${testDirPathRelative}/goodFile.txt`.replace(/\\\\/g, '/'),
489+
`${testDirPathRelative}/badEntry`.replace(/\\\\/g, '/'), // Assumes not a dir if stat fails
490+
]),
491+
);
492+
});
493+
494+
it('should skip current directory entry (.) when returned by glob', async () => {
495+
if (!tempTestDir) throw new Error('Temp directory not created');
496+
const testDirPathRelative = path.relative(process.cwd(), tempTestDir!);
497+
await fsPromises.writeFile(path.join(tempTestDir, 'file1.txt'), 'content');
498+
499+
// Mock glob to return '.' along with the file
500+
mockGlob.mockResolvedValue(['.', 'file1.txt']);
501+
502+
const args = {
503+
path: testDirPathRelative,
504+
recursive: false,
505+
include_stats: true,
506+
}; // Use glob path
507+
const result = await handleListFilesFunc(mockDependencies, args);
508+
const resultData = JSON.parse(result.content[0].text);
509+
510+
expect(resultData).toHaveLength(1); // '.' should be skipped
511+
expect(resultData[0].path).toBe(
512+
`${testDirPathRelative}/file1.txt`.replace(/\\\\/g, '/'),
513+
);
514+
});
515+
516+
it('should handle stat errors within glob results when include_stats is true', async () => {
517+
if (!tempTestDir) throw new Error('Temp directory not created');
518+
const testDirPathRelative = path.relative(process.cwd(), tempTestDir!);
519+
await fsPromises.writeFile(path.join(tempTestDir, 'file1.txt'), 'content');
520+
await fsPromises.writeFile(
521+
path.join(tempTestDir, 'file2-stat-error.txt'),
522+
'content2',
523+
);
524+
525+
// Mock glob to return both files
526+
mockGlob.mockResolvedValue(['file1.txt', 'file2-stat-error.txt']);
527+
528+
// Mock stat to fail for the second file
529+
const mockStat = mockDependencies.stat as Mock;
530+
mockStat.mockImplementation(async (p: PathLike) => {
531+
if (p.toString().endsWith('file2-stat-error.txt')) {
532+
throw new Error('Mocked stat error for glob');
533+
}
534+
const actualFsPromises =
535+
await vi.importActual<typeof fsPromises>('fs/promises');
536+
return actualFsPromises.stat(p);
537+
});
538+
539+
const args = {
540+
path: testDirPathRelative,
541+
recursive: false,
542+
include_stats: true,
543+
}; // Use glob path
544+
const result = await handleListFilesFunc(mockDependencies, args);
545+
const resultData = JSON.parse(result.content[0].text);
546+
547+
expect(resultData).toHaveLength(2);
548+
const file1Result = resultData.find((r: any) =>
549+
r.path.endsWith('file1.txt'),
550+
);
551+
const file2Result = resultData.find((r: any) =>
552+
r.path.endsWith('file2-stat-error.txt'),
553+
);
554+
555+
expect(file1Result?.stats?.error).toBeUndefined();
556+
expect(file2Result?.stats?.error).toMatch(
557+
/Could not get stats: Mocked stat error for glob/,
558+
);
559+
});
560+
561+
it('should throw McpError if glob itself throws an error', async () => {
562+
if (!tempTestDir) throw new Error('Temp directory not created');
563+
const testDirPathRelative = path.relative(process.cwd(), tempTestDir!);
564+
const globError = new Error('Internal glob failure');
565+
mockGlob.mockRejectedValue(globError);
566+
567+
const args = {
568+
path: testDirPathRelative,
569+
recursive: true,
570+
include_stats: true,
571+
}; // Use glob path
572+
573+
await expect(handleListFilesFunc(mockDependencies, args)).rejects.toThrow(
574+
McpError,
575+
);
576+
await expect(
577+
handleListFilesFunc(mockDependencies, args),
578+
).rejects.toMatchObject({
579+
code: ErrorCode.InternalError,
580+
message: expect.stringContaining(
581+
'Failed to list files using glob: Internal glob failure',
582+
),
583+
});
584+
});
585+
586+
it('should handle generic errors during initial stat (non-ENOENT)', async () => {
587+
if (!tempTestDir) throw new Error('Temp directory not created');
588+
const testDirPathRelative = path.relative(process.cwd(), tempTestDir!);
589+
const genericError = new Error('Generic stat failure');
590+
(mockDependencies.stat as Mock).mockRejectedValue(genericError);
591+
592+
const args = { path: testDirPathRelative };
593+
594+
await expect(handleListFilesFunc(mockDependencies, args)).rejects.toThrow(
595+
McpError,
596+
);
597+
await expect(
598+
handleListFilesFunc(mockDependencies, args),
599+
).rejects.toMatchObject({
600+
code: ErrorCode.InternalError,
601+
message: expect.stringContaining(
602+
'Failed to process path: Generic stat failure',
603+
),
604+
});
605+
});
606+
607+
// Add more tests..." // Keep this line for potential future additions
445608
});

0 commit comments

Comments
 (0)