Restructure solution layout by module

This commit is contained in:
master
2025-10-28 15:10:40 +02:00
parent 95daa159c4
commit d870da18ce
4103 changed files with 192899 additions and 187024 deletions

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http.Json;
using System.Text;
using System.Threading.Tasks;
using MongoDB.Driver;
using StellaOps.Signals.Models;
using StellaOps.Signals.Tests.TestInfrastructure;
using Xunit;
namespace StellaOps.Signals.Tests;
public class CallgraphIngestionTests : IClassFixture<SignalsTestFactory>
{
private readonly SignalsTestFactory factory;
public CallgraphIngestionTests(SignalsTestFactory factory)
{
this.factory = factory;
}
[Theory]
[InlineData("java")]
[InlineData("nodejs")]
[InlineData("python")]
[InlineData("go")]
public async Task Ingest_Callgraph_PersistsDocumentAndArtifact(string language)
{
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Scopes", "signals:write");
var component = $"demo-{language}";
var request = CreateRequest(language, component: component);
var response = await client.PostAsJsonAsync("/signals/callgraphs", request);
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<CallgraphIngestResponse>();
Assert.NotNull(body);
var database = new MongoClient(factory.MongoRunner.ConnectionString).GetDatabase("signals-tests");
var collection = database.GetCollection<CallgraphDocument>("callgraphs");
var doc = await collection.Find(d => d.Id == body!.CallgraphId).FirstOrDefaultAsync();
Assert.NotNull(doc);
Assert.Equal(language, doc!.Language);
Assert.Equal(component, doc.Component);
Assert.Equal("1.0.0", doc.Version);
Assert.Equal(2, doc.Nodes.Count);
Assert.Equal(1, doc.Edges.Count);
var artifactPath = Path.Combine(factory.StoragePath, body.ArtifactPath);
Assert.True(File.Exists(artifactPath));
Assert.False(string.IsNullOrWhiteSpace(body.ArtifactHash));
}
[Fact]
public async Task Ingest_UnsupportedLanguage_ReturnsBadRequest()
{
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Scopes", "signals:write");
var request = CreateRequest("ruby");
var response = await client.PostAsJsonAsync("/signals/callgraphs", request);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task Ingest_InvalidArtifactContent_ReturnsBadRequest()
{
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Scopes", "signals:write");
var request = CreateRequest("java") with { ArtifactContentBase64 = "not-base64" };
var response = await client.PostAsJsonAsync("/signals/callgraphs", request);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task Ingest_InvalidGraphStructure_ReturnsUnprocessableEntity()
{
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Scopes", "signals:write");
var json = "{\"formatVersion\":\"1.0\",\"graph\":{}}";
var request = CreateRequest("java", json);
var response = await client.PostAsJsonAsync("/signals/callgraphs", request);
Assert.Equal(HttpStatusCode.UnprocessableEntity, response.StatusCode);
}
[Fact]
public async Task Ingest_SameComponentUpsertsDocument()
{
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Scopes", "signals:write");
var firstRequest = CreateRequest("python");
var secondJson = "{\"graph\":{\"nodes\":[{\"id\":\"module.entry\",\"name\":\"module.entry\"}],\"edges\":[]}}";
var secondRequest = CreateRequest("python", secondJson);
var firstResponse = await client.PostAsJsonAsync("/signals/callgraphs", firstRequest);
var secondResponse = await client.PostAsJsonAsync("/signals/callgraphs", secondRequest);
Assert.Equal(HttpStatusCode.Accepted, firstResponse.StatusCode);
Assert.Equal(HttpStatusCode.Accepted, secondResponse.StatusCode);
var database = new MongoClient(factory.MongoRunner.ConnectionString).GetDatabase("signals-tests");
var collection = database.GetCollection<CallgraphDocument>("callgraphs");
var count = await collection.CountDocumentsAsync(FilterDefinition<CallgraphDocument>.Empty);
Assert.Equal(1, count);
var doc = await collection.Find(_ => true).FirstAsync();
Assert.Single(doc.Nodes);
Assert.Equal("python", doc.Language);
}
private static CallgraphIngestRequest CreateRequest(string language, string? customJson = null, string component = "demo")
{
var json = customJson ?? "{\"formatVersion\":\"1.0\",\"graph\":{\"nodes\":[{\"id\":\"main.entry\",\"name\":\"main.entry\",\"kind\":\"function\",\"file\":\"main\",\"line\":1},{\"id\":\"helper.run\",\"name\":\"helper.run\",\"kind\":\"function\",\"file\":\"helper\",\"line\":2}],\"edges\":[{\"source\":\"main.entry\",\"target\":\"helper.run\",\"type\":\"call\"}]}}";
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(json));
return new CallgraphIngestRequest(
Language: language,
Component: component,
Version: "1.0.0",
ArtifactContentType: "application/json",
ArtifactFileName: $"{language}-callgraph.json",
ArtifactContentBase64: base64,
Metadata: new Dictionary<string, string?>
{
["source"] = "unit-test"
});
}
}

View File

@@ -0,0 +1,112 @@
using System.Collections.Generic;
using System.Net;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using StellaOps.Signals.Tests.TestInfrastructure;
using Xunit;
namespace StellaOps.Signals.Tests;
public class SignalsApiTests : IClassFixture<SignalsTestFactory>
{
private readonly SignalsTestFactory factory;
public SignalsApiTests(SignalsTestFactory factory)
{
this.factory = factory;
}
[Fact]
public async Task Healthz_ReturnsOk()
{
using var client = factory.CreateClient();
var response = await client.GetAsync("/healthz");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Fact]
public async Task Readyz_ReturnsOk()
{
using var client = factory.CreateClient();
var response = await client.GetAsync("/readyz");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var payload = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();
Assert.NotNull(payload);
Assert.Equal("ready", payload!["status"]);
}
[Fact]
public async Task Ping_WithoutScopeHeader_ReturnsUnauthorized()
{
using var client = factory.CreateClient();
var response = await client.GetAsync("/signals/ping");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task Ping_WithMissingScope_ReturnsForbidden()
{
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Scopes", "signals:write");
var response = await client.GetAsync("/signals/ping");
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
[Fact]
public async Task Ping_WithReadScope_ReturnsNoContent()
{
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Scopes", "signals:read");
var response = await client.GetAsync("/signals/ping");
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
[Fact]
public async Task Ping_WithFallbackDisabled_ReturnsUnauthorized()
{
using var app = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureAppConfiguration((_, configuration) =>
{
configuration.AddInMemoryCollection(new Dictionary<string, string?>
{
["Signals:Authority:AllowAnonymousFallback"] = "false"
});
});
});
using var client = app.CreateClient();
var response = await client.GetAsync("/signals/ping");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task Status_WithReadScope_ReturnsOk()
{
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Scopes", "signals:read");
var response = await client.GetAsync("/signals/status");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var payload = await response.Content.ReadFromJsonAsync<Dictionary<string, string>>();
Assert.NotNull(payload);
Assert.Equal("signals", payload!["service"]);
}
[Fact]
public async Task Status_WithMissingScope_ReturnsForbidden()
{
using var client = factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Scopes", "signals:write");
var response = await client.GetAsync("/signals/status");
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
}

View File

@@ -0,0 +1,22 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="Mongo2Go" Version="4.1.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../Signals/StellaOps.Signals/StellaOps.Signals.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Mongo2Go;
namespace StellaOps.Signals.Tests.TestInfrastructure;
internal sealed class SignalsTestFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly MongoDbRunner mongoRunner;
private readonly string storagePath;
public SignalsTestFactory()
{
mongoRunner = MongoDbRunner.Start(singleNodeReplSet: true);
storagePath = Path.Combine(Path.GetTempPath(), "signals-tests", Guid.NewGuid().ToString());
Directory.CreateDirectory(storagePath);
}
public string StoragePath => storagePath;
public MongoDbRunner MongoRunner => mongoRunner;
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration((context, configuration) =>
{
var settings = new Dictionary<string, string?>
{
["Signals:Authority:Enabled"] = "false",
["Signals:Authority:AllowAnonymousFallback"] = "true",
["Signals:Mongo:ConnectionString"] = mongoRunner.ConnectionString,
["Signals:Mongo:Database"] = "signals-tests",
["Signals:Mongo:CallgraphsCollection"] = "callgraphs",
["Signals:Storage:RootPath"] = storagePath
};
configuration.AddInMemoryCollection(settings);
});
}
public Task InitializeAsync() => Task.CompletedTask;
public async Task DisposeAsync()
{
await Task.Run(() => mongoRunner.Dispose());
try
{
if (Directory.Exists(storagePath))
{
Directory.Delete(storagePath, recursive: true);
}
}
catch
{
// best effort cleanup.
}
}
}