feat: Add Bun language analyzer and related functionality
- Implemented BunPackageNormalizer to deduplicate packages by name and version. - Created BunProjectDiscoverer to identify Bun project roots in the filesystem. - Added project files for the Bun analyzer including manifest and project configuration. - Developed comprehensive tests for Bun language analyzer covering various scenarios. - Included fixture files for testing standard installs, isolated linker installs, lockfile-only scenarios, and workspaces. - Established stubs for authentication sessions to facilitate testing in the web application.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Concelier.Core.Linksets;
|
||||
|
||||
namespace StellaOps.Concelier.WebService.Tests.Cache;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for LNM linkset cache read-through behavior.
|
||||
/// Per CONCELIER-AIAI-31-002.
|
||||
/// </summary>
|
||||
public sealed class LinksetCacheReadThroughTests
|
||||
{
|
||||
[Fact]
|
||||
public void AdvisoryLinkset_CanBeCreatedForCache()
|
||||
{
|
||||
var linkset = new AdvisoryLinkset(
|
||||
TenantId: "test-tenant",
|
||||
Source: "nvd",
|
||||
AdvisoryId: "CVE-2024-0001",
|
||||
ObservationIds: ImmutableArray.Create("obs-1", "obs-2"),
|
||||
Normalized: new AdvisoryLinksetNormalized(
|
||||
Purls: new[] { "pkg:npm/lodash@4.17.20" },
|
||||
Cpes: new[] { "cpe:2.3:a:lodash:lodash:*" },
|
||||
Versions: new[] { "4.17.20" },
|
||||
Ranges: null,
|
||||
Severities: null),
|
||||
Provenance: new AdvisoryLinksetProvenance(
|
||||
ObservationHashes: new[] { "sha256:abc123" },
|
||||
ToolVersion: "1.0.0",
|
||||
PolicyHash: null),
|
||||
Confidence: 0.95,
|
||||
Conflicts: null,
|
||||
CreatedAt: DateTimeOffset.UtcNow,
|
||||
BuiltByJobId: "job-123");
|
||||
|
||||
Assert.Equal("test-tenant", linkset.TenantId);
|
||||
Assert.Equal("nvd", linkset.Source);
|
||||
Assert.Equal("CVE-2024-0001", linkset.AdvisoryId);
|
||||
Assert.Equal(2, linkset.ObservationIds.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdvisoryLinkset_WithConflicts_CanBeCreated()
|
||||
{
|
||||
var conflicts = new List<AdvisoryLinksetConflict>
|
||||
{
|
||||
new AdvisoryLinksetConflict(
|
||||
Field: "severity",
|
||||
Reason: "severity-mismatch",
|
||||
Values: new[] { "critical", "high" },
|
||||
SourceIds: new[] { "nvd", "github" })
|
||||
};
|
||||
|
||||
var linkset = new AdvisoryLinkset(
|
||||
TenantId: "test-tenant",
|
||||
Source: "aggregated",
|
||||
AdvisoryId: "CVE-2024-0002",
|
||||
ObservationIds: ImmutableArray.Create("obs-1"),
|
||||
Normalized: null,
|
||||
Provenance: null,
|
||||
Confidence: 0.72,
|
||||
Conflicts: conflicts,
|
||||
CreatedAt: DateTimeOffset.UtcNow,
|
||||
BuiltByJobId: null);
|
||||
|
||||
Assert.NotNull(linkset.Conflicts);
|
||||
Assert.Single(linkset.Conflicts);
|
||||
Assert.Equal("severity", linkset.Conflicts[0].Field);
|
||||
Assert.Equal("severity-mismatch", linkset.Conflicts[0].Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdvisoryLinksetNormalized_ContainsExpectedFields()
|
||||
{
|
||||
var normalized = new AdvisoryLinksetNormalized(
|
||||
Purls: new[] { "pkg:npm/example@1.0.0", "pkg:npm/example@1.0.1" },
|
||||
Cpes: new[] { "cpe:2.3:a:example:*" },
|
||||
Versions: new[] { "1.0.0", "1.0.1" },
|
||||
Ranges: new[]
|
||||
{
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["type"] = "SEMVER",
|
||||
["events"] = new[]
|
||||
{
|
||||
new Dictionary<string, object?> { ["introduced"] = "0" },
|
||||
new Dictionary<string, object?> { ["fixed"] = "1.0.2" }
|
||||
}
|
||||
}
|
||||
},
|
||||
Severities: new[]
|
||||
{
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["type"] = "CVSS_V3",
|
||||
["score"] = 9.8
|
||||
}
|
||||
});
|
||||
|
||||
Assert.NotNull(normalized.Purls);
|
||||
Assert.Equal(2, normalized.Purls.Count);
|
||||
Assert.NotNull(normalized.Versions);
|
||||
Assert.Equal(2, normalized.Versions.Count);
|
||||
Assert.NotNull(normalized.Ranges);
|
||||
Assert.Single(normalized.Ranges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdvisoryLinksetProvenance_ContainsHashes()
|
||||
{
|
||||
var provenance = new AdvisoryLinksetProvenance(
|
||||
ObservationHashes: new[] { "sha256:abc123", "sha256:def456" },
|
||||
ToolVersion: "concelier-v1.0.0",
|
||||
PolicyHash: "sha256:policy789");
|
||||
|
||||
Assert.Equal(2, provenance.ObservationHashes!.Count);
|
||||
Assert.Equal("concelier-v1.0.0", provenance.ToolVersion);
|
||||
Assert.Equal("sha256:policy789", provenance.PolicyHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CacheKey_DeterministicFromLinkset()
|
||||
{
|
||||
// Cache key should be deterministic: {tenant}:{advisoryId}:{source}
|
||||
var linkset = new AdvisoryLinkset(
|
||||
TenantId: "acme",
|
||||
Source: "nvd",
|
||||
AdvisoryId: "CVE-2024-0001",
|
||||
ObservationIds: ImmutableArray<string>.Empty,
|
||||
Normalized: null,
|
||||
Provenance: null,
|
||||
Confidence: null,
|
||||
Conflicts: null,
|
||||
CreatedAt: DateTimeOffset.UtcNow,
|
||||
BuiltByJobId: null);
|
||||
|
||||
var cacheKey = $"{linkset.TenantId}:{linkset.AdvisoryId}:{linkset.Source}";
|
||||
Assert.Equal("acme:CVE-2024-0001:nvd", cacheKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using StellaOps.Concelier.WebService.Deprecation;
|
||||
|
||||
namespace StellaOps.Concelier.WebService.Tests.Deprecation;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for deprecation headers infrastructure.
|
||||
/// Per CONCELIER-WEB-OAS-63-001.
|
||||
/// </summary>
|
||||
public sealed class DeprecationHeadersTests
|
||||
{
|
||||
[Fact]
|
||||
public void DeprecationInfo_LegacyLinksets_HasCorrectValues()
|
||||
{
|
||||
var info = DeprecatedEndpoints.LegacyLinksets;
|
||||
|
||||
Assert.Equal(DeprecatedEndpoints.LegacyApisDeprecatedAt, info.DeprecatedAt);
|
||||
Assert.Equal(DeprecatedEndpoints.LegacyApisSunsetAt, info.SunsetAt);
|
||||
Assert.Equal("/v1/lnm/linksets", info.SuccessorUri);
|
||||
Assert.NotEmpty(info.Message);
|
||||
Assert.NotNull(info.MigrationGuideUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeprecationInfo_LegacyAdvisoryObservations_HasCorrectValues()
|
||||
{
|
||||
var info = DeprecatedEndpoints.LegacyAdvisoryObservations;
|
||||
|
||||
Assert.Equal(DeprecatedEndpoints.LegacyApisDeprecatedAt, info.DeprecatedAt);
|
||||
Assert.Equal(DeprecatedEndpoints.LegacyApisSunsetAt, info.SunsetAt);
|
||||
Assert.Equal("/v1/lnm/linksets", info.SuccessorUri);
|
||||
Assert.Contains("includeObservations", info.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeprecationInfo_LegacyAdvisoryLinksets_HasCorrectValues()
|
||||
{
|
||||
var info = DeprecatedEndpoints.LegacyAdvisoryLinksets;
|
||||
|
||||
Assert.Equal(DeprecatedEndpoints.LegacyApisDeprecatedAt, info.DeprecatedAt);
|
||||
Assert.Equal("/v1/lnm/linksets", info.SuccessorUri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeprecationInfo_LegacyAdvisoryLinksetsExport_HasCorrectValues()
|
||||
{
|
||||
var info = DeprecatedEndpoints.LegacyAdvisoryLinksetsExport;
|
||||
|
||||
Assert.Equal(DeprecatedEndpoints.LegacyApisDeprecatedAt, info.DeprecatedAt);
|
||||
Assert.Equal("/v1/lnm/linksets", info.SuccessorUri);
|
||||
Assert.Contains("pagination", info.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeprecationInfo_LegacyConcelierObservations_HasCorrectValues()
|
||||
{
|
||||
var info = DeprecatedEndpoints.LegacyConcelierObservations;
|
||||
|
||||
Assert.Equal(DeprecatedEndpoints.LegacyApisDeprecatedAt, info.DeprecatedAt);
|
||||
Assert.Equal("/v1/lnm/linksets", info.SuccessorUri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllDeprecatedEndpoints_HaveMigrationGuides()
|
||||
{
|
||||
var endpoints = new[]
|
||||
{
|
||||
DeprecatedEndpoints.LegacyLinksets,
|
||||
DeprecatedEndpoints.LegacyAdvisoryObservations,
|
||||
DeprecatedEndpoints.LegacyAdvisoryLinksets,
|
||||
DeprecatedEndpoints.LegacyAdvisoryLinksetsExport,
|
||||
DeprecatedEndpoints.LegacyConcelierObservations
|
||||
};
|
||||
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
Assert.NotNull(endpoint.MigrationGuideUrl);
|
||||
Assert.StartsWith(DeprecatedEndpoints.MigrationGuideBaseUrl, endpoint.MigrationGuideUrl);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllDeprecatedEndpoints_HaveSunsetDates()
|
||||
{
|
||||
var endpoints = new[]
|
||||
{
|
||||
DeprecatedEndpoints.LegacyLinksets,
|
||||
DeprecatedEndpoints.LegacyAdvisoryObservations,
|
||||
DeprecatedEndpoints.LegacyAdvisoryLinksets,
|
||||
DeprecatedEndpoints.LegacyAdvisoryLinksetsExport,
|
||||
DeprecatedEndpoints.LegacyConcelierObservations
|
||||
};
|
||||
|
||||
foreach (var endpoint in endpoints)
|
||||
{
|
||||
Assert.NotNull(endpoint.SunsetAt);
|
||||
Assert.True(endpoint.SunsetAt > endpoint.DeprecatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SunsetDate_IsAfterDeprecationDate()
|
||||
{
|
||||
Assert.True(
|
||||
DeprecatedEndpoints.LegacyApisSunsetAt > DeprecatedEndpoints.LegacyApisDeprecatedAt,
|
||||
"Sunset date must be after deprecation date");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeprecationHeaders_ConstantsAreDefined()
|
||||
{
|
||||
Assert.Equal("Deprecation", DeprecationHeaders.Deprecation);
|
||||
Assert.Equal("Sunset", DeprecationHeaders.Sunset);
|
||||
Assert.Equal("Link", DeprecationHeaders.Link);
|
||||
Assert.Equal("X-Deprecation-Notice", DeprecationHeaders.XDeprecationNotice);
|
||||
Assert.Equal("X-Deprecation-Guide", DeprecationHeaders.XDeprecationGuide);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user