Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

289 lines
7.6 KiB
Go

package languages
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
func TestRbExtractor_Class(t *testing.T) {
src := []byte(`class UserService
def initialize(db)
@db = db
end
def find_user(id)
@db.query(id)
end
end
`)
e := NewRubyExtractor()
result, err := e.Extract("service.rb", src)
require.NoError(t, err)
types := nodesOfKind(result.Nodes, graph.KindType)
require.Len(t, types, 1)
assert.Equal(t, "UserService", types[0].Name)
methods := nodesOfKind(result.Nodes, graph.KindMethod)
assert.Len(t, methods, 2)
memberEdges := edgesOfKind(result.Edges, graph.EdgeMemberOf)
assert.Len(t, memberEdges, 2)
}
func TestRbExtractor_TopLevelMethod(t *testing.T) {
src := []byte(`def greet(name)
puts "Hello #{name}"
end
`)
e := NewRubyExtractor()
result, err := e.Extract("app.rb", src)
require.NoError(t, err)
funcs := nodesOfKind(result.Nodes, graph.KindFunction)
require.Len(t, funcs, 1)
assert.Equal(t, "greet", funcs[0].Name)
}
func TestRbExtractor_Require(t *testing.T) {
src := []byte(`require "json"
require_relative "helper"
`)
e := NewRubyExtractor()
result, err := e.Extract("app.rb", src)
require.NoError(t, err)
imports := edgesOfKind(result.Edges, graph.EdgeImports)
assert.GreaterOrEqual(t, len(imports), 1)
}
func TestRbExtractor_Module(t *testing.T) {
src := []byte(`module Authentication
def self.verify(token)
true
end
end
`)
e := NewRubyExtractor()
result, err := e.Extract("auth.rb", src)
require.NoError(t, err)
pkgs := nodesOfKind(result.Nodes, graph.KindPackage)
require.Len(t, pkgs, 1)
assert.Equal(t, "Authentication", pkgs[0].Name)
}
func TestRbExtractor_Constants(t *testing.T) {
src := []byte(`MAX_RETRIES = 3
DEFAULT_HOST = "localhost"
`)
e := NewRubyExtractor()
result, err := e.Extract("config.rb", src)
require.NoError(t, err)
consts := nodesOfKind(result.Nodes, graph.KindConstant)
require.Len(t, consts, 2)
names := []string{consts[0].Name, consts[1].Name}
assert.Contains(t, names, "MAX_RETRIES")
assert.Contains(t, names, "DEFAULT_HOST")
}
func TestRbExtractor_CallSites(t *testing.T) {
src := []byte(`class Greeter
def greet(name)
puts("Hello")
logger.info("greeting")
end
end
`)
e := NewRubyExtractor()
result, err := e.Extract("greeter.rb", src)
require.NoError(t, err)
calls := edgesOfKind(result.Edges, graph.EdgeCalls)
assert.GreaterOrEqual(t, len(calls), 1)
}
func TestRbExtractor_NoTopLevelMethodInClass(t *testing.T) {
// Methods inside a class should NOT appear as top-level functions.
src := []byte(`class Foo
def bar
end
end
def baz
end
`)
e := NewRubyExtractor()
result, err := e.Extract("test.rb", src)
require.NoError(t, err)
funcs := nodesOfKind(result.Nodes, graph.KindFunction)
require.Len(t, funcs, 1)
assert.Equal(t, "baz", funcs[0].Name)
methods := nodesOfKind(result.Nodes, graph.KindMethod)
require.Len(t, methods, 1)
assert.Equal(t, "bar", methods[0].Name)
}
func TestRubyExtractor_RailsBeforeAction(t *testing.T) {
// before_action :name binds a callback method to the class's
// action methods. With only: filters, only the listed actions
// fire the callback. This test covers the default (all actions
// bound) plus an only: list.
src := []byte(`
class UsersController
before_action :authenticate
before_action :load_user, only: [:show, :update]
def index; end
def show; @user end
def update; @user end
private
def authenticate; end
def load_user; end
end
`)
e := NewRubyExtractor()
result, err := e.Extract("users_controller.rb", src)
require.NoError(t, err)
var authEdges, loadEdges int
actionsForAuth := map[string]bool{}
actionsForLoad := map[string]bool{}
for _, ed := range edgesOfKind(result.Edges, graph.EdgeCalls) {
if ed.Meta == nil {
continue
}
cb, _ := ed.Meta["rails_callback"].(string)
if cb == "authenticate" {
authEdges++
actionsForAuth[ed.From] = true
}
if cb == "load_user" {
loadEdges++
actionsForLoad[ed.From] = true
}
}
// authenticate guards every action (no only:/except:).
assert.Equal(t, 3, authEdges, "authenticate should bind to every action")
// load_user has only: [:show, :update].
assert.Equal(t, 2, loadEdges, "load_user should bind only to :show, :update")
assert.Contains(t, actionsForLoad, "users_controller.rb::UsersController.show")
assert.Contains(t, actionsForLoad, "users_controller.rb::UsersController.update")
assert.NotContains(t, actionsForLoad, "users_controller.rb::UsersController.index")
}
func TestRubyExtractor_SingletonMethod(t *testing.T) {
// `def self.x` (singleton_method) must be extracted alongside
// regular instance methods — previously the extractor missed them.
src := []byte(`
class User
def instance; 1 end
def self.class_method; 2 end
end
`)
e := NewRubyExtractor()
result, err := e.Extract("user.rb", src)
require.NoError(t, err)
names := make(map[string]bool)
for _, n := range result.Nodes {
if n.Kind == graph.KindMethod {
names[n.Name] = true
}
}
assert.True(t, names["instance"])
assert.True(t, names["class_method"])
}
func TestRubyExtractor_DocAndVisibility(t *testing.T) {
src := []byte(`# Greeter handles welcomes.
class Greeter
# Says hi to the user.
def hello
end
def silent
end
end
`)
e := NewRubyExtractor()
result, err := e.Extract("greeter.rb", src)
require.NoError(t, err)
byID := map[string]*graph.Node{}
for _, n := range result.Nodes {
byID[n.ID] = n
}
greeter := byID["greeter.rb::Greeter"]
require.NotNil(t, greeter)
if greeter.Meta["visibility"] != "public" {
t.Fatalf("Greeter.vis = %q", greeter.Meta["visibility"])
}
if greeter.Meta["doc"] != "Greeter handles welcomes." {
t.Fatalf("Greeter.doc = %q", greeter.Meta["doc"])
}
hello := byID["greeter.rb::Greeter.hello"]
require.NotNil(t, hello)
if hello.Meta["doc"] != "Says hi to the user." {
t.Fatalf("hello.doc = %q", hello.Meta["doc"])
}
if hello.Meta["visibility"] != "public" {
t.Fatalf("hello.vis = %q", hello.Meta["visibility"])
}
}
// TestRubyExtractor_MixinsBareCallsVisibility is the C5 test: include/extend/
// prepend produce module-composition edges, a bare receiver-less call becomes a
// call edge, and private/protected section markers (and the targeted form) set
// method visibility.
func TestRubyExtractor_MixinsBareCallsVisibility(t *testing.T) {
src := []byte("class Foo\n" +
" include Comparable\n" +
" prepend Loggable\n" +
" def pub; helper; end\n" +
" private\n" +
" def sec; end\n" +
" public\n" +
" def pub2; end\n" +
" private :pub2\n" +
"end\n")
res, err := NewRubyExtractor().Extract("foo.rb", src)
require.NoError(t, err)
vis := map[string]string{}
for _, n := range res.Nodes {
if n.Kind == graph.KindMethod {
v, _ := n.Meta["visibility"].(string)
vis[n.Name] = v
}
}
assert.Equal(t, "public", vis["pub"], "pub before any marker is public")
assert.Equal(t, "private", vis["sec"], "sec after `private` is private")
assert.Equal(t, "private", vis["pub2"], "pub2 flipped private by the targeted `private :pub2`")
mixins := map[string]string{}
var sawBareCall bool
for _, e := range res.Edges {
if e.Kind == graph.EdgeExtends && e.From == "foo.rb::Foo" && e.Meta != nil {
via, _ := e.Meta["via"].(string)
mixins[e.To] = via
}
if e.Kind == graph.EdgeCalls && e.From == "foo.rb::Foo.pub" && e.To == "unresolved::helper" {
sawBareCall = true
}
}
assert.Equal(t, "include", mixins["unresolved::Comparable"], "include composes Comparable")
assert.Equal(t, "prepend", mixins["unresolved::Loggable"], "prepend composes Loggable")
assert.True(t, sawBareCall, "a bare `helper` call should produce a call edge")
}