more features checks. setup improvements
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user