package languages import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zzet/gortex/internal/graph" ) func TestPHPExtractor_Class(t *testing.T) { src := []byte(`db = $db; } public function findUser($id) { return $this->db->query($id); } } `) e := NewPHPExtractor() result, err := e.Extract("service.php", src) require.NoError(t, err) types := nodesOfKind(result.Nodes, graph.KindType) assert.GreaterOrEqual(t, len(types), 1) methods := nodesOfKind(result.Nodes, graph.KindMethod) assert.GreaterOrEqual(t, len(methods), 1) } func TestPHPExtractor_ScopeInterfaces(t *testing.T) { src := []byte(`db->query($id); } } `) e := NewPHPExtractor() result, err := e.Extract("service.php", src) require.NoError(t, err) methods := nodesOfKind(result.Nodes, graph.KindMethod) require.GreaterOrEqual(t, len(methods), 1) memberEdges := edgesOfKind(result.Edges, graph.EdgeMemberOf) require.GreaterOrEqual(t, len(memberEdges), 1) for _, e := range memberEdges { assert.Equal(t, "service.php::UserService", e.To) } } func TestPHPExtractor_Namespace(t *testing.T) { src := []byte(`helper(); doSomething(); } } `) e := NewPHPExtractor() result, err := e.Extract("svc.php", src) require.NoError(t, err) calls := edgesOfKind(result.Edges, graph.EdgeCalls) assert.GreaterOrEqual(t, len(calls), 1) } func TestPHPExtractor_LaravelControllerMiddleware(t *testing.T) { // $this->middleware(X::class) in the controller ctor binds X.handle // as a dispatch edge on every action. ->only([...]) restricts. src := []byte(`middleware(AuthMiddleware::class); $this->middleware(AdminMiddleware::class)->only(['destroy']); } public function index() {} public function show() {} public function destroy() {} } `) e := NewPHPExtractor() result, err := e.Extract("c.php", src) require.NoError(t, err) auth := map[string]bool{} admin := map[string]bool{} for _, ed := range edgesOfKind(result.Edges, graph.EdgeCalls) { if ed.Meta == nil { continue } if v, _ := ed.Meta["laravel_middleware"].(string); v == "AuthMiddleware" { auth[ed.From] = true } if v, _ := ed.Meta["laravel_middleware"].(string); v == "AdminMiddleware" { admin[ed.From] = true } } assert.Len(t, auth, 3, "AuthMiddleware should bind to every action") assert.Len(t, admin, 1, "AdminMiddleware should bind only to :destroy") assert.Contains(t, admin, "c.php::UserController.destroy") } func TestPHPExtractor_LaravelServiceProviderBindings(t *testing.T) { // $this->app->bind(Interface, Impl) emits two EdgeProvides edges: // one useClass-style (to Impl with provides_for=Interface) and one // token-style (to Interface) so find_usages on the interface // surfaces the provider. src := []byte(`app->bind(Foo::class, FooImpl::class); $this->app->singleton(Bar::class, function($app){ return new Bar(); }); } } `) e := NewPHPExtractor() result, err := e.Extract("p.php", src) require.NoError(t, err) var fooUseClass, fooProvider bool var barProvider bool for _, ed := range edgesOfKind(result.Edges, graph.EdgeProvides) { if ed.Meta == nil { continue } binding, _ := ed.Meta["binding"].(string) if binding == "useClass" && ed.Meta["provides_for"] == "Foo" { fooUseClass = true } if binding == "bind" && ed.To == "unresolved::Foo" { fooProvider = true } if binding == "singleton" && ed.To == "unresolved::Bar" { barProvider = true } } assert.True(t, fooUseClass, "bind should emit useClass edge to impl") assert.True(t, fooProvider, "bind should emit provider edge to interface") assert.True(t, barProvider, "singleton with factory should emit provider edge to token") } func TestPHPExtractor_SymfonyAsEventListener(t *testing.T) { // #[AsEventListener(event: X::class)] on a method emits an // EdgeConsumes from the method to X so find_usages(X) returns // the listener. Class-level form also supported. src := []byte(`