up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-13 18:08:55 +02:00
parent 6e45066e37
commit f1a39c4ce3
234 changed files with 24038 additions and 6910 deletions

View File

@@ -579,4 +579,213 @@ public sealed class PythonLanguageAnalyzerTests
Directory.CreateDirectory(path);
return path;
}
// ===== SCAN-PY-405-007 Fixtures =====
[Fact]
public async Task RequirementsWithIncludesAreFollowedAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
// Create main requirements.txt that includes another file
var requirementsPath = Path.Combine(fixturePath, "requirements.txt");
await File.WriteAllTextAsync(requirementsPath, $"requests==2.28.0{Environment.NewLine}-r requirements-base.txt{Environment.NewLine}", cancellationToken);
// Create included requirements file
var baseRequirementsPath = Path.Combine(fixturePath, "requirements-base.txt");
await File.WriteAllTextAsync(baseRequirementsPath, $"urllib3==1.26.0{Environment.NewLine}certifi==2022.12.7{Environment.NewLine}", cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// All three packages should be found (from both files)
Assert.True(ComponentHasMetadata(root, "requests", "declaredOnly", "true"));
Assert.True(ComponentHasMetadata(root, "urllib3", "declaredOnly", "true"));
Assert.True(ComponentHasMetadata(root, "certifi", "declaredOnly", "true"));
// urllib3 and certifi should come from the included file
Assert.True(ComponentHasMetadata(root, "urllib3", "lockSource", "requirements-base.txt"));
Assert.True(ComponentHasMetadata(root, "certifi", "lockSource", "requirements-base.txt"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
[Fact]
public async Task PipfileLockDevelopSectionIsParsedAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
// Create Pipfile.lock with default and develop sections
var pipfileLockPath = Path.Combine(fixturePath, "Pipfile.lock");
var pipfileLock = """
{
"_meta": { "sources": [] },
"default": {
"requests": { "version": "==2.28.0" }
},
"develop": {
"pytest": { "version": "==7.0.0" }
}
}
""";
await File.WriteAllTextAsync(pipfileLockPath, pipfileLock, cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// Both packages should be found
Assert.True(ComponentHasMetadata(root, "requests", "declaredOnly", "true"));
Assert.True(ComponentHasMetadata(root, "pytest", "declaredOnly", "true"));
// requests should be prod scope, pytest should be dev scope
Assert.True(ComponentHasMetadata(root, "requests", "scope", "prod"));
Assert.True(ComponentHasMetadata(root, "pytest", "scope", "dev"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
[Fact]
public async Task RequirementsDevTxtGetsScopeDevAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
// Create requirements.txt for prod
var requirementsPath = Path.Combine(fixturePath, "requirements.txt");
await File.WriteAllTextAsync(requirementsPath, $"flask==2.0.0{Environment.NewLine}", cancellationToken);
// Create requirements-dev.txt for dev dependencies
var requirementsDevPath = Path.Combine(fixturePath, "requirements-dev.txt");
await File.WriteAllTextAsync(requirementsDevPath, $"pytest==7.0.0{Environment.NewLine}", cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// flask should be prod scope (from requirements.txt)
Assert.True(ComponentHasMetadata(root, "flask", "scope", "prod"));
// pytest should be dev scope (from requirements-dev.txt)
Assert.True(ComponentHasMetadata(root, "pytest", "scope", "dev"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
[Fact]
public async Task Pep508DirectReferenceIsParsedAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
// Create requirements.txt with direct reference
var requirementsPath = Path.Combine(fixturePath, "requirements.txt");
await File.WriteAllTextAsync(requirementsPath,
$"mypackage @ https://example.com/packages/mypackage-1.0.0.whl{Environment.NewLine}",
cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// Package should be found with URL reference
Assert.True(ComponentHasMetadata(root, "mypackage", "declaredOnly", "true"));
Assert.True(ComponentHasMetadata(root, "mypackage", "lockDirectUrl", "https://example.com/packages/mypackage-1.0.0.whl"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
[Fact]
public async Task RequirementsCycleIsDetectedAndHandledAsync()
{
var cancellationToken = TestContext.Current.CancellationToken;
var fixturePath = CreateTemporaryWorkspace();
try
{
// Create requirements.txt that includes base
var requirementsPath = Path.Combine(fixturePath, "requirements.txt");
await File.WriteAllTextAsync(requirementsPath, $"requests==2.28.0{Environment.NewLine}-r requirements-base.txt{Environment.NewLine}", cancellationToken);
// Create requirements-base.txt that includes back to main (cycle)
var baseRequirementsPath = Path.Combine(fixturePath, "requirements-base.txt");
await File.WriteAllTextAsync(baseRequirementsPath, $"urllib3==1.26.0{Environment.NewLine}-r requirements.txt{Environment.NewLine}", cancellationToken);
var analyzers = new ILanguageAnalyzer[]
{
new PythonLanguageAnalyzer()
};
// Should not throw due to infinite loop
var json = await LanguageAnalyzerTestHarness.RunToJsonAsync(
fixturePath,
analyzers,
cancellationToken);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
// Both packages should still be found (cycle handled gracefully)
Assert.True(ComponentHasMetadata(root, "requests", "declaredOnly", "true"));
Assert.True(ComponentHasMetadata(root, "urllib3", "declaredOnly", "true"));
}
finally
{
Directory.Delete(fixturePath, recursive: true);
}
}
}