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
+34
View File
@@ -0,0 +1,34 @@
using Microsoft.Playwright;
using Microsoft.Playwright.NUnit;
namespace Tests;
[Parallelizable(ParallelScope.Self)]
[TestFixture]
public class CardsPageTests : PageTest
{
private const string BaseUrl = "http://localhost:5256";
[Test]
public async Task CardsPage_InspectRendering()
{
var consoleMessages = new List<string>();
var pageErrors = new List<string>();
Page.Console += (_, msg) => Console.WriteLine($"[BROWSER {msg.Type}] {msg.Text}");
Page.PageError += (_, err) => Console.WriteLine($"[BROWSER PAGE ERROR] {err}");
await Page.GotoAsync($"{BaseUrl}/cards");
await Page.WaitForSelectorAsync("h1", new() { Timeout = 30000 });
// Give Blazor WASM a moment to render
await Page.WaitForTimeoutAsync(3000);
var cardCells = Page.Locator(".card-cell");
var count = await cardCells.CountAsync();
Console.WriteLine($"Found {count} card cells");
var html = await Page.ContentAsync();
File.WriteAllText("cards_page_dump.html", html);
}
}
+217
View File
@@ -0,0 +1,217 @@
using Cloud.Models;
using DatabaseTest.Models;
using DatabaseTest.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using Model;
namespace Tests;
[TestFixture]
public class DatabaseServiceTests
{
private IDatabaseConfigService _configService = null!;
private IDatabaseService _dbService = null!;
private string _testDbName = null!;
[SetUp]
public async Task Setup()
{
_testDbName = $"chrono_test_{Guid.NewGuid():N}";
var config = new ConfigurationBuilder().Build();
var configService = new DatabaseConfigService(config);
configService.SetProvider(DatabaseProviderType.InMemory);
configService.SetInMemoryDbName(_testDbName);
_configService = configService;
_dbService = new DatabaseService(configService, NullLogger<DatabaseService>.Instance);
await _dbService.EnsureDatabaseCreatedAsync();
}
[Test]
public async Task CheckHealth_InMemory_ReturnsConnected()
{
var health = await _dbService.CheckHealthAsync();
Assert.That(health.IsConnected, Is.True);
Assert.That(health.ProviderType, Is.EqualTo(DatabaseProviderType.InMemory));
}
[Test]
public async Task CardNotes_FullCrudLifecycle()
{
var cardName = "Test Hero " + Guid.NewGuid().ToString("N");
var initialNote = new CardNote { CardName = cardName, Note = "Initial note text" };
// 1. Create
var createResult = await _dbService.SaveCardNoteAsync(initialNote, isNew: true);
Assert.That(createResult.Success, Is.True);
// 2. Read
var fetched = await _dbService.GetCardNoteAsync(cardName);
Assert.That(fetched, Is.Not.Null);
Assert.That(fetched!.Note, Is.EqualTo("Initial note text"));
// 3. Update
fetched.Note = "Updated note text";
var updateResult = await _dbService.SaveCardNoteAsync(fetched, isNew: false);
Assert.That(updateResult.Success, Is.True);
var updated = await _dbService.GetCardNoteAsync(cardName);
Assert.That(updated!.Note, Is.EqualTo("Updated note text"));
// 4. Delete
var deleteResult = await _dbService.DeleteCardNoteAsync(cardName);
Assert.That(deleteResult.Success, Is.True);
var deleted = await _dbService.GetCardNoteAsync(cardName);
Assert.That(deleted, Is.Null);
}
[Test]
public async Task DuplicateCardNote_ReturnsFailureResult()
{
var cardName = "Unique Card " + Guid.NewGuid().ToString("N");
var note1 = new CardNote { CardName = cardName, Note = "First" };
var note2 = new CardNote { CardName = cardName, Note = "Second" };
var res1 = await _dbService.SaveCardNoteAsync(note1, isNew: true);
Assert.That(res1.Success, Is.True);
var res2 = await _dbService.SaveCardNoteAsync(note2, isNew: true);
Assert.That(res2.Success, Is.False);
Assert.That(res2.Message, Does.Contain("already exists"));
}
[Test]
public async Task UserDecks_FullCrudLifecycle_WithJsonCollections()
{
var deckId = Guid.NewGuid();
var deck = new UserDeck
{
Id = deckId,
Name = "Control Deck",
Season = "Season 1",
Notes = "Strategic combo control",
Cards = new List<string> { "Chronos, Time Shifter", "Temporal Rift", "Chronos, Time Shifter" },
Divers = new List<string> { "Chronos, Time Shifter" }
};
// 1. Create
var createResult = await _dbService.SaveUserDeckAsync(deck, isNew: true);
Assert.That(createResult.Success, Is.True);
// 2. Read
var fetched = await _dbService.GetUserDeckAsync(deckId);
Assert.That(fetched, Is.Not.Null);
Assert.That(fetched!.Name, Is.EqualTo("Control Deck"));
Assert.That(fetched.Cards.Count, Is.EqualTo(3));
Assert.That(fetched.Divers.Count, Is.EqualTo(1));
// 3. Update
fetched.Cards.Add("Added Card");
fetched.Name = "Control Deck v2";
var updateResult = await _dbService.SaveUserDeckAsync(fetched, isNew: false);
Assert.That(updateResult.Success, Is.True);
var updated = await _dbService.GetUserDeckAsync(deckId);
Assert.That(updated!.Name, Is.EqualTo("Control Deck v2"));
Assert.That(updated.Cards.Count, Is.EqualTo(4));
// 4. Delete
var deleteResult = await _dbService.DeleteUserDeckAsync(deckId);
Assert.That(deleteResult.Success, Is.True);
var deleted = await _dbService.GetUserDeckAsync(deckId);
Assert.That(deleted, Is.Null);
}
[Test]
public async Task PasskeyCredentials_FullCrudLifecycle_WithBinaryData()
{
var credId = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0xAA, 0xBB, 0xCC };
var pubKey = new byte[] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80 };
var userHandle = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF };
var passkey = new PasskeyCredential
{
CredentialId = credId,
PublicKey = pubKey,
UserHandle = userHandle,
AaGuid = Guid.NewGuid().ToString(),
SignCount = 5
};
// 1. Create
var createResult = await _dbService.SavePasskeyAsync(passkey, isNew: true);
Assert.That(createResult.Success, Is.True);
Assert.That(passkey.Id, Is.GreaterThan(0));
// 2. Read & Binary Verification
var fetched = await _dbService.GetPasskeyAsync(passkey.Id);
Assert.That(fetched, Is.Not.Null);
Assert.That(fetched!.CredentialId, Is.EqualTo(credId));
Assert.That(fetched.PublicKey, Is.EqualTo(pubKey));
Assert.That(fetched.UserHandle, Is.EqualTo(userHandle));
Assert.That(fetched.SignCount, Is.EqualTo(5));
// 3. Update
fetched.SignCount = 10;
var updateResult = await _dbService.SavePasskeyAsync(fetched, isNew: false);
Assert.That(updateResult.Success, Is.True);
var updated = await _dbService.GetPasskeyAsync(passkey.Id);
Assert.That(updated!.SignCount, Is.EqualTo(10));
// 4. Delete
var deleteResult = await _dbService.DeletePasskeyAsync(passkey.Id);
Assert.That(deleteResult.Success, Is.True);
var deleted = await _dbService.GetPasskeyAsync(passkey.Id);
Assert.That(deleted, Is.Null);
}
[Test]
public async Task SeedAndResetDatabase_WorksCorrectly()
{
// 1. Seed
var seedResult = await _dbService.SeedSampleDataAsync();
Assert.That(seedResult.Success, Is.True);
Assert.That(seedResult.CardNotesSeeded, Is.GreaterThan(0));
Assert.That(seedResult.UserDecksSeeded, Is.GreaterThan(0));
Assert.That(seedResult.PasskeysSeeded, Is.GreaterThan(0));
var notes = await _dbService.GetCardNotesAsync();
var decks = await _dbService.GetUserDecksAsync();
var passkeys = await _dbService.GetPasskeysAsync();
Assert.That(notes.Count, Is.GreaterThan(0));
Assert.That(decks.Count, Is.GreaterThan(0));
Assert.That(passkeys.Count, Is.GreaterThan(0));
// 2. Reset
var resetResult = await _dbService.ResetDatabaseAsync();
Assert.That(resetResult.Success, Is.True);
var notesAfterReset = await _dbService.GetCardNotesAsync();
var decksAfterReset = await _dbService.GetUserDecksAsync();
var passkeysAfterReset = await _dbService.GetPasskeysAsync();
Assert.That(notesAfterReset, Is.Empty);
Assert.That(decksAfterReset, Is.Empty);
Assert.That(passkeysAfterReset, Is.Empty);
}
[Test]
public async Task RunAllTestsAsync_ExecutesAndPassesCoreTests()
{
var testResults = await _dbService.RunAllTestsAsync();
Assert.That(testResults, Is.Not.Empty);
var failed = testResults.Where(t => t.Status == TestStatus.Failed).ToList();
Assert.That(failed, Is.Empty, () => $"Failed tests: {string.Join(", ", failed.Select(f => $"{f.Name}: {f.ErrorMessage}"))}");
var passedCount = testResults.Count(t => t.Status == TestStatus.Passed);
Assert.That(passedCount, Is.GreaterThanOrEqualTo(6));
}
}
+95
View File
@@ -0,0 +1,95 @@
namespace Tests;
using Microsoft.JSInterop;
using Web.Services;
[TestFixture]
public class NetworkStatusTests
{
private class MockJSRuntime : IJSRuntime
{
public bool ReturnOnlineValue { get; set; } = true;
public bool InitializeCalled { get; private set; }
public bool DisposeCalled { get; private set; }
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args)
{
if (identifier == "networkStatus.initialize")
{
InitializeCalled = true;
return new ValueTask<TValue>((TValue)(object)ReturnOnlineValue);
}
return new ValueTask<TValue>(default(TValue)!);
}
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object?[]? args)
{
return InvokeAsync<TValue>(identifier, args);
}
}
[Test]
public void NetworkStatusService_InitialState_IsOnlineByDefault()
{
var jsRuntime = new MockJSRuntime();
using var service = new NetworkStatusService(jsRuntime);
Assert.That(service.IsOnline, Is.True);
}
[Test]
public async Task NetworkStatusService_InitializeAsync_SetsStatusFromBrowser()
{
var jsRuntime = new MockJSRuntime { ReturnOnlineValue = false };
var service = new NetworkStatusService(jsRuntime);
var eventFired = false;
bool? reportedStatus = null;
service.StatusChanged += status =>
{
eventFired = true;
reportedStatus = status;
};
await service.InitializeAsync();
Assert.That(jsRuntime.InitializeCalled, Is.True);
Assert.That(service.IsOnline, Is.False);
Assert.That(eventFired, Is.True);
Assert.That(reportedStatus, Is.False);
await service.DisposeAsync();
}
[Test]
public void NetworkStatusService_SetOnlineStatus_FiresEventWhenChanged()
{
var jsRuntime = new MockJSRuntime();
using var service = new NetworkStatusService(jsRuntime);
var eventCount = 0;
bool? currentStatus = null;
service.StatusChanged += status =>
{
eventCount++;
currentStatus = status;
};
// Transition to offline
service.SetOnlineStatus(false);
Assert.That(service.IsOnline, Is.False);
Assert.That(eventCount, Is.EqualTo(1));
Assert.That(currentStatus, Is.False);
// Setting same status again should not re-trigger event
service.SetOnlineStatus(false);
Assert.That(eventCount, Is.EqualTo(1));
// Transition back to online
service.SetOnlineStatus(true);
Assert.That(service.IsOnline, Is.True);
Assert.That(eventCount, Is.EqualTo(2));
Assert.That(currentStatus, Is.True);
}
}
+6 -6
View File
@@ -11,7 +11,7 @@ public class TestSetup
[OneTimeSetUp]
public async Task BeforeAllTests()
{
Console.WriteLine("[DEBUG_LOG] Starting Cloud...");
Console.WriteLine("[DEBUG_LOG] Starting Web server...");
// Find solution root
var currentDir = AppContext.BaseDirectory;
@@ -20,12 +20,12 @@ public class TestSetup
if (currentDir == null) throw new Exception("Could not find Chrono.sln to determine project path.");
var projectPath = Path.Combine(currentDir, "Cloud", "Cloud.csproj");
var projectPath = Path.Combine(currentDir, "Web", "Web.csproj");
var startInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"run --project \"{projectPath}\" --no-build",
Arguments = $"run --project \"{projectPath}\" --urls \"{ServerUrl}\" --no-build",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
@@ -71,10 +71,10 @@ public class TestSetup
if (!isReady)
{
_serverProcess.Kill(true);
throw new Exception($"Cloud did not start within {timeout.TotalSeconds} seconds.");
throw new Exception($"Web server did not start within {timeout.TotalSeconds} seconds.");
}
Console.WriteLine("[DEBUG_LOG] Cloud is ready.");
Console.WriteLine("[DEBUG_LOG] Web server is ready.");
}
[OneTimeTearDown]
@@ -82,7 +82,7 @@ public class TestSetup
{
if (_serverProcess != null && !_serverProcess.HasExited)
{
Console.WriteLine("[DEBUG_LOG] Stopping Cloud...");
Console.WriteLine("[DEBUG_LOG] Stopping Web server...");
_serverProcess.Kill(true);
_serverProcess.Dispose();
}
+2
View File
@@ -19,6 +19,8 @@
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj"/>
<ProjectReference Include="..\DatabaseTest\DatabaseTest.csproj"/>
<ProjectReference Include="..\Web\Web.csproj"/>
</ItemGroup>
<ItemGroup>