import { describe, it, expect, beforeEach } from 'vitest';
import { resolve } from 'path';
import { PathResolver } from '../../src/utils/path-resolver.js';
import { getFrontmatterTool } from '../../src/tools/get-frontmatter.js';
const FIXTURES_DIR = resolve(process.cwd(), 'tests', 'fixtures');
describe('get_mdx_frontmatter tool', () => {
let pathResolver: PathResolver;
beforeEach(() => {
pathResolver = new PathResolver(FIXTURES_DIR);
});
describe('files with frontmatter', () => {
it('should extract frontmatter from MDX file', async () => {
const result = await getFrontmatterTool(
{ path: 'basic-with-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
expect(result.content).toHaveLength(1);
const parsed = JSON.parse(result.content[0].text);
expect(parsed).toHaveProperty('frontmatter');
expect(parsed).toHaveProperty('hasFrontmatter');
expect(parsed.hasFrontmatter).toBe(true);
expect(parsed.frontmatter).not.toBeNull();
});
it('should extract all frontmatter fields', async () => {
const result = await getFrontmatterTool(
{ path: 'basic-with-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.frontmatter).toHaveProperty('title');
expect(parsed.frontmatter).toHaveProperty('author');
expect(parsed.frontmatter).toHaveProperty('date');
expect(parsed.frontmatter).toHaveProperty('tags');
expect(parsed.frontmatter).toHaveProperty('published');
});
it('should correctly parse string values', async () => {
const result = await getFrontmatterTool(
{ path: 'basic-with-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.frontmatter.title).toBe('Getting Started Guide');
expect(parsed.frontmatter.author).toBe('John Doe');
expect(typeof parsed.frontmatter.title).toBe('string');
});
it('should correctly parse boolean values', async () => {
const result = await getFrontmatterTool(
{ path: 'basic-with-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.frontmatter.published).toBe(true);
expect(typeof parsed.frontmatter.published).toBe('boolean');
});
it('should correctly parse string values', async () => {
const result = await getFrontmatterTool(
{ path: 'simple-jsx.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
// Verify string type is correctly parsed
expect(parsed.frontmatter.type).toBeDefined();
expect(parsed.frontmatter.type).toBe('demo');
expect(typeof parsed.frontmatter.type).toBe('string');
});
it('should handle frontmatter with multiple field types', async () => {
const result = await getFrontmatterTool(
{ path: 'search-test.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.hasFrontmatter).toBe(true);
expect(parsed.frontmatter).toHaveProperty('title');
expect(parsed.frontmatter).toHaveProperty('category');
expect(parsed.frontmatter).toHaveProperty('keywords');
});
});
describe('files without frontmatter', () => {
it('should return null frontmatter for files without it', async () => {
const result = await getFrontmatterTool(
{ path: 'no-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.hasFrontmatter).toBe(false);
expect(parsed.frontmatter).toBeNull();
});
it('should correctly identify files without frontmatter', async () => {
const result = await getFrontmatterTool(
{ path: 'no-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed).toHaveProperty('hasFrontmatter');
expect(parsed.hasFrontmatter).toBe(false);
});
});
describe('error handling', () => {
it('should handle non-existent files', async () => {
const result = await getFrontmatterTool(
{ path: 'nonexistent.mdx' },
pathResolver
);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('File not found');
});
it('should handle unsafe paths (directory traversal)', async () => {
const result = await getFrontmatterTool(
{ path: '../../etc/passwd' },
pathResolver
);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('outside workspace root');
});
it('should handle absolute paths outside workspace', async () => {
const result = await getFrontmatterTool(
{ path: '/etc/passwd' },
pathResolver
);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('outside workspace root');
});
});
describe('frontmatter parsing', () => {
it('should handle quoted string values', async () => {
const result = await getFrontmatterTool(
{ path: 'simple-jsx.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.frontmatter.title).toBe('Simple JSX Example');
expect(parsed.frontmatter.type).toBe('demo');
});
it('should handle date strings', async () => {
const result = await getFrontmatterTool(
{ path: 'basic-with-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.frontmatter.date).toBe('2025-01-15');
expect(typeof parsed.frontmatter.date).toBe('string');
});
it('should handle comma-separated values', async () => {
const result = await getFrontmatterTool(
{ path: 'basic-with-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.frontmatter.tags).toBeTruthy();
// It might be a string with commas or an array depending on parser
expect(parsed.frontmatter.tags).toBeDefined();
});
it('should return consistent structure for all files', async () => {
const results = await Promise.all([
getFrontmatterTool({ path: 'basic-with-frontmatter.mdx' }, pathResolver),
getFrontmatterTool({ path: 'no-frontmatter.mdx' }, pathResolver),
getFrontmatterTool({ path: 'simple-jsx.mdx' }, pathResolver)
]);
results.forEach(result => {
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed).toHaveProperty('frontmatter');
expect(parsed).toHaveProperty('hasFrontmatter');
expect(typeof parsed.hasFrontmatter).toBe('boolean');
});
});
});
describe('content validation', () => {
it('should return valid JSON in response', async () => {
const result = await getFrontmatterTool(
{ path: 'basic-with-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
expect(() => JSON.parse(result.content[0].text)).not.toThrow();
});
it('should handle empty frontmatter fields gracefully', async () => {
const result = await getFrontmatterTool(
{ path: 'search-test.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
// All frontmatter fields should be accessible
Object.keys(parsed.frontmatter).forEach(key => {
expect(parsed.frontmatter[key]).toBeDefined();
});
});
});
describe('path handling', () => {
it('should handle relative paths correctly', async () => {
const result = await getFrontmatterTool(
{ path: './basic-with-frontmatter.mdx' },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.hasFrontmatter).toBe(true);
});
it('should work with different file types', async () => {
// Test with multiple MDX files
const files = [
'basic-with-frontmatter.mdx',
'simple-jsx.mdx',
'search-test.mdx'
];
for (const file of files) {
const result = await getFrontmatterTool(
{ path: file },
pathResolver
);
expect(result.isError).toBeFalsy();
const parsed = JSON.parse(result.content[0].text);
expect(parsed.hasFrontmatter).toBe(true);
}
});
});
});