Skip to main content
Glama

CTX: Context as Code (CaC) tool

by context-hub
MIT License
235
  • Apple
  • Linux
PromptsTest.php21.7 kB
<?php declare(strict_types=1); namespace Tests\Feature\Console\GenerateCommand; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use Tests\Feature\Console\ConsoleTestCase; final class PromptsTest extends ConsoleTestCase { private string $outputDir; public static function commandsProvider(): \Generator { yield 'generate' => ['generate']; yield 'build' => ['build']; yield 'compile' => ['compile']; } #[Test] #[DataProvider('commandsProvider')] public function basic_prompts_should_be_compiled(string $command): void { // Create a basic prompt configuration file $configFile = $this->createTempFile( <<<'YAML' prompts: - id: test-prompt description: "A simple test prompt" schema: properties: name: description: "User's name" required: - name messages: - role: user content: "Hello {{name}}, this is a test prompt." YAML, '.yaml', ); $this ->buildContext( workDir: $this->outputDir, configPath: $configFile, command: $command, ) ->assertSuccess() ->assertPromptExists('test-prompt') ->assertPrompt('test-prompt', [ 'type' => 'prompt', 'description' => 'A simple test prompt', ]) ->assertPromptMessages('test-prompt', [ 'Hello {{name}}, this is a test prompt.', ]) ->assertPromptSchema( 'test-prompt', [ 'name' => ['description' => "User's name"], ], ['name'], ); } #[Test] #[DataProvider('commandsProvider')] public function multiple_prompts_should_be_compiled(string $command): void { // Create a configuration with multiple prompts $configFile = $this->createTempFile( <<<'YAML' prompts: - id: first-prompt description: "First test prompt" schema: properties: name: description: "User's name" required: - name messages: - role: user content: "Hello {{name}}, this is the first prompt." - id: second-prompt description: "Second test prompt" schema: properties: query: description: "User's query" required: - query messages: - role: assistant content: "I'll help you with: {{query}}" YAML, '.yaml', ); $this ->buildContext( workDir: $this->outputDir, configPath: $configFile, command: $command, ) ->assertSuccess() ->assertPromptCount(2) ->assertPromptExists('first-prompt') ->assertPromptExists('second-prompt') ->assertPrompt('first-prompt', [ 'type' => 'prompt', 'description' => 'First test prompt', ]) ->assertPrompt('second-prompt', [ 'type' => 'prompt', 'description' => 'Second test prompt', ]); } #[Test] #[DataProvider('commandsProvider')] public function prompt_templates_should_be_compiled(string $command): void { // Create a configuration with template and prompt that extends it $configFile = $this->createTempFile( <<<'YAML' prompts: - id: base-template type: template messages: - role: user content: "This is a base template with {{variable}}." - id: extended-prompt type: prompt description: "Prompt that extends a template" extend: - id: base-template arguments: variable: "custom value" schema: properties: additionalVar: description: "Additional parameter" required: - additionalVar messages: - role: user content: "Additional message with {{additionalVar}}." YAML, '.yaml', ); $this ->buildContext( workDir: $this->outputDir, configPath: $configFile, command: $command, ) ->assertSuccess() ->assertPromptExists('extended-prompt') ->assertPrompt('extended-prompt', [ 'type' => 'prompt', 'description' => 'Prompt that extends a template', ]) ->assertPromptExtends('extended-prompt', 'base-template') ->assertPromptTemplateArguments('extended-prompt', 'base-template', [ 'variable' => 'custom value', ]) ->assertPromptMessages('extended-prompt', [ 'This is a base template with custom value.', 'Additional message with {{additionalVar}}.', ]) ->assertPromptSchema( 'extended-prompt', [ 'additionalVar' => ['description' => 'Additional parameter'], ], ['additionalVar'], ); } #[Test] #[DataProvider('commandsProvider')] public function complex_prompt_inheritance_should_be_compiled(string $command): void { // Create a configuration with multiple levels of template inheritance $configFile = $this->createTempFile( <<<'YAML' prompts: - id: base-template type: template messages: - role: user content: "Base content with {{baseVar}}." - id: intermediate-template type: template extend: - id: base-template arguments: baseVar: "{{intermediateVar}}" messages: - role: user content: "Intermediate content with {{intermediateVar}}." - id: final-prompt type: prompt description: "Multi-level inherited prompt" extend: - id: intermediate-template arguments: intermediateVar: "final value" messages: - role: user content: "Final content." YAML, '.yaml', ); $this ->buildContext( workDir: $this->outputDir, configPath: $configFile, command: $command, ) ->assertSuccess() ->assertPromptExists('final-prompt') ->assertPromptExtends('final-prompt', 'intermediate-template') ->assertPromptTemplateArguments('final-prompt', 'intermediate-template', [ 'intermediateVar' => 'final value', ]) ->assertPromptMessages('final-prompt', [ 'Base content with final value.', 'Intermediate content with final value.', 'Final content.', ]); } #[Test] #[DataProvider('commandsProvider')] public function prompt_with_complex_schema_should_be_compiled(string $command): void { // Create a configuration with a complex schema $configFile = $this->createTempFile( <<<'YAML' prompts: - id: complex-schema-prompt description: "Prompt with complex schema" schema: properties: title: description: "Document title" sections: description: "Document sections" author: description: "Document author" tags: description: "Document tags" required: - title - author messages: - role: user content: "Create a document titled '{{title}}' by {{author}} with sections on {{sections}}. Tags: {{tags}}" YAML, '.yaml', ); $this ->buildContext( workDir: $this->outputDir, configPath: $configFile, command: $command, ) ->assertSuccess() ->assertPromptExists('complex-schema-prompt') ->assertPromptSchema( 'complex-schema-prompt', [ 'title' => ['description' => 'Document title'], 'sections' => ['description' => 'Document sections'], 'author' => ['description' => 'Document author'], 'tags' => ['description' => 'Document tags'], ], ['title', 'author'], ); } #[Test] #[DataProvider('commandsProvider')] public function prompts_with_tags_should_be_compiled(string $command): void { // Create a configuration with prompts having tags $configFile = $this->createTempFile( <<<'YAML' prompts: - id: coding-prompt description: "Coding assistant prompt" tags: ["coding", "development", "programming"] messages: - role: user content: "Help me with coding." - id: writing-prompt description: "Writing assistant prompt" tags: ["writing", "content", "creativity"] messages: - role: user content: "Help me with writing." YAML, '.yaml', ); $result = $this->buildContext( workDir: $this->outputDir, configPath: $configFile, command: $command, ); $result->assertSuccess(); // Get raw result to examine the tags $rawResult = $result->getResult(); // Find the prompts in the result $codingPrompt = null; $writingPrompt = null; foreach ($rawResult['prompts'] as $prompt) { if ($prompt['id'] === 'coding-prompt') { $codingPrompt = $prompt; } elseif ($prompt['id'] === 'writing-prompt') { $writingPrompt = $prompt; } } // Assert the tags are present and correct $this->assertNotNull($codingPrompt, 'Coding prompt not found'); $this->assertNotNull($writingPrompt, 'Writing prompt not found'); $this->assertEquals(['coding', 'development', 'programming'], $codingPrompt['tags']); $this->assertEquals(['writing', 'content', 'creativity'], $writingPrompt['tags']); } #[Test] #[DataProvider('commandsProvider')] public function imported_prompts_should_be_compiled(string $command): void { // Create a base prompts file to be imported $basePromptsFile = $this->createTempFile( <<<'YAML' prompts: - id: imported-template type: template description: "Imported template" messages: - role: user content: "This template was imported from another file with {{importedVar}}." - id: imported-prompt description: "Imported standalone prompt" schema: properties: query: description: "User's query" required: - query messages: - role: assistant content: "Imported prompt response for: {{query}}" YAML, '.yaml', ); // Create a main config file that imports the base prompts $mainConfigFile = $this->createTempFile( <<<YAML import: - path: {$this->getRelativePath($basePromptsFile)} type: local prompts: - id: main-prompt description: "Main file prompt" extend: - id: imported-template arguments: importedVar: "successfully imported value" messages: - role: user content: "Additional content from main file." YAML, '.yaml', ); $this ->buildContext( workDir: $this->outputDir, configPath: $mainConfigFile, command: $command, ) ->assertSuccess() ->assertImported(path: $this->getRelativePath($basePromptsFile), type: 'local') ->assertPromptCount(2) // Only prompts, not templates, should be counted ->assertPromptExists('imported-prompt') ->assertPromptExists('main-prompt') ->assertPromptExtends('main-prompt', 'imported-template') ->assertPromptTemplateArguments('main-prompt', 'imported-template', [ 'importedVar' => 'successfully imported value', ]) ->assertPromptMessages('main-prompt', [ 'This template was imported from another file with successfully imported value.', 'Additional content from main file.', ]); } #[Test] #[DataProvider('commandsProvider')] public function imported_prompts_with_filter_should_be_compiled(string $command): void { // Create a prompts file with multiple prompts to be selectively imported $promptsFile = $this->createTempFile( <<<'YAML' prompts: - id: coding-helper description: "Helps with coding tasks" tags: ["coding", "development"] messages: - role: user content: "I need help with coding." - id: design-helper description: "Helps with design tasks" tags: ["design", "ui"] messages: - role: user content: "I need help with design." - id: advanced-coding description: "Advanced coding assistance" tags: ["coding", "advanced"] messages: - role: user content: "I need advanced coding help." YAML, '.yaml', ); // Create a main config that imports with a filter $mainConfigFile = $this->createTempFile( <<<YAML import: - path: {$this->getRelativePath($promptsFile)} type: local filter: tags: include: ["coding"] exclude: ["advanced"] prompts: - id: local-prompt description: "Local prompt" messages: - role: user content: "This is a local prompt." YAML, '.yaml', ); $this ->buildContext( workDir: $this->outputDir, configPath: $mainConfigFile, command: $command, ) ->assertSuccess() ->assertImported(path: $this->getRelativePath($promptsFile), type: 'local') ->assertPromptCount( 2, ) // Local prompt + coding-helper (design-helper and advanced-coding should be filtered out) ->assertPromptExists('coding-helper') ->assertPromptExists('local-prompt') ->assertPrompt('coding-helper', [ 'description' => 'Helps with coding tasks', ]); } #[Test] #[DataProvider('commandsProvider')] public function imported_prompts_with_ids_filter_should_be_compiled(string $command): void { // Create a prompts file with multiple prompts to be selectively imported by ID $promptsFile = $this->createTempFile( <<<'YAML' prompts: - id: prompt-one description: "First prompt" messages: - role: user content: "This is the first prompt." - id: prompt-two description: "Second prompt" messages: - role: user content: "This is the second prompt." - id: prompt-three description: "Third prompt" messages: - role: user content: "This is the third prompt." YAML, '.yaml', ); // Create a main config that imports specific prompts by ID $mainConfigFile = $this->createTempFile( <<<YAML import: - path: {$this->getRelativePath($promptsFile)} type: local filter: ids: ["prompt-one", "prompt-three"] prompts: - id: local-prompt description: "Local prompt" messages: - role: user content: "This is a local prompt." YAML, '.yaml', ); $this ->buildContext( workDir: $this->outputDir, configPath: $mainConfigFile, command: $command, ) ->assertSuccess() ->assertImported(path: $this->getRelativePath($promptsFile), type: 'local') ->assertPromptCount(3) // Local prompt + prompt-one + prompt-three ->assertPromptExists('prompt-one') ->assertPromptExists('prompt-three') ->assertPromptExists('local-prompt'); } #[Test] #[DataProvider('commandsProvider')] public function invalid_prompt_should_report_error(string $command): void { // Create a configuration with an invalid prompt (missing required fields) $configFile = $this->createTempFile( <<<'YAML' prompts: - id: invalid-prompt # Missing description # Missing messages YAML, '.yaml', ); // Execute command which should report an error but not fail the process $result = $this->buildContext( workDir: $this->outputDir, configPath: $configFile, command: $command, ); // At minimum, the prompt count should be 0 $result->assertPromptCount(0); } #[\Override] protected function setUp(): void { parent::setUp(); $this->outputDir = $this->createTempDir(); } protected function buildContext( string $workDir, ?string $configPath = null, ?string $inlineJson = null, ?string $envFile = null, string $command = 'generate', bool $asJson = true, ): CompilingResult { return (new ContextBuilder($this->getConsole()))->build( workDir: $workDir, configPath: $configPath, inlineJson: $inlineJson, envFile: $envFile, command: $command, asJson: $asJson, ); } private function getRelativePath(string $absolutePath): string { // Convert absolute path to relative path for use in YAML configurations // This ensures the test is independent of the absolute paths on the test system return \basename($absolutePath); } }

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/context-hub/generator'

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