Skip to content

Commit b521613

Browse files
committed
test: logic namespace tests
1 parent 6203af1 commit b521613

File tree

5 files changed

+75
-80
lines changed

5 files changed

+75
-80
lines changed

src/Logic/AppAutoloader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
namespace Gt\WebEngine\Logic;
2+
namespace GT\WebEngine\Logic;
33

44
/**
55
* This autoloader is only necessary if Composer's PSR-4 autoloader isn't set up

src/Logic/LogicExecutor.php

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function invoke(Assembly $logicAssembly, string $name, array $extraArgs =
2424
// TODO: Why convert to array?
2525
foreach(iterator_to_array($logicAssembly) as $file) {
2626
$nsProject = (string)(new LogicProjectNamespace(
27-
$file,
27+
$this->relativePath($file),
2828
$this->appNamespace
2929
));
3030

@@ -56,8 +56,7 @@ public function invoke(Assembly $logicAssembly, string $name, array $extraArgs =
5656

5757
foreach($fnReferenceArray as $fnReference) {
5858
if(function_exists($fnReference)) {
59-
$closure = $fnReference(...);
60-
$refFunction = new ReflectionFunction($closure);
59+
$refFunction = new ReflectionFunction($fnReference);
6160
foreach($refFunction->getAttributes() as $refAttr) {
6261
$functionReference .= "#";
6362
$functionReference .= $refAttr->getName();
@@ -91,7 +90,40 @@ public function invoke(Assembly $logicAssembly, string $name, array $extraArgs =
9190
}
9291

9392
private function loadLogicFile(string $file):void {
93+
// If the target file already declares a namespace, load it directly.
94+
// The LogicStreamWrapper injects a namespace for classless scripts, but
95+
// passing an already-namespaced file through the wrapper can cause an
96+
// extra namespace to be injected which leads to syntax errors when
97+
// executing tests in isolation.
98+
if($this->fileHasNamespace($file)) {
99+
require_once($file);
100+
return;
101+
}
102+
94103
$streamPath = LogicStreamWrapper::STREAM_NAME . "://$file";
95104
require_once($streamPath);
96105
}
106+
107+
private function fileHasNamespace(string $file):bool {
108+
$fh = fopen($file, "r");
109+
$maxLines = 50;
110+
$read = "";
111+
for($i = 0; $i < $maxLines && !feof($fh); $i++) {
112+
$read .= fgets($fh);
113+
}
114+
fclose($fh);
115+
return (bool)preg_match('/^\s*namespace\s+[^;]+;/m', $read);
116+
}
117+
118+
private function relativePath(string $path):string {
119+
if(!str_starts_with($path, "/") && !preg_match('/^[A-Za-z]:[\\\\\/]/', $path)) {
120+
return $path;
121+
}
122+
$cwd = rtrim(getcwd(), "/");
123+
$real = realpath($path) ?: $path;
124+
if(str_starts_with($real, $cwd . "/")) {
125+
$path = ltrim(substr($real, strlen($cwd) + 1), "/");
126+
}
127+
return $path;
128+
}
97129
}

src/Logic/LogicProjectNamespace.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
namespace Gt\WebEngine\Logic;
2+
namespace GT\WebEngine\Logic;
33

44
use Stringable;
55

src/Logic/LogicStreamHandler.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
namespace GT\WebEngine\Logic;
33

44
use Closure;
5-
use Gt\Routing\LogicStream\LogicStreamWrapper;
65

76
/**
87
* Handles the registration of a custom stream wrapper, as defined by the
@@ -13,8 +12,8 @@ class LogicStreamHandler {
1312
private Closure $streamWrapperRegisterCallback;
1413

1514
public function __construct(
16-
private readonly string $streamName = LogicStreamWrapper::STREAM_NAME,
17-
private readonly string $logicStreamClassName = LogicStreamWrapper::class,
15+
private readonly string $streamName = WebEngineLogicStreamWrapper::STREAM_NAME,
16+
private readonly string $logicStreamClassName = WebEngineLogicStreamWrapper::class,
1817
?Closure $streamWrapperRegisterCallback = null,
1918
) {
2019
$this->streamWrapperRegisterCallback = $streamWrapperRegisterCallback ??

test/phpunit/Logic/AppAutoloaderTest.php

Lines changed: 36 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,98 +2,62 @@
22
namespace GT\WebEngine\Test\Logic;
33

44
use GT\WebEngine\Logic\AppAutoloader;
5-
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
65
use PHPUnit\Framework\TestCase;
76

87
class AppAutoloaderTest extends TestCase {
9-
private string $classDir;
8+
private string $cwd;
9+
private string $relBaseDir;
1010

1111
protected function setUp():void {
12-
// Use system temporary directory to avoid writing into the project tree.
13-
$sysTmp = rtrim(sys_get_temp_dir(), "/\\");
14-
$this->classDir = $sysTmp . "/phpgt-webengine-test--Logic-AppAutoloader-" . uniqid();
15-
@mkdir($this->classDir, recursive: true);
12+
parent::setUp();
13+
$this->cwd = getcwd();
14+
$this->relBaseDir = "test-autoload-" . uniqid();
15+
mkdir($this->relBaseDir . "/Foo", 0777, true);
1616
}
1717

1818
protected function tearDown():void {
19-
// Recursively remove the temporary directory we created.
20-
$path = $this->classDir;
21-
if(is_dir($path)) {
22-
self::rrmdir($path);
19+
// Recursively remove created files/dirs
20+
$it = new \RecursiveIteratorIterator(
21+
new \RecursiveDirectoryIterator($this->relBaseDir, \FilesystemIterator::SKIP_DOTS),
22+
\RecursiveIteratorIterator::CHILD_FIRST
23+
);
24+
foreach($it as $file) {
25+
$file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
2326
}
27+
@rmdir($this->relBaseDir);
28+
parent::tearDown();
2429
}
2530

26-
private static function rrmdir(string $dir):void {
27-
/** @var list<string> $items */
28-
$items = @scandir($dir) ?: [];
29-
foreach($items as $item) {
30-
if($item === "." || $item === "..") {
31-
continue;
32-
}
33-
$full = $dir . "/" . $item;
34-
if(is_dir($full)) {
35-
self::rrmdir($full);
36-
}
37-
else {
38-
@unlink($full);
39-
}
40-
}
41-
@rmdir($dir);
31+
public function testSetup_noDir_doesNotRegisterOrLoad():void {
32+
$sut = new AppAutoloader('App', 'non-existent-dir-' . uniqid());
33+
// Should not throw, and autoloader should not attempt to load anything
34+
$sut->setup();
35+
self::assertFalse(class_exists('App\\Nope\\Thing'));
4236
}
4337

44-
#[RunInSeparateProcess]
45-
public function testLoadsClassFromConfiguredDirectory():void {
46-
// Arrange: create a simple class file under the configured directory.
47-
$ns = 'TestApp';
48-
$cls = 'Widget';
49-
$code = <<<PHP
50-
<?php
51-
namespace {$ns};
52-
class {$cls} {
53-
public function ping(): string { return 'pong'; }
54-
}
55-
PHP;
38+
public function testAutoload_relativePathAndUcfirstSegments():void {
39+
// Create class file using ucfirst segment mapping: Foo/Bar.php
40+
$code = "<?php\nnamespace App\\Foo; class Bar { public static function hello(): string { return 'hi'; } }\n";
41+
file_put_contents($this->relBaseDir . "/Foo/Bar.php", $code);
5642

57-
$filePath = "{$this->classDir}/{$cls}.php";
58-
file_put_contents($filePath, $code);
59-
60-
$sut = new AppAutoloader(namespace: $ns, classDir: $this->classDir);
43+
$sut = new AppAutoloader('App', $this->relBaseDir);
6144
$sut->setup();
6245

63-
// Act: reference the class so the autoloader resolves it.
64-
$fullClass = "\\{$ns}\\{$cls}";
65-
$instance = new $fullClass();
66-
67-
// Assert
68-
self::assertSame('pong', $instance->ping());
46+
$class = 'App\\Foo\\Bar';
47+
self::assertTrue(class_exists($class));
48+
self::assertSame('hi', \call_user_func([$class, 'hello']));
6949
}
7050

71-
#[RunInSeparateProcess]
72-
public function testLoadsClassFromNestedNamespaceUsingUcfirstPath():void {
73-
// Arrange: nested namespace parts should map to capitalised path segments.
74-
$ns = 'TestApp2';
75-
$nsParts = ['foo', 'bar'];
76-
$class = 'baz';
51+
public function testAutoload_ignoresDifferentNamespace():void {
52+
// Create class that shouldn't be autoloaded by our AppAutoloader because of namespace mismatch
53+
$otherDir = $this->relBaseDir . '/Other';
54+
mkdir($otherDir, 0777, true);
55+
file_put_contents($otherDir . '/Qux.php', "<?php\nnamespace Other\\Ns; class Qux {}\n");
7756

78-
// The autoloader ucfirst()s each part, so we create directories capitalised.
79-
$dir = "{$this->classDir}/" . implode('/', array_map('ucfirst', $nsParts));
80-
@mkdir($dir, recursive: true);
81-
82-
$code = <<<PHP
83-
<?php
84-
namespace {$ns}\\foo\\bar;
85-
class {$class} { public function id(): string { return 'ok'; } }
86-
PHP;
87-
88-
file_put_contents($dir . '/Baz.php', $code);
89-
90-
$sut = new AppAutoloader(namespace: $ns, classDir: $this->classDir);
57+
$sut = new AppAutoloader('App', $this->relBaseDir);
9158
$sut->setup();
9259

93-
// Use lowercased parts to confirm ucfirst mapping works.
94-
$fullClass = "\\{$ns}\\{$nsParts[0]}\\{$nsParts[1]}\\$class";
95-
$instance = new $fullClass();
96-
97-
self::assertSame('ok', $instance->id());
60+
// Asserting that merely referencing a different namespace doesn't cause load attempt here.
61+
self::assertFalse(class_exists('Other\\Ns\\Qux', false));
9862
}
9963
}

0 commit comments

Comments
 (0)