Skip to main content
Glama

AI Code Toolkit

by AgiFlow
ScaffoldingMethodsService.test.ts12.9 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { IFileSystemService } from '../../src/types/interfaces'; import { ScaffoldingMethodsService } from '../../src/services/ScaffoldingMethodsService'; // Mock ProjectConfigResolver vi.mock('@agiflowai/aicode-utils', () => ({ log: { warn: vi.fn(), error: vi.fn(), info: vi.fn(), }, ProjectConfigResolver: { resolveProjectConfig: vi.fn(), }, })); describe('ScaffoldingMethodsService', () => { let service: ScaffoldingMethodsService; let mockFileSystem: IFileSystemService; const templatesPath = '/test/templates'; beforeEach(() => { mockFileSystem = { pathExists: vi.fn(), readFile: vi.fn(), writeFile: vi.fn(), ensureDir: vi.fn(), readdir: vi.fn(), stat: vi.fn(), copyFile: vi.fn(), remove: vi.fn(), } as any; service = new ScaffoldingMethodsService(mockFileSystem, templatesPath); vi.clearAllMocks(); }); describe('listScaffoldingMethods with boilerplate name', () => { it('should find template by boilerplate name when sourceTemplate is a boilerplate name', async () => { const { ProjectConfigResolver } = await import('@agiflowai/aicode-utils'); // Mock project config returning a boilerplate name instead of template name (ProjectConfigResolver.resolveProjectConfig as any).mockResolvedValue({ sourceTemplate: 'scaffold-nextjs-app', // This is a boilerplate name from nextjs-15-drizzle }); // Mock file system for template discovery (mockFileSystem.readdir as any) .mockResolvedValueOnce([ // First level - templates root 'nextjs-15-drizzle', 'typescript-mcp-package', 'README.md', ]) .mockResolvedValueOnce([ // Won't be called for this test ]); // Mock stats for directory checking (mockFileSystem.stat as any).mockImplementation((path: string) => { if (path.includes('README.md')) { return Promise.resolve({ isDirectory: () => false }); } return Promise.resolve({ isDirectory: () => true }); }); // Mock pathExists for scaffold.yaml files (mockFileSystem.pathExists as any).mockImplementation((path: string) => { if (path.includes('nextjs-15-drizzle/scaffold.yaml')) { return Promise.resolve(true); } if (path.includes('typescript-mcp-package/scaffold.yaml')) { return Promise.resolve(true); } return Promise.resolve(false); }); // Mock scaffold.yaml content for nextjs-15-drizzle const scaffoldContent = ` boilerplate: - name: scaffold-nextjs-app targetFolder: apps description: A Next.js app variables_schema: type: object properties: appName: type: string required: - appName additionalProperties: false features: - name: scaffold-route description: Generate a new route variables_schema: type: object properties: routePath: type: string pageTitle: type: string required: - routePath - pageTitle additionalProperties: false - name: scaffold-component description: Generate a new component variables_schema: type: object properties: componentName: type: string required: - componentName additionalProperties: false `; (mockFileSystem.readFile as any).mockResolvedValue(scaffoldContent); const result = await service.listScaffoldingMethods('/test/apps/my-app'); expect(result.sourceTemplate).toBe('scaffold-nextjs-app'); expect(result.templatePath).toBe('nextjs-15-drizzle'); expect(result.methods).toHaveLength(2); expect(result.methods[0].name).toBe('scaffold-route'); expect(result.methods[1].name).toBe('scaffold-component'); }); it('should handle template name as sourceTemplate (exact match)', async () => { const { ProjectConfigResolver } = await import('@agiflowai/aicode-utils'); // Mock project config with exact template name (ProjectConfigResolver.resolveProjectConfig as any).mockResolvedValue({ sourceTemplate: 'nextjs-15-drizzle', }); // Mock file system for template discovery (mockFileSystem.readdir as any).mockResolvedValue([ 'nextjs-15-drizzle', 'typescript-mcp-package', ]); (mockFileSystem.stat as any).mockResolvedValue({ isDirectory: () => true }); (mockFileSystem.pathExists as any).mockImplementation((path: string) => { if (path.includes('nextjs-15-drizzle/scaffold.yaml')) { return Promise.resolve(true); } return Promise.resolve(false); }); const scaffoldContent = ` features: - name: scaffold-route description: Generate a new route variables_schema: type: object properties: {} required: [] additionalProperties: false `; (mockFileSystem.readFile as any).mockResolvedValue(scaffoldContent); const result = await service.listScaffoldingMethods('/test/apps/my-app'); expect(result.sourceTemplate).toBe('nextjs-15-drizzle'); expect(result.templatePath).toBe('nextjs-15-drizzle'); expect(result.methods).toHaveLength(1); }); it('should throw error when template not found for boilerplate name', async () => { const { ProjectConfigResolver } = await import('@agiflowai/aicode-utils'); (ProjectConfigResolver.resolveProjectConfig as any).mockResolvedValue({ sourceTemplate: 'nonexistent-boilerplate', }); (mockFileSystem.readdir as any).mockResolvedValue(['nextjs-15-drizzle']); (mockFileSystem.stat as any).mockResolvedValue({ isDirectory: () => true }); (mockFileSystem.pathExists as any).mockResolvedValue(true); const scaffoldContent = ` boilerplate: - name: scaffold-some-other-app features: [] `; (mockFileSystem.readFile as any).mockResolvedValue(scaffoldContent); await expect(service.listScaffoldingMethods('/test/apps/my-app')).rejects.toThrow( 'Template not found for sourceTemplate: nonexistent-boilerplate', ); }); it('should handle nested template directories', async () => { const { ProjectConfigResolver } = await import('@agiflowai/aicode-utils'); (ProjectConfigResolver.resolveProjectConfig as any).mockResolvedValue({ sourceTemplate: 'scaffold-nested-app', }); // Mock file system for nested structure (mockFileSystem.readdir as any) .mockResolvedValueOnce([ // First level - templates root 'apps', 'packages', ]) .mockResolvedValueOnce([ // Second level - apps/ 'nextjs-15', ]) .mockResolvedValueOnce([ // Second level - packages/ 'typescript-lib', ]); (mockFileSystem.stat as any).mockResolvedValue({ isDirectory: () => true }); (mockFileSystem.pathExists as any).mockImplementation((path: string) => { // No scaffold.yaml at top level if (path === '/test/templates/apps/scaffold.yaml') { return Promise.resolve(false); } if (path === '/test/templates/packages/scaffold.yaml') { return Promise.resolve(false); } // scaffold.yaml exists in nested directories if (path.includes('apps/nextjs-15/scaffold.yaml')) { return Promise.resolve(true); } if (path.includes('packages/typescript-lib/scaffold.yaml')) { return Promise.resolve(true); } return Promise.resolve(false); }); const scaffoldContent = ` boilerplate: - name: scaffold-nested-app features: - name: scaffold-feature description: Test feature variables_schema: type: object properties: {} required: [] additionalProperties: false `; (mockFileSystem.readFile as any).mockResolvedValue(scaffoldContent); const result = await service.listScaffoldingMethods('/test/apps/my-app'); expect(result.sourceTemplate).toBe('scaffold-nested-app'); expect(result.templatePath).toBe('apps/nextjs-15'); expect(result.methods).toHaveLength(1); }); it('should return empty methods array when no features defined', async () => { const { ProjectConfigResolver } = await import('@agiflowai/aicode-utils'); (ProjectConfigResolver.resolveProjectConfig as any).mockResolvedValue({ sourceTemplate: 'empty-template', }); (mockFileSystem.readdir as any).mockResolvedValue(['empty-template']); (mockFileSystem.stat as any).mockResolvedValue({ isDirectory: () => true }); (mockFileSystem.pathExists as any).mockResolvedValue(true); const scaffoldContent = ` boilerplate: - name: scaffold-empty-app # No features defined `; (mockFileSystem.readFile as any).mockResolvedValue(scaffoldContent); const result = await service.listScaffoldingMethods('/test/apps/my-app'); expect(result.methods).toHaveLength(0); }); it('should use feature name when provided, fallback to sourceTemplate otherwise', async () => { const { ProjectConfigResolver } = await import('@agiflowai/aicode-utils'); (ProjectConfigResolver.resolveProjectConfig as any).mockResolvedValue({ sourceTemplate: 'test-template', }); (mockFileSystem.readdir as any).mockResolvedValue(['test-template']); (mockFileSystem.stat as any).mockResolvedValue({ isDirectory: () => true }); (mockFileSystem.pathExists as any).mockResolvedValue(true); const scaffoldContent = ` features: - name: scaffold-with-name description: Feature with explicit name variables_schema: type: object properties: {} required: [] additionalProperties: false - description: Feature without name variables_schema: type: object properties: {} required: [] additionalProperties: false `; (mockFileSystem.readFile as any).mockResolvedValue(scaffoldContent); const result = await service.listScaffoldingMethods('/test/apps/my-app'); expect(result.methods).toHaveLength(2); expect(result.methods[0].name).toBe('scaffold-with-name'); expect(result.methods[1].name).toBe('scaffold-test-template'); // Fallback to sourceTemplate }); }); describe('listScaffoldingMethodsWithVariables', () => { it('should call listScaffoldingMethods and return methods', async () => { const { ProjectConfigResolver } = await import('@agiflowai/aicode-utils'); (ProjectConfigResolver.resolveProjectConfig as any).mockResolvedValue({ sourceTemplate: 'test-template', }); (mockFileSystem.readdir as any).mockResolvedValue(['test-template']); (mockFileSystem.stat as any).mockResolvedValue({ isDirectory: () => true }); (mockFileSystem.pathExists as any).mockResolvedValue(true); const scaffoldContent = ` features: - name: scaffold-feature description: Test feature instruction: "Create a component" variables_schema: type: object properties: componentName: type: string required: - componentName additionalProperties: false `; (mockFileSystem.readFile as any).mockResolvedValue(scaffoldContent); const result = await service.listScaffoldingMethodsWithVariables('/test/apps/my-app', { componentName: 'MyButton', }); expect(result.methods).toHaveLength(1); expect(result.methods[0].name).toBe('scaffold-feature'); // The instruction should be returned (it doesn't have template variables in this simple test) expect(result.methods[0].instruction).toBe('Create a component'); }); it('should handle methods without instructions', async () => { const { ProjectConfigResolver } = await import('@agiflowai/aicode-utils'); (ProjectConfigResolver.resolveProjectConfig as any).mockResolvedValue({ sourceTemplate: 'test-template', }); (mockFileSystem.readdir as any).mockResolvedValue(['test-template']); (mockFileSystem.stat as any).mockResolvedValue({ isDirectory: () => true }); (mockFileSystem.pathExists as any).mockResolvedValue(true); const scaffoldContent = ` features: - name: scaffold-feature description: Test feature variables_schema: type: object properties: {} required: [] additionalProperties: false `; (mockFileSystem.readFile as any).mockResolvedValue(scaffoldContent); const result = await service.listScaffoldingMethodsWithVariables('/test/apps/my-app', {}); expect(result.methods).toHaveLength(1); expect(result.methods[0].instruction).toBeUndefined(); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AgiFlow/aicode-toolkit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server