Skip to main content
Glama
ProjectConfigResolver.test.ts8.89 kB
import * as path from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ConfigSource, ProjectType } from '../../src/constants/projectType'; import { ProjectConfigResolver } from '../../src/services/ProjectConfigResolver'; import { TemplatesManagerService } from '../../src/services/TemplatesManagerService'; import * as fsHelpers from '../../src/utils/fsHelpers'; // Mock fsHelpers vi.mock('../../src/utils/fsHelpers', async () => { const actual = await vi.importActual('../../src/utils/fsHelpers'); return { ...actual, pathExists: vi.fn(), readJson: vi.fn(), writeJson: vi.fn(), }; }); // Helper to setup pathExists mock function mockPathExists(existingPaths: string[]): void { vi.mocked(fsHelpers.pathExists).mockImplementation(async (filePath) => { return existingPaths.includes(filePath as string); }); } // Helper to setup readJson mock function mockReadJson(content: unknown): void { vi.mocked(fsHelpers.readJson).mockResolvedValueOnce(content); } // Mock TemplatesManagerService vi.mock('../../src/services/TemplatesManagerService', () => ({ TemplatesManagerService: { getWorkspaceRoot: vi.fn(), readToolkitConfig: vi.fn(), writeToolkitConfig: vi.fn(), }, })); describe('ProjectConfigResolver', () => { beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { vi.restoreAllMocks(); }); describe('resolveProjectConfig', () => { it('should resolve config from project.json (monorepo)', async () => { const projectPath = '/test/apps/my-app'; const projectJsonPath = path.join(projectPath, 'project.json'); const mockProjectJson = { name: 'my-app', sourceTemplate: 'nextjs-15', }; mockPathExists([projectJsonPath]); mockReadJson(mockProjectJson); const result = await ProjectConfigResolver.resolveProjectConfig(projectPath); expect(result).toEqual({ type: ProjectType.MONOREPO, sourceTemplate: 'nextjs-15', configSource: ConfigSource.PROJECT_JSON, }); }); it('should resolve config from toolkit.yaml (monolith)', async () => { const projectPath = '/test/my-monolith'; const workspaceRoot = '/test'; const mockToolkitConfig = { version: '1.0', templatesPath: './templates', projectType: 'monolith' as const, sourceTemplate: 'react-vite', }; mockPathExists([]); // project.json doesn't exist vi.mocked(TemplatesManagerService.getWorkspaceRoot).mockResolvedValueOnce(workspaceRoot); vi.mocked(TemplatesManagerService.readToolkitConfig).mockResolvedValueOnce(mockToolkitConfig); const result = await ProjectConfigResolver.resolveProjectConfig(projectPath); expect(result).toEqual({ type: ProjectType.MONOLITH, sourceTemplate: 'react-vite', configSource: ConfigSource.TOOLKIT_YAML, workspaceRoot, }); }); it('should use explicit template when provided', async () => { const projectPath = '/test/my-app'; const explicitTemplate = 'custom-template'; const result = await ProjectConfigResolver.resolveProjectConfig( projectPath, explicitTemplate, ); expect(result).toEqual({ type: ProjectType.MONOLITH, sourceTemplate: 'custom-template', configSource: ConfigSource.TOOLKIT_YAML, }); }); it('should throw helpful error when no configuration found', async () => { const projectPath = '/test/no-config'; mockPathExists([]); vi.mocked(TemplatesManagerService.getWorkspaceRoot).mockResolvedValueOnce('/test'); vi.mocked(TemplatesManagerService.readToolkitConfig).mockResolvedValueOnce(null); await expect(ProjectConfigResolver.resolveProjectConfig(projectPath)).rejects.toThrow( 'No project configuration found', ); }); }); describe('hasConfiguration', () => { it('should return true when configuration exists', async () => { const projectPath = '/test/apps/my-app'; const projectJsonPath = path.join(projectPath, 'project.json'); const mockProjectJson = { name: 'my-app', sourceTemplate: 'nextjs-15', }; mockPathExists([projectJsonPath]); mockReadJson(mockProjectJson); const result = await ProjectConfigResolver.hasConfiguration(projectPath); expect(result).toBe(true); }); it('should return false when configuration does not exist', async () => { const projectPath = '/test/no-config'; mockPathExists([]); vi.mocked(TemplatesManagerService.getWorkspaceRoot).mockResolvedValueOnce('/test'); vi.mocked(TemplatesManagerService.readToolkitConfig).mockResolvedValueOnce(null); const result = await ProjectConfigResolver.hasConfiguration(projectPath); expect(result).toBe(false); }); it('should return false when path does not exist', async () => { const projectPath = '/test/error'; // When pathExists returns false (file doesn't exist), // the flow continues and eventually returns false (no config found) vi.mocked(fsHelpers.pathExists).mockResolvedValueOnce(false); vi.mocked(TemplatesManagerService.getWorkspaceRoot).mockResolvedValueOnce('/test'); vi.mocked(TemplatesManagerService.readToolkitConfig).mockResolvedValueOnce(null); const result = await ProjectConfigResolver.hasConfiguration(projectPath); expect(result).toBe(false); }); }); describe('createToolkitYaml', () => { it('should create toolkit.yaml for monolith project', async () => { const sourceTemplate = 'react-vite'; const workspaceRoot = '/test'; vi.mocked(TemplatesManagerService.getWorkspaceRoot).mockResolvedValueOnce(workspaceRoot); vi.mocked(TemplatesManagerService.readToolkitConfig).mockResolvedValueOnce(null); vi.mocked(TemplatesManagerService.writeToolkitConfig).mockResolvedValueOnce(); await ProjectConfigResolver.createToolkitYaml(sourceTemplate); expect(TemplatesManagerService.writeToolkitConfig).toHaveBeenCalledWith( { version: '1.0', templatesPath: './templates', projectType: 'monolith', sourceTemplate: 'react-vite', }, workspaceRoot, ); }); it('should preserve existing toolkit.yaml settings', async () => { const sourceTemplate = 'react-vite'; const workspaceRoot = '/test'; const existingConfig = { version: '2.0', templatesPath: './custom-templates', }; vi.mocked(TemplatesManagerService.getWorkspaceRoot).mockResolvedValueOnce(workspaceRoot); vi.mocked(TemplatesManagerService.readToolkitConfig).mockResolvedValueOnce(existingConfig); vi.mocked(TemplatesManagerService.writeToolkitConfig).mockResolvedValueOnce(); await ProjectConfigResolver.createToolkitYaml(sourceTemplate); expect(TemplatesManagerService.writeToolkitConfig).toHaveBeenCalledWith( { version: '2.0', // Preserves existing version instead of overwriting templatesPath: './custom-templates', projectType: 'monolith', sourceTemplate: 'react-vite', }, workspaceRoot, ); }); }); describe('createProjectJson', () => { it('should create new project.json when it does not exist', async () => { const projectPath = '/test/apps/my-app'; const projectName = 'my-app'; const sourceTemplate = 'nextjs-15'; const projectJsonPath = path.join(projectPath, 'project.json'); mockPathExists([]); vi.mocked(fsHelpers.writeJson).mockResolvedValueOnce(); await ProjectConfigResolver.createProjectJson(projectPath, projectName, sourceTemplate); expect(fsHelpers.writeJson).toHaveBeenCalledWith( projectJsonPath, expect.objectContaining({ sourceTemplate: 'nextjs-15' }), ); }); it('should update existing project.json with sourceTemplate', async () => { const projectPath = '/test/apps/my-app'; const projectName = 'my-app'; const sourceTemplate = 'nextjs-15'; const projectJsonPath = path.join(projectPath, 'project.json'); const existingProjectJson = { name: 'my-app', $schema: '../../node_modules/nx/schemas/project-schema.json', targets: {}, }; mockPathExists([projectJsonPath]); mockReadJson(existingProjectJson); vi.mocked(fsHelpers.writeJson).mockResolvedValueOnce(); await ProjectConfigResolver.createProjectJson(projectPath, projectName, sourceTemplate); expect(fsHelpers.writeJson).toHaveBeenCalledWith( projectJsonPath, expect.objectContaining({ name: 'my-app', sourceTemplate: 'nextjs-15', targets: {}, }), ); }); }); });

Latest Blog Posts

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