This commit is contained in:
6d486f49
2026-08-14 15:56:17 -04:00
parent af64b9e95c
commit 09a8adfc4a
827 changed files with 65045 additions and 71849 deletions
@@ -0,0 +1,263 @@
@page "/test-suite"
@implements IDisposable
<PageTitle>Database Test Suite Runner - Chrono DB</PageTitle>
<div class="d-flex align-items-center justify-content-between mb-4">
<div>
<h2 class="fw-bold mb-1 text-white">Database Feature Test Suite</h2>
<p class="text-secondary mb-0">Automated end-to-end verification of all database features, constraints, and JSONB mappings referenced in Chrono Cloud.</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-chrono-secondary" @onclick="ClearResults" disabled="@isRunning">
<i class="bi bi-eraser-fill"></i> Clear Results
</button>
<button class="btn btn-chrono-primary" @onclick="RunTestsAsync" disabled="@isRunning">
<i class="bi bi-play-circle-fill @(isRunning ? "spin" : "")"></i>
@(isRunning ? "Running Test Suite..." : "Run All Database Tests")
</button>
</div>
</div>
<!-- Scorecard Summary -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="metric-card">
<div class="metric-icon-box metric-icon-blue">
<i class="bi bi-list-check"></i>
</div>
<div>
<div class="metric-value">@testResults.Count</div>
<div class="metric-label">Total Tests</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="metric-card">
<div class="metric-icon-box metric-icon-green">
<i class="bi bi-check-circle-fill"></i>
</div>
<div>
<div class="metric-value text-success">@testResults.Count(t => t.Status == TestStatus.Passed)</div>
<div class="metric-label">Passed</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="metric-card">
<div class="metric-icon-box @(testResults.Any(t => t.Status == TestStatus.Failed) ? "metric-icon-red" : "metric-icon-purple")">
<i class="bi @(testResults.Any(t => t.Status == TestStatus.Failed) ? "bi-x-circle-fill text-danger" : "bi-shield-check")"></i>
</div>
<div>
<div class="metric-value @(testResults.Any(t => t.Status == TestStatus.Failed) ? "text-danger" : "")">
@testResults.Count(t => t.Status == TestStatus.Failed)
</div>
<div class="metric-label">Failed</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="metric-card">
<div class="metric-icon-box metric-icon-yellow">
<i class="bi bi-stopwatch-fill"></i>
</div>
<div>
<div class="metric-value">@(testResults.Sum(t => t.DurationMs))ms</div>
<div class="metric-label">Total Duration</div>
</div>
</div>
</div>
</div>
@if (isRunning)
{
<div class="chrono-card mb-4 border-primary" style="background-color: rgba(88, 166, 255, 0.08);">
<div class="d-flex align-items-center gap-3">
<div class="spinner-border text-primary" role="status"></div>
<div>
<h6 class="text-primary fw-bold mb-0">Executing Test Suite...</h6>
<span class="text-secondary small">@currentProgressStatus</span>
</div>
</div>
</div>
}
<!-- Test Cases List -->
<div class="chrono-card">
<div class="chrono-card-header">
<h5 class="chrono-card-title">
<i class="bi bi-card-checklist text-primary"></i>
Test Suite Execution Results
</h5>
<div class="d-flex align-items-center gap-2">
<span class="text-secondary small">Target Provider:</span>
<span class="badge @(ConfigService.ProviderType == DatabaseProviderType.PostgreSQL ? "bg-primary" : "bg-purple")">
@ConfigService.ProviderType
</span>
</div>
</div>
@if (!testResults.Any() && !isRunning)
{
<div class="text-center py-5 text-secondary">
<i class="bi bi-play-circle fs-1 d-block mb-2"></i>
<h5>Test Suite Ready</h5>
<p class="small">Click "Run All Database Tests" above to execute tests against the database.</p>
<button class="btn btn-chrono-primary mt-2" @onclick="RunTestsAsync">
<i class="bi bi-play-circle-fill"></i> Start Test Run
</button>
</div>
}
else
{
<div class="d-flex flex-column gap-3">
@foreach (var test in testResults)
{
<div class="p-3 rounded bg-dark border @GetBorderClass(test.Status)">
<div class="d-flex align-items-center justify-content-between mb-2">
<div class="d-flex align-items-center gap-2">
<span class="badge bg-secondary font-monospace">@test.Id</span>
<span class="fw-bold text-white fs-6">@test.Name</span>
<span class="badge bg-dark border border-secondary text-secondary small">@test.Category</span>
</div>
<div class="d-flex align-items-center gap-3">
<span class="text-secondary small font-monospace">@(test.DurationMs)ms</span>
<span class="badge @GetBadgeClass(test.Status) px-3 py-2">
@if (test.Status == TestStatus.Running)
{
<span class="spinner-border spinner-border-sm me-1" role="status"></span>
}
else if (test.Status == TestStatus.Passed)
{
<i class="bi bi-check-lg me-1"></i>
}
else if (test.Status == TestStatus.Failed)
{
<i class="bi bi-x-lg me-1"></i>
}
@test.Status.ToString().ToUpper()
</span>
</div>
</div>
<p class="text-secondary small mb-2">@test.Description</p>
@if (!string.IsNullOrEmpty(test.ErrorMessage))
{
<div class="alert alert-danger p-2 mb-2 small">
<i class="bi bi-exclamation-triangle-fill me-1"></i>
<strong>Failure:</strong> @test.ErrorMessage
</div>
}
<!-- Expandable Logs -->
<div class="mt-2">
<button class="btn btn-sm btn-outline-secondary py-0 px-2" style="font-size: 0.78rem;"
@onclick="() => ToggleLogs(test.Id)">
<i class="bi @(expandedTestLogs.Contains(test.Id) ? "bi-chevron-up" : "bi-chevron-down") me-1"></i>
@(expandedTestLogs.Contains(test.Id) ? "Hide Log Details" : $"View Logs ({test.Logs.Count} events)")
</button>
@if (expandedTestLogs.Contains(test.Id))
{
<div class="log-console mt-2">
@if (test.Logs.Any())
{
@foreach (var log in test.Logs)
{
<div>@log</div>
}
}
else
{
<div class="text-secondary fst-italic">No logs recorded.</div>
}
</div>
}
</div>
</div>
}
</div>
}
</div>
@code {
private List<DatabaseTestCaseResult> testResults = [];
private HashSet<string> expandedTestLogs = [];
private bool isRunning;
private string currentProgressStatus = "";
protected override void OnInitialized()
{
ConfigService.OnConfigurationChanged += HandleConfigChanged;
}
private void HandleConfigChanged()
{
ClearResults();
InvokeAsync(StateHasChanged);
}
private async Task RunTestsAsync()
{
isRunning = true;
testResults.Clear();
expandedTestLogs.Clear();
currentProgressStatus = "Initializing test suite...";
StateHasChanged();
testResults = await DbService.RunAllTestsAsync(msg =>
{
currentProgressStatus = msg;
InvokeAsync(StateHasChanged);
});
// Automatically expand any failed tests for convenience
foreach (var failed in testResults.Where(t => t.Status == TestStatus.Failed))
{
expandedTestLogs.Add(failed.Id);
}
isRunning = false;
StateHasChanged();
}
private void ClearResults()
{
testResults.Clear();
expandedTestLogs.Clear();
}
private void ToggleLogs(string testId)
{
if (expandedTestLogs.Contains(testId))
expandedTestLogs.Remove(testId);
else
expandedTestLogs.Add(testId);
}
private static string GetBorderClass(TestStatus status) => status switch
{
TestStatus.Passed => "border-success border-opacity-50",
TestStatus.Failed => "border-danger border-opacity-75",
TestStatus.Running => "border-primary border-opacity-50",
_ => "border-secondary border-opacity-25"
};
private static string GetBadgeClass(TestStatus status) => status switch
{
TestStatus.Passed => "bg-success",
TestStatus.Failed => "bg-danger",
TestStatus.Running => "bg-primary",
_ => "bg-secondary"
};
public void Dispose()
{
ConfigService.OnConfigurationChanged -= HandleConfigChanged;
}
}