Files
git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.CallGraph.Tests/PythonCallGraphExtractorTests.cs

199 lines
5.6 KiB
C#

// -----------------------------------------------------------------------------
// PythonCallGraphExtractorTests.cs
// Sprint: SPRINT_3610_0004_0001_python_callgraph
// Description: Unit tests for Python call graph extraction.
// -----------------------------------------------------------------------------
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Scanner.CallGraph.Python;
using StellaOps.Scanner.Reachability;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.CallGraph.Tests;
public class PythonCallGraphExtractorTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PythonEntrypointClassifier_ClassifiesFlaskRoute()
{
// Arrange
var classifier = new PythonEntrypointClassifier();
var node = new CallGraphNode(
NodeId: "py:myapp/views.get_users",
Symbol: "get_users",
File: "views.py",
Line: 10,
Package: "myapp",
Visibility: Visibility.Public,
IsEntrypoint: false,
EntrypointType: null,
IsSink: false,
SinkCategory: null);
var decorators = new[] { "@app.route('/users')", "@login_required" };
// Act
var result = classifier.Classify(node, decorators);
// Assert
Assert.True(result.HasValue);
Assert.Equal(EntrypointType.HttpHandler, result.Value);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PythonEntrypointClassifier_ClassifiesFastApiRoute()
{
// Arrange
var classifier = new PythonEntrypointClassifier();
var node = new CallGraphNode(
NodeId: "py:api/endpoints.create_user",
Symbol: "create_user",
File: "endpoints.py",
Line: 25,
Package: "api",
Visibility: Visibility.Public,
IsEntrypoint: false,
EntrypointType: null,
IsSink: false,
SinkCategory: null);
var decorators = new[] { "@router.post('/users')" };
// Act
var result = classifier.Classify(node, decorators);
// Assert
Assert.True(result.HasValue);
Assert.Equal(EntrypointType.HttpHandler, result.Value);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PythonEntrypointClassifier_ClassifiesCeleryTask()
{
// Arrange
var classifier = new PythonEntrypointClassifier();
var node = new CallGraphNode(
NodeId: "py:tasks/email.send_notification",
Symbol: "send_notification",
File: "email.py",
Line: 15,
Package: "tasks",
Visibility: Visibility.Public,
IsEntrypoint: false,
EntrypointType: null,
IsSink: false,
SinkCategory: null);
var decorators = new[] { "@app.task(bind=True)" };
// Act
var result = classifier.Classify(node, decorators);
// Assert
Assert.True(result.HasValue);
Assert.Equal(EntrypointType.BackgroundJob, result.Value);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PythonEntrypointClassifier_ClassifiesClickCommand()
{
// Arrange
var classifier = new PythonEntrypointClassifier();
var node = new CallGraphNode(
NodeId: "py:cli/commands.deploy",
Symbol: "deploy",
File: "commands.py",
Line: 50,
Package: "cli",
Visibility: Visibility.Public,
IsEntrypoint: false,
EntrypointType: null,
IsSink: false,
SinkCategory: null);
var decorators = new[] { "@cli.command()" };
// Act
var result = classifier.Classify(node, decorators);
// Assert
Assert.True(result.HasValue);
Assert.Equal(EntrypointType.CliCommand, result.Value);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PythonSinkMatcher_MatchesSubprocessCall()
{
// Arrange
var matcher = new PythonSinkMatcher();
// Act
var result = matcher.Match("subprocess", "call");
// Assert
Assert.Equal(SinkCategory.CmdExec, result);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PythonSinkMatcher_MatchesEval()
{
// Arrange
var matcher = new PythonSinkMatcher();
// Act
var result = matcher.Match("builtins", "eval");
// Assert
Assert.Equal(SinkCategory.CodeInjection, result);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PythonSinkMatcher_MatchesPickleLoads()
{
// Arrange
var matcher = new PythonSinkMatcher();
// Act
var result = matcher.Match("pickle", "loads");
// Assert
Assert.Equal(SinkCategory.UnsafeDeser, result);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PythonSinkMatcher_MatchesSqlAlchemyExecute()
{
// Arrange
var matcher = new PythonSinkMatcher();
// Act
var result = matcher.Match("sqlalchemy.engine.Connection", "execute");
// Assert
Assert.Equal(SinkCategory.SqlRaw, result);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PythonSinkMatcher_ReturnsNullForSafeFunction()
{
// Arrange
var matcher = new PythonSinkMatcher();
// Act
var result = matcher.Match("myapp.utils", "format_string");
// Assert
Assert.Null(result);
}
}