more features checks. setup improvements

This commit is contained in:
master
2026-02-13 02:04:55 +02:00
parent 9911b7d73c
commit 9ca2de05df
675 changed files with 37550 additions and 1826 deletions

View File

@@ -593,9 +593,9 @@ public static class SetupEndpoints
{
var host = configValues.GetValueOrDefault("database.host", "db.stella-ops.local");
var port = configValues.GetValueOrDefault("database.port", "5432");
var db = configValues.GetValueOrDefault("database.name", "stellaops_platform");
var user = configValues.GetValueOrDefault("database.username", "stellaops");
var pass = configValues.GetValueOrDefault("database.password", "");
var db = configValues.GetValueOrDefault("database.database", "stellaops_platform");
var user = configValues.GetValueOrDefault("database.user", "stellaops");
var pass = configValues.GetValueOrDefault("database.password", "stellaops");
var connStr = $"Host={host};Port={port};Database={db};Username={user};Password={pass};Timeout=5";
using var conn = new Npgsql.NpgsqlConnection(connStr);
await conn.OpenAsync(ct);
@@ -618,7 +618,7 @@ public static class SetupEndpoints
var host = configValues.GetValueOrDefault("cache.host", "cache.stella-ops.local");
var port = configValues.GetValueOrDefault("cache.port", "6379");
using var tcp = new System.Net.Sockets.TcpClient();
await tcp.ConnectAsync(host, int.Parse(port), ct);
await tcp.ConnectAsync(host, int.TryParse(port, out var p) ? p : 6379, ct);
sw.Stop();
return Results.Ok(new
{

View File

@@ -0,0 +1,235 @@
// Copyright (c) StellaOps. All rights reserved.
// Licensed under BUSL-1.1. See LICENSE in the project root.
// Regression tests for POST /api/v1/setup/steps/{stepId}/test-connection
// Ensures config key names match the frontend contract and connection errors
// return 200 with success=false (not 500).
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Platform.WebService.Tests;
public sealed class SetupTestConnectionEndpointTests : IClassFixture<PlatformWebApplicationFactory>
{
private readonly PlatformWebApplicationFactory _factory;
public SetupTestConnectionEndpointTests(PlatformWebApplicationFactory factory)
{
_factory = factory;
}
private HttpClient CreateSetupClient()
{
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-StellaOps-Tenant", $"tenant-conn-{Guid.NewGuid():N}");
client.DefaultRequestHeaders.Add("X-StellaOps-Actor", "setup-tester");
return client;
}
// ───────────────────────────────────────────────────────────────────
// Database step: config key contract
// ───────────────────────────────────────────────────────────────────
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Database_TestConnection_UsesCorrectConfigKeys_ReturnsGracefulFailure()
{
// These are the exact keys the frontend sends.
// If the backend ever changes them, this test will catch the mismatch.
var payload = new Dictionary<string, object>
{
["configValues"] = new Dictionary<string, string>
{
["database.host"] = "127.0.0.1",
["database.port"] = "5432",
["database.database"] = "stellaops_test_nonexistent",
["database.user"] = "postgres",
["database.password"] = "postgres"
}
};
using var client = CreateSetupClient();
var response = await client.PostAsJsonAsync(
"/api/v1/setup/steps/database/test-connection",
payload,
TestContext.Current.CancellationToken);
// Must be 200 (graceful), NOT 500
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var json = await response.Content.ReadFromJsonAsync<JsonElement>(
TestContext.Current.CancellationToken);
var data = json.GetProperty("data");
// Connection will fail (no DB running in test), but response shape must be correct
Assert.True(data.TryGetProperty("success", out var success));
Assert.True(data.TryGetProperty("message", out var message));
Assert.True(data.TryGetProperty("latencyMs", out _));
// success is false because no real PostgreSQL is available in test
Assert.False(success.GetBoolean());
// The error message should reference the host we sent, proving the key was read
Assert.False(string.IsNullOrWhiteSpace(message.GetString()));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Database_TestConnection_WithEmptyBody_UsesDefaultsAndReturns200()
{
using var client = CreateSetupClient();
var response = await client.PostAsJsonAsync(
"/api/v1/setup/steps/database/test-connection",
new Dictionary<string, object>(),
TestContext.Current.CancellationToken);
// Must always return 200 (never 500), regardless of whether defaults connect
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var json = await response.Content.ReadFromJsonAsync<JsonElement>(
TestContext.Current.CancellationToken);
var data = json.GetProperty("data");
Assert.True(data.TryGetProperty("success", out _));
Assert.True(data.TryGetProperty("message", out _));
Assert.True(data.TryGetProperty("latencyMs", out _));
}
// ───────────────────────────────────────────────────────────────────
// Cache step: config key contract + safe port parsing
// ───────────────────────────────────────────────────────────────────
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Cache_TestConnection_UsesCorrectConfigKeys_ReturnsGracefulFailure()
{
var payload = new Dictionary<string, object>
{
["configValues"] = new Dictionary<string, string>
{
["cache.host"] = "127.0.0.1",
["cache.port"] = "6379"
}
};
using var client = CreateSetupClient();
var response = await client.PostAsJsonAsync(
"/api/v1/setup/steps/cache/test-connection",
payload,
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var json = await response.Content.ReadFromJsonAsync<JsonElement>(
TestContext.Current.CancellationToken);
var data = json.GetProperty("data");
Assert.True(data.TryGetProperty("success", out _));
Assert.True(data.TryGetProperty("message", out _));
Assert.True(data.TryGetProperty("latencyMs", out _));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Cache_TestConnection_InvalidPort_DoesNotCrash()
{
var payload = new Dictionary<string, object>
{
["configValues"] = new Dictionary<string, string>
{
["cache.host"] = "127.0.0.1",
["cache.port"] = "not-a-number"
}
};
using var client = CreateSetupClient();
var response = await client.PostAsJsonAsync(
"/api/v1/setup/steps/cache/test-connection",
payload,
TestContext.Current.CancellationToken);
// Must not throw FormatException / 500 — should fallback to default port
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
// ───────────────────────────────────────────────────────────────────
// Default step: unknown step IDs return generic success shape
// ───────────────────────────────────────────────────────────────────
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task UnknownStep_TestConnection_ReturnsGenericSuccessShape()
{
using var client = CreateSetupClient();
var response = await client.PostAsJsonAsync(
"/api/v1/setup/steps/notifications/test-connection",
new Dictionary<string, object>(),
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var json = await response.Content.ReadFromJsonAsync<JsonElement>(
TestContext.Current.CancellationToken);
var data = json.GetProperty("data");
Assert.True(data.GetProperty("success").GetBoolean());
Assert.Contains("notifications", data.GetProperty("message").GetString());
}
// ───────────────────────────────────────────────────────────────────
// Regression guard: frontend config key contract
// These constants document the exact keys the Angular frontend sends.
// If someone renames backend keys, these tests will fail immediately.
// ───────────────────────────────────────────────────────────────────
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("database.host")]
[InlineData("database.port")]
[InlineData("database.database")]
[InlineData("database.user")]
[InlineData("database.password")]
public async Task Database_TestConnection_AcceptsExpectedConfigKey(string key)
{
var payload = new Dictionary<string, object>
{
["configValues"] = new Dictionary<string, string>
{
[key] = "test-value"
}
};
using var client = CreateSetupClient();
var response = await client.PostAsJsonAsync(
"/api/v1/setup/steps/database/test-connection",
payload,
TestContext.Current.CancellationToken);
// The endpoint must not crash (500) for any of these keys
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("cache.host")]
[InlineData("cache.port")]
public async Task Cache_TestConnection_AcceptsExpectedConfigKey(string key)
{
var payload = new Dictionary<string, object>
{
["configValues"] = new Dictionary<string, string>
{
[key] = "test-value"
}
};
using var client = CreateSetupClient();
var response = await client.PostAsJsonAsync(
"/api/v1/setup/steps/cache/test-connection",
payload,
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}