Skip to content

Commit 96fe621

Browse files
committed
Add feature tests for file source
1 parent 825af46 commit 96fe621

14 files changed

+436
-46
lines changed

src/Document/DocumentRegistry.php

+21-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* @implements RegistryInterface<Document>
1111
*/
12-
final class DocumentRegistry implements RegistryInterface
12+
final class DocumentRegistry implements RegistryInterface, \ArrayAccess
1313
{
1414
public function __construct(
1515
/** @var array<Document> */
@@ -45,4 +45,24 @@ public function getIterator(): \Traversable
4545
{
4646
return new \ArrayIterator($this->getItems());
4747
}
48+
49+
public function offsetExists(mixed $offset): bool
50+
{
51+
return \array_key_exists($offset, $this->documents);
52+
}
53+
54+
public function offsetGet(mixed $offset): mixed
55+
{
56+
return $this->documents[$offset] ?? null;
57+
}
58+
59+
public function offsetSet(mixed $offset, mixed $value): void
60+
{
61+
throw new \BadMethodCallException('Cannot set value directly. Use register() method.');
62+
}
63+
64+
public function offsetUnset(mixed $offset): void
65+
{
66+
throw new \BadMethodCallException('Cannot unset value directly.');
67+
}
4868
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
$schema: 'https://raw.githubusercontent.com/context-hub/generator/refs/heads/main/json-schema.json'
2+
3+
documents:
4+
- description: File Source Test Document
5+
outputPath: file_source_test.md
6+
overwrite: true
7+
sources:
8+
- type: file
9+
description: Basic file source test
10+
sourcePaths: test_files
11+
filePattern: '*.txt'
12+
treeView:
13+
enabled: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
$schema: 'https://raw.githubusercontent.com/context-hub/generator/refs/heads/main/json-schema.json'
2+
3+
documents:
4+
- description: Filtered File Source Test
5+
outputPath: filtered_file_source_test.md
6+
overwrite: true
7+
sources:
8+
- type: file
9+
description: File source with filters
10+
sourcePaths: test_files
11+
filePattern: '*.txt'
12+
path: 'test_directory'
13+
notPath: ['**/unwanted*.txt']
14+
contains: 'nested file'
15+
treeView:
16+
enabled: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
$schema: 'https://raw.githubusercontent.com/context-hub/generator/refs/heads/main/json-schema.json'
2+
3+
documents:
4+
- description: Invalid File Source Test
5+
outputPath: invalid_file_source_test.md
6+
overwrite: true
7+
sources:
8+
- type: file
9+
description: File source with invalid paths
10+
sourcePaths: non_existent_directory
11+
filePattern: '*.txt'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is nested file content for testing directory traversal.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is test content for the file source test.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
$schema: 'https://raw.githubusercontent.com/context-hub/generator/refs/heads/main/json-schema.json'
2+
3+
documents:
4+
- description: Tree View File Source Test
5+
outputPath: tree_view_file_source_test.md
6+
overwrite: true
7+
sources:
8+
- type: file
9+
description: File source with custom tree view options
10+
sourcePaths: test_files
11+
filePattern: '*.txt'
12+
treeView:
13+
enabled: true
14+
showSize: true
15+
showLastModified: true
16+
showCharCount: true
17+
includeFiles: true
18+
maxDepth: 2

tests/src/Feature/Compiler/AbstractCompilerTestCase.php

+3-45
Original file line numberDiff line numberDiff line change
@@ -4,59 +4,17 @@
44

55
namespace Tests\Feature\Compiler;
66

7-
use Butschster\ContextGenerator\Application\AppScope;
8-
use Butschster\ContextGenerator\Application\FSPath;
9-
use Butschster\ContextGenerator\Config\ConfigurationProvider;
107
use Butschster\ContextGenerator\Config\Registry\ConfigRegistryAccessor;
118
use Butschster\ContextGenerator\Document\Compiler\DocumentCompiler;
12-
use PHPUnit\Framework\Attributes\Test;
13-
use Spiral\Core\Scope;
14-
use Spiral\Files\Files;
15-
use Tests\AppTestCase;
9+
use Tests\Feature\FeatureTestCases;
1610

17-
abstract class AbstractCompilerTestCase extends AppTestCase
11+
abstract class AbstractCompilerTestCase extends FeatureTestCases
1812
{
19-
#[\Override]
20-
public function rootDirectory(): string
13+
protected function assertConfigItems(DocumentCompiler $compiler, ConfigRegistryAccessor $config): void
2114
{
22-
return $this->getFixturesDir('Compiler');
23-
}
24-
25-
#[Test]
26-
public function compile(): void
27-
{
28-
$this->getContainer()->runScope(
29-
bindings: new Scope(
30-
name: AppScope::Compiler,
31-
),
32-
scope: $this->compileDocuments(...),
33-
);
34-
}
35-
36-
#[\Override]
37-
protected function tearDown(): void
38-
{
39-
parent::tearDown();
40-
41-
$files = new Files();
42-
$files->deleteDirectory($this->getContextsDir());
43-
}
44-
45-
protected function getContextsDir(string $path = ''): string
46-
{
47-
return (string) FSPath::create($this->getFixturesDir('Compiler/.context'))->join($path);
48-
}
49-
50-
abstract protected function getConfigPath(): string;
51-
52-
private function compileDocuments(DocumentCompiler $compiler, ConfigurationProvider $configProvider): void
53-
{
54-
$loader = $configProvider->fromPath($this->getConfigPath());
55-
5615
$outputPaths = [];
5716
$results = [];
5817

59-
$config = new ConfigRegistryAccessor($loader->load());
6018
foreach ($config->getDocuments() as $document) {
6119
$outputPaths[] = $this->getContextsDir($document->outputPath);
6220
$compiledDocument = $compiler->compile($document);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Feature\Source\File;
6+
7+
use Butschster\ContextGenerator\Config\Registry\ConfigRegistryAccessor;
8+
use Butschster\ContextGenerator\Source\File\FileSource;
9+
use Butschster\ContextGenerator\Source\SourceInterface;
10+
use Tests\Feature\FeatureTestCases;
11+
12+
/**
13+
* Base class for file source tests providing common functionality
14+
*/
15+
abstract class AbstractFileSourceTestCase extends FeatureTestCases
16+
{
17+
#[\Override]
18+
public function rootDirectory(): string
19+
{
20+
return $this->getFixturesDir('Source/File');
21+
}
22+
23+
/**
24+
* Gets the first file source from the first document
25+
*/
26+
protected function getFileSourceFromConfig(ConfigRegistryAccessor $config): FileSource
27+
{
28+
$documents = $config->getDocuments();
29+
$this->assertCount(1, $documents, 'Should have exactly 1 document');
30+
31+
$document = $documents[0];
32+
$sources = $document->getSources();
33+
$this->assertCount(1, $sources, 'Document should have exactly 1 source');
34+
35+
$source = $sources[0];
36+
$this->assertInstanceOf(FileSource::class, $source, 'Source should be a FileSource instance');
37+
38+
return $source;
39+
}
40+
41+
/**
42+
* Gets the type of a source from its JSON serialization
43+
*
44+
* @param SourceInterface $source The source to get the type from
45+
* @return string The source type
46+
*/
47+
protected function getSourceType(SourceInterface $source): string
48+
{
49+
return \json_decode(\json_encode($source), true)['type'] ?? '';
50+
}
51+
52+
/**
53+
* Creates a test file structure for tests requiring file operations
54+
*/
55+
protected function createTestFileStructure(): string
56+
{
57+
$tempDir = $this->createTempDir();
58+
59+
// Create a basic structure
60+
$rootFile = $tempDir . '/root_file.txt';
61+
\file_put_contents($rootFile, 'Root file content');
62+
63+
$subDir = $tempDir . '/subdir';
64+
\mkdir($subDir, 0777, true);
65+
66+
$nestedFile = $subDir . '/nested_file.txt';
67+
\file_put_contents($nestedFile, 'Nested file content');
68+
69+
$deepDir = $subDir . '/deep';
70+
\mkdir($deepDir, 0777, true);
71+
72+
$deepFile = $deepDir . '/deep_file.txt';
73+
\file_put_contents($deepFile, 'Deep nested file content');
74+
75+
return $tempDir;
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Feature\Source\File;
6+
7+
use Butschster\ContextGenerator\Config\Registry\ConfigRegistryAccessor;
8+
use Butschster\ContextGenerator\Document\Compiler\DocumentCompiler;
9+
use PHPUnit\Framework\Attributes\Group;
10+
11+
/**
12+
* Tests the filtering capabilities of the FileSource component.
13+
*/
14+
#[Group('source')]
15+
#[Group('file-source')]
16+
final class FileSourceFilterTest extends AbstractFileSourceTestCase
17+
{
18+
#[\Override]
19+
protected function getConfigPath(): string
20+
{
21+
return $this->getFixturesDir('Source/File/filtered_file_source.yaml');
22+
}
23+
24+
protected function assertConfigItems(DocumentCompiler $compiler, ConfigRegistryAccessor $config): void
25+
{
26+
$documents = $config->getDocuments();
27+
$document = $documents[0];
28+
$this->assertEquals('Filtered File Source Test', $document->description);
29+
$source = $this->getFileSourceFromConfig($config);
30+
31+
// Verify filter properties
32+
$this->assertEquals('test_directory', $source->path);
33+
$this->assertEquals(['**/unwanted*.txt'], $source->notPath);
34+
$this->assertEquals('nested file', $source->contains);
35+
36+
// Compile and verify content filtering worked
37+
$compiledDocument = $compiler->compile($document);
38+
$content = (string) $compiledDocument->content;
39+
40+
// Should include nested file but not the root test_file.txt
41+
$this->assertStringContainsString('nested_file.txt', $content);
42+
$this->assertStringContainsString('nested file content', $content);
43+
$this->assertStringNotContainsString('test_file.txt', $content, 'Root test_file.txt should be filtered out');
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Feature\Source\File;
6+
7+
use Butschster\ContextGenerator\Config\Registry\ConfigRegistryAccessor;
8+
use Butschster\ContextGenerator\Document\Compiler\DocumentCompiler;
9+
use Butschster\ContextGenerator\Lib\TreeBuilder\TreeViewConfig;
10+
use Butschster\ContextGenerator\Source\File\FileSource;
11+
use PHPUnit\Framework\Attributes\Group;
12+
13+
/**
14+
* Tests JSON serialization functionality of the FileSource component.
15+
*/
16+
#[Group('source')]
17+
#[Group('file-source')]
18+
final class FileSourceSerializationTest extends AbstractFileSourceTestCase
19+
{
20+
protected function getConfigPath(): string
21+
{
22+
return $this->getFixturesDir('Source/File/basic_file_source.yaml');
23+
}
24+
25+
protected function assertConfigItems(DocumentCompiler $compiler, ConfigRegistryAccessor $config): void
26+
{
27+
$source = $this->getFileSourceFromConfig($config);
28+
29+
// Test JSON serialization
30+
$serialized = \json_encode($source);
31+
$this->assertNotFalse($serialized, 'JSON serialization failed');
32+
33+
$decoded = \json_decode($serialized, true);
34+
$this->assertIsArray($decoded);
35+
36+
// Check required fields
37+
$this->assertArrayHasKey('type', $decoded);
38+
$this->assertEquals('file', $decoded['type']);
39+
40+
$this->assertArrayHasKey('sourcePaths', $decoded);
41+
$this->assertArrayHasKey('filePattern', $decoded);
42+
$this->assertArrayHasKey('treeView', $decoded);
43+
44+
// Create a new FileSource with minimal parameters
45+
$minimalSource = new FileSource(
46+
sourcePaths: '/test/path',
47+
description: 'Minimal source',
48+
);
49+
50+
$minimalSerialized = \json_encode($minimalSource);
51+
$this->assertNotFalse($minimalSerialized, 'Minimal source JSON serialization failed');
52+
53+
$minimalDecoded = \json_decode($minimalSerialized, true);
54+
$this->assertIsArray($minimalDecoded);
55+
56+
// Check that optional fields are not included when empty
57+
$this->assertArrayNotHasKey('path', $minimalDecoded);
58+
$this->assertArrayNotHasKey('contains', $minimalDecoded);
59+
$this->assertArrayNotHasKey('notContains', $minimalDecoded);
60+
$this->assertArrayNotHasKey('size', $minimalDecoded);
61+
$this->assertArrayNotHasKey('date', $minimalDecoded);
62+
63+
// Test source with all options
64+
$fullSource = new FileSource(
65+
sourcePaths: '/test/path',
66+
description: 'Full source',
67+
filePattern: '*.php',
68+
notPath: ['*/vendor/*'],
69+
path: 'src/Controller',
70+
contains: 'namespace',
71+
notContains: 'private',
72+
size: '> 1K',
73+
date: 'since yesterday',
74+
ignoreUnreadableDirs: true,
75+
treeView: new TreeViewConfig(
76+
enabled: true,
77+
showSize: true,
78+
showLastModified: true,
79+
),
80+
maxFiles: 10,
81+
);
82+
83+
$fullSerialized = \json_encode($fullSource);
84+
$this->assertNotFalse($fullSerialized, 'Full source JSON serialization failed');
85+
86+
$fullDecoded = \json_decode($fullSerialized, true);
87+
$this->assertIsArray($fullDecoded);
88+
89+
// Check that all options are serialized
90+
$this->assertArrayHasKey('path', $fullDecoded);
91+
$this->assertArrayHasKey('contains', $fullDecoded);
92+
$this->assertArrayHasKey('notContains', $fullDecoded);
93+
$this->assertArrayHasKey('size', $fullDecoded);
94+
$this->assertArrayHasKey('date', $fullDecoded);
95+
$this->assertArrayHasKey('ignoreUnreadableDirs', $fullDecoded);
96+
$this->assertArrayHasKey('maxFiles', $fullDecoded);
97+
$this->assertEquals(10, $fullDecoded['maxFiles']);
98+
}
99+
}

0 commit comments

Comments
 (0)