264 lines
10 KiB
Plaintext
264 lines
10 KiB
Plaintext
@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;
|
|
}
|
|
}
|