Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using StellaOps.Concelier.Normalization.Identifiers;
|
||||
|
||||
namespace StellaOps.Concelier.Normalization.Tests;
|
||||
|
||||
public sealed class CpeNormalizerTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryNormalizeCpe_Preserves2Dot3Format()
|
||||
{
|
||||
var input = "cpe:2.3:A:Example:Product:1.0:*:*:*:*:*:*:*";
|
||||
|
||||
var success = IdentifierNormalizer.TryNormalizeCpe(input, out var normalized);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal("cpe:2.3:a:example:product:1.0:*:*:*:*:*:*:*", normalized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryNormalizeCpe_UpgradesUriBinding()
|
||||
{
|
||||
var input = "cpe:/o:RedHat:Enterprise_Linux:8";
|
||||
|
||||
var success = IdentifierNormalizer.TryNormalizeCpe(input, out var normalized);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal("cpe:2.3:o:redhat:enterprise_linux:8:*:*:*:*:*:*:*", normalized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryNormalizeCpe_InvalidInputReturnsFalse()
|
||||
{
|
||||
var success = IdentifierNormalizer.TryNormalizeCpe("not-a-cpe", out var normalized);
|
||||
|
||||
Assert.False(success);
|
||||
Assert.Null(normalized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryNormalizeCpe_DecodesPercentEncodingAndEscapes()
|
||||
{
|
||||
var input = "cpe:/a:Example%20Corp:Widget%2fSuite:1.0:update:%7e:%2a";
|
||||
|
||||
var success = IdentifierNormalizer.TryNormalizeCpe(input, out var normalized);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal(@"cpe:2.3:a:example\ corp:widget\/suite:1.0:update:*:*:*:*:*:*", normalized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryNormalizeCpe_ExpandsEditionFields()
|
||||
{
|
||||
var input = "cpe:/a:Vendor:Product:1.0:update:~pro~~windows~~:en-US";
|
||||
|
||||
var success = IdentifierNormalizer.TryNormalizeCpe(input, out var normalized);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal("cpe:2.3:a:vendor:product:1.0:update:*:en-us:pro:*:windows:*", normalized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryNormalizeCpe_PreservesEscapedCharactersIn23()
|
||||
{
|
||||
var input = @"cpe:2.3:a:example:printer\/:1.2.3:*:*:*:*:*:*:*";
|
||||
|
||||
var success = IdentifierNormalizer.TryNormalizeCpe(input, out var normalized);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal(@"cpe:2.3:a:example:printer\/:1.2.3:*:*:*:*:*:*:*", normalized);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Normalization.Cvss;
|
||||
|
||||
namespace StellaOps.Concelier.Normalization.Tests;
|
||||
|
||||
public sealed class CvssMetricNormalizerTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryNormalize_ComputesCvss31Defaults()
|
||||
{
|
||||
var vector = "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H";
|
||||
|
||||
var success = CvssMetricNormalizer.TryNormalize(null, vector, null, null, out var normalized);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal("3.1", normalized.Version);
|
||||
Assert.Equal(vector, normalized.Vector);
|
||||
Assert.Equal(9.8, normalized.BaseScore);
|
||||
Assert.Equal("critical", normalized.BaseSeverity);
|
||||
|
||||
var provenance = new AdvisoryProvenance("nvd", "cvss", "https://example", DateTimeOffset.UnixEpoch);
|
||||
var metric = normalized.ToModel(provenance);
|
||||
Assert.Equal("3.1", metric.Version);
|
||||
Assert.Equal(vector, metric.Vector);
|
||||
Assert.Equal(9.8, metric.BaseScore);
|
||||
Assert.Equal("critical", metric.BaseSeverity);
|
||||
Assert.Equal(provenance, metric.Provenance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryNormalize_NormalizesCvss20Severity()
|
||||
{
|
||||
var vector = "AV:N/AC:M/Au:S/C:P/I:P/A:P";
|
||||
|
||||
var success = CvssMetricNormalizer.TryNormalize("2.0", vector, 6.4, "MEDIUM", out var normalized);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal("2.0", normalized.Version);
|
||||
Assert.Equal("CVSS:2.0/AV:N/AC:M/AU:S/C:P/I:P/A:P", normalized.Vector);
|
||||
Assert.Equal(6.0, normalized.BaseScore);
|
||||
Assert.Equal("medium", normalized.BaseSeverity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryNormalize_ReturnsFalseWhenVectorMissing()
|
||||
{
|
||||
var success = CvssMetricNormalizer.TryNormalize("3.1", string.Empty, 9.8, "CRITICAL", out var normalized);
|
||||
|
||||
Assert.False(success);
|
||||
Assert.Equal(default, normalized);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using StellaOps.Concelier.Normalization.Distro;
|
||||
|
||||
namespace StellaOps.Concelier.Normalization.Tests;
|
||||
|
||||
public sealed class DebianEvrParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToCanonicalString_RoundTripsExplicitEpoch()
|
||||
{
|
||||
var parsed = DebianEvr.Parse(" 1:1.2.3-1 ");
|
||||
|
||||
Assert.Equal("1:1.2.3-1", parsed.Original);
|
||||
Assert.Equal("1:1.2.3-1", parsed.ToCanonicalString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToCanonicalString_SuppressesZeroEpochWhenMissing()
|
||||
{
|
||||
var parsed = DebianEvr.Parse("1.2.3-1");
|
||||
|
||||
Assert.Equal("1.2.3-1", parsed.ToCanonicalString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToCanonicalString_HandlesMissingRevision()
|
||||
{
|
||||
var parsed = DebianEvr.Parse("2:4.5");
|
||||
|
||||
Assert.Equal("2:4.5", parsed.ToCanonicalString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using StellaOps.Concelier.Normalization.Text;
|
||||
|
||||
namespace StellaOps.Concelier.Normalization.Tests;
|
||||
|
||||
public sealed class DescriptionNormalizerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Normalize_RemovesMarkupAndCollapsesWhitespace()
|
||||
{
|
||||
var candidates = new[]
|
||||
{
|
||||
new LocalizedText("<p>Hello\n\nworld!</p>", "en-US"),
|
||||
};
|
||||
|
||||
var result = DescriptionNormalizer.Normalize(candidates);
|
||||
|
||||
Assert.Equal("hello world!", result.Text.ToLowerInvariant());
|
||||
Assert.Equal("en", result.Language);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_FallsBackToPreferredLanguage()
|
||||
{
|
||||
var candidates = new[]
|
||||
{
|
||||
new LocalizedText("Bonjour", "fr"),
|
||||
new LocalizedText("Hello", "en-GB"),
|
||||
};
|
||||
|
||||
var result = DescriptionNormalizer.Normalize(candidates);
|
||||
|
||||
Assert.Equal("Hello", result.Text);
|
||||
Assert.Equal("en", result.Language);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_ReturnsDefaultWhenEmpty()
|
||||
{
|
||||
var result = DescriptionNormalizer.Normalize(Array.Empty<LocalizedText>());
|
||||
|
||||
Assert.Equal(string.Empty, result.Text);
|
||||
Assert.Equal("en", result.Language);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using StellaOps.Concelier.Normalization.Distro;
|
||||
|
||||
namespace StellaOps.Concelier.Normalization.Tests;
|
||||
|
||||
public sealed class NevraParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToCanonicalString_RoundTripsTrimmedInput()
|
||||
{
|
||||
var parsed = Nevra.Parse(" kernel-0:4.18.0-80.el8.x86_64 ");
|
||||
|
||||
Assert.Equal("kernel-0:4.18.0-80.el8.x86_64", parsed.Original);
|
||||
Assert.Equal("kernel-0:4.18.0-80.el8.x86_64", parsed.ToCanonicalString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToCanonicalString_ReconstructsKnownArchitecture()
|
||||
{
|
||||
var parsed = Nevra.Parse("bash-5.2.15-3.el9_4.arm64");
|
||||
|
||||
Assert.Equal("bash-5.2.15-3.el9_4.arm64", parsed.ToCanonicalString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToCanonicalString_HandlesMissingArchitecture()
|
||||
{
|
||||
var parsed = Nevra.Parse("openssl-libs-1:1.1.1k-7.el8");
|
||||
|
||||
Assert.Equal("openssl-libs-1:1.1.1k-7.el8", parsed.ToCanonicalString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_ReturnsTrueForExplicitZeroEpoch()
|
||||
{
|
||||
var success = Nevra.TryParse("glibc-0:2.36-8.el9.x86_64", out var nevra);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.NotNull(nevra);
|
||||
Assert.True(nevra!.HasExplicitEpoch);
|
||||
Assert.Equal(0, nevra.Epoch);
|
||||
Assert.Equal("glibc-0:2.36-8.el9.x86_64", nevra.ToCanonicalString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_IgnoresUnknownArchitectureSuffix()
|
||||
{
|
||||
var success = Nevra.TryParse("package-1.0-1.el9.weirdarch", out var nevra);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.NotNull(nevra);
|
||||
Assert.Null(nevra!.Architecture);
|
||||
Assert.Equal("package-1.0-1.el9.weirdarch", nevra.Original);
|
||||
Assert.Equal("package-1.0-1.el9.weirdarch", nevra.ToCanonicalString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_ReturnsFalseForMalformedNevra()
|
||||
{
|
||||
var success = Nevra.TryParse("bad-format", out var nevra);
|
||||
|
||||
Assert.False(success);
|
||||
Assert.Null(nevra);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Linq;
|
||||
using StellaOps.Concelier.Normalization.Identifiers;
|
||||
|
||||
namespace StellaOps.Concelier.Normalization.Tests;
|
||||
|
||||
public sealed class PackageUrlNormalizerTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryNormalizePackageUrl_LowersTypeAndNamespace()
|
||||
{
|
||||
var input = "pkg:NPM/Acme/Widget@1.0.0?Arch=X86_64";
|
||||
|
||||
var success = IdentifierNormalizer.TryNormalizePackageUrl(input, out var normalized, out var parsed);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal("pkg:npm/acme/widget@1.0.0?arch=X86_64", normalized);
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal("npm", parsed!.Type);
|
||||
Assert.Equal(new[] { "acme" }, parsed.NamespaceSegments.ToArray());
|
||||
Assert.Equal("widget", parsed.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryNormalizePackageUrl_OrdersQualifiers()
|
||||
{
|
||||
var input = "pkg:deb/debian/openssl?distro=x%2Fy&arch=amd64";
|
||||
|
||||
var success = IdentifierNormalizer.TryNormalizePackageUrl(input, out var normalized, out _);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal("pkg:deb/debian/openssl?arch=amd64&distro=x%2Fy", normalized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryNormalizePackageUrl_TrimsWhitespace()
|
||||
{
|
||||
var input = " pkg:pypi/Example/Package ";
|
||||
|
||||
var success = IdentifierNormalizer.TryNormalizePackageUrl(input, out var normalized, out _);
|
||||
|
||||
Assert.True(success);
|
||||
Assert.Equal("pkg:pypi/example/package", normalized);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Normalization.SemVer;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Normalization.Tests;
|
||||
|
||||
public sealed class SemVerRangeRuleBuilderTests
|
||||
{
|
||||
private const string Note = "spec:test";
|
||||
|
||||
[Theory]
|
||||
[InlineData("< 1.5.0", null, NormalizedVersionRuleTypes.LessThan, null, true, "1.5.0", false, null, false)]
|
||||
[InlineData(">= 1.0.0, < 2.0.0", null, NormalizedVersionRuleTypes.Range, "1.0.0", true, "2.0.0", false, null, false)]
|
||||
[InlineData(">1.2.3, <=1.3.0", null, NormalizedVersionRuleTypes.Range, "1.2.3", false, null, false, "1.3.0", true)]
|
||||
public void Build_ParsesCommonRanges(
|
||||
string range,
|
||||
string? patched,
|
||||
string expectedNormalizedType,
|
||||
string? expectedIntroduced,
|
||||
bool expectedIntroducedInclusive,
|
||||
string? expectedFixed,
|
||||
bool expectedFixedInclusive,
|
||||
string? expectedLastAffected,
|
||||
bool expectedLastInclusive)
|
||||
{
|
||||
var results = SemVerRangeRuleBuilder.Build(range, patched, Note);
|
||||
var result = Assert.Single(results);
|
||||
|
||||
var primitive = result.Primitive;
|
||||
Assert.Equal(expectedIntroduced, primitive.Introduced);
|
||||
Assert.Equal(expectedIntroducedInclusive, primitive.IntroducedInclusive);
|
||||
Assert.Equal(expectedFixed, primitive.Fixed);
|
||||
Assert.Equal(expectedFixedInclusive, primitive.FixedInclusive);
|
||||
Assert.Equal(expectedLastAffected, primitive.LastAffected);
|
||||
Assert.Equal(expectedLastInclusive, primitive.LastAffectedInclusive);
|
||||
|
||||
var normalized = result.NormalizedRule;
|
||||
Assert.Equal(NormalizedVersionSchemes.SemVer, normalized.Scheme);
|
||||
Assert.Equal(expectedNormalizedType, normalized.Type);
|
||||
Assert.Equal(expectedIntroduced, normalized.Min);
|
||||
Assert.Equal(expectedIntroduced is null ? (bool?)null : expectedIntroducedInclusive, normalized.MinInclusive);
|
||||
Assert.Equal(expectedFixed ?? expectedLastAffected, normalized.Max);
|
||||
Assert.Equal(
|
||||
expectedFixed is not null ? expectedFixedInclusive : expectedLastInclusive,
|
||||
normalized.MaxInclusive);
|
||||
Assert.Equal(patched is null && expectedIntroduced is null && expectedFixed is null && expectedLastAffected is null ? null : Note, normalized.Notes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_UsesPatchedVersionWhenUpperBoundMissing()
|
||||
{
|
||||
var results = SemVerRangeRuleBuilder.Build(">= 4.0.0", "4.3.6", Note);
|
||||
var result = Assert.Single(results);
|
||||
|
||||
Assert.Equal("4.0.0", result.Primitive.Introduced);
|
||||
Assert.Equal("4.3.6", result.Primitive.Fixed);
|
||||
Assert.False(result.Primitive.FixedInclusive);
|
||||
|
||||
var normalized = result.NormalizedRule;
|
||||
Assert.Equal(NormalizedVersionRuleTypes.Range, normalized.Type);
|
||||
Assert.Equal("4.0.0", normalized.Min);
|
||||
Assert.True(normalized.MinInclusive);
|
||||
Assert.Equal("4.3.6", normalized.Max);
|
||||
Assert.False(normalized.MaxInclusive);
|
||||
Assert.Equal(Note, normalized.Notes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("^1.2.3", "1.2.3", "2.0.0")]
|
||||
[InlineData("~1.2.3", "1.2.3", "1.3.0")]
|
||||
[InlineData("~> 1.2", "1.2.0", "1.3.0")]
|
||||
public void Build_HandlesCaretAndTilde(string range, string expectedMin, string expectedMax)
|
||||
{
|
||||
var results = SemVerRangeRuleBuilder.Build(range, null, Note);
|
||||
var result = Assert.Single(results);
|
||||
var normalized = result.NormalizedRule;
|
||||
Assert.Equal(expectedMin, normalized.Min);
|
||||
Assert.True(normalized.MinInclusive);
|
||||
Assert.Equal(expectedMax, normalized.Max);
|
||||
Assert.False(normalized.MaxInclusive);
|
||||
Assert.Equal(NormalizedVersionRuleTypes.Range, normalized.Type);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1.2.x", "1.2.0", "1.3.0")]
|
||||
[InlineData("1.x", "1.0.0", "2.0.0")]
|
||||
public void Build_HandlesWildcardNotation(string range, string expectedMin, string expectedMax)
|
||||
{
|
||||
var results = SemVerRangeRuleBuilder.Build(range, null, Note);
|
||||
var result = Assert.Single(results);
|
||||
Assert.Equal(expectedMin, result.Primitive.Introduced);
|
||||
Assert.Equal(expectedMax, result.Primitive.Fixed);
|
||||
|
||||
var normalized = result.NormalizedRule;
|
||||
Assert.Equal(expectedMin, normalized.Min);
|
||||
Assert.Equal(expectedMax, normalized.Max);
|
||||
Assert.Equal(NormalizedVersionRuleTypes.Range, normalized.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_PreservesPreReleaseAndMetadataInExactRule()
|
||||
{
|
||||
var results = SemVerRangeRuleBuilder.Build("= 2.5.1-alpha.1+build.7", null, Note);
|
||||
var result = Assert.Single(results);
|
||||
|
||||
Assert.Equal("2.5.1-alpha.1+build.7", result.Primitive.ExactValue);
|
||||
|
||||
var normalized = result.NormalizedRule;
|
||||
Assert.Equal(NormalizedVersionRuleTypes.Exact, normalized.Type);
|
||||
Assert.Equal("2.5.1-alpha.1+build.7", normalized.Value);
|
||||
Assert.Equal(Note, normalized.Notes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_ParsesComparatorWithoutCommaSeparators()
|
||||
{
|
||||
var results = SemVerRangeRuleBuilder.Build(">=1.0.0 <1.2.0", null, Note);
|
||||
var result = Assert.Single(results);
|
||||
|
||||
var primitive = result.Primitive;
|
||||
Assert.Equal("1.0.0", primitive.Introduced);
|
||||
Assert.True(primitive.IntroducedInclusive);
|
||||
Assert.Equal("1.2.0", primitive.Fixed);
|
||||
Assert.False(primitive.FixedInclusive);
|
||||
Assert.Equal(">= 1.0.0, < 1.2.0", primitive.ConstraintExpression);
|
||||
|
||||
var normalized = result.NormalizedRule;
|
||||
Assert.Equal(NormalizedVersionRuleTypes.Range, normalized.Type);
|
||||
Assert.Equal("1.0.0", normalized.Min);
|
||||
Assert.True(normalized.MinInclusive);
|
||||
Assert.Equal("1.2.0", normalized.Max);
|
||||
Assert.False(normalized.MaxInclusive);
|
||||
Assert.Equal(Note, normalized.Notes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_HandlesMultipleSegmentsSeparatedByOr()
|
||||
{
|
||||
var results = SemVerRangeRuleBuilder.Build(">=1.0.0 <1.2.0 || >=2.0.0 <2.2.0", null, Note);
|
||||
Assert.Equal(2, results.Count);
|
||||
|
||||
var first = results[0];
|
||||
Assert.Equal("1.0.0", first.Primitive.Introduced);
|
||||
Assert.Equal("1.2.0", first.Primitive.Fixed);
|
||||
Assert.Equal(NormalizedVersionRuleTypes.Range, first.NormalizedRule.Type);
|
||||
Assert.Equal("1.0.0", first.NormalizedRule.Min);
|
||||
Assert.Equal("1.2.0", first.NormalizedRule.Max);
|
||||
|
||||
var second = results[1];
|
||||
Assert.Equal("2.0.0", second.Primitive.Introduced);
|
||||
Assert.Equal("2.2.0", second.Primitive.Fixed);
|
||||
Assert.Equal(NormalizedVersionRuleTypes.Range, second.NormalizedRule.Type);
|
||||
Assert.Equal("2.0.0", second.NormalizedRule.Min);
|
||||
Assert.Equal("2.2.0", second.NormalizedRule.Max);
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
Assert.Equal(Note, result.NormalizedRule.Notes);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildNormalizedRules_ProjectsNormalizedRules()
|
||||
{
|
||||
var rules = SemVerRangeRuleBuilder.BuildNormalizedRules(">=1.0.0 <1.2.0", null, Note);
|
||||
var rule = Assert.Single(rules);
|
||||
|
||||
Assert.Equal(NormalizedVersionSchemes.SemVer, rule.Scheme);
|
||||
Assert.Equal(NormalizedVersionRuleTypes.Range, rule.Type);
|
||||
Assert.Equal("1.0.0", rule.Min);
|
||||
Assert.True(rule.MinInclusive);
|
||||
Assert.Equal("1.2.0", rule.Max);
|
||||
Assert.False(rule.MaxInclusive);
|
||||
Assert.Equal(Note, rule.Notes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildNormalizedRules_ReturnsEmptyWhenNoRules()
|
||||
{
|
||||
var rules = SemVerRangeRuleBuilder.BuildNormalizedRules(" ", null, Note);
|
||||
Assert.Empty(rules);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Normalization/StellaOps.Concelier.Normalization.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user