using Microsoft.Data.Sqlite; using VoiceCat.Server.Data; namespace VoiceCat.Tests; public sealed class AccountStoreTests { [Fact] public void UnsupportedSchemaIsRejectedWithoutCreatingAccountTables() { string path = Path.Combine(Path.GetTempPath(), "voicecat-future-" + Guid.NewGuid().ToString("N") + ".db"); try { SQLitePCL.Batteries_V2.Init(); using var connection = new SqliteConnection(new SqliteConnectionStringBuilder { DataSource = path, Pooling = false }.ToString()); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = "CREATE TABLE server_meta (key TEXT PRIMARY KEY,value TEXT NOT NULL); INSERT INTO server_meta VALUES ('schema_version','99');"; command.ExecuteNonQuery(); Assert.Throws(() => new AccountStore(path)); command.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE name='accounts'"; Assert.Equal(0L, command.ExecuteScalar()); command.CommandText = "SELECT value FROM server_meta WHERE key='schema_version'"; Assert.Equal("99", command.ExecuteScalar()); } finally { File.Delete(path); File.Delete(path + "-wal"); File.Delete(path + "-shm"); } } [Fact] public async Task AccountsSurviveRestartAndFailedAuthDoesNotChangeLastLogin() { string directory = Path.Combine(Path.GetTempPath(), "voicecat-db-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(directory); string path = Path.Combine(directory, "voicecat.db"); try { Account account; using (var store = new AccountStore(path)) account = await store.CreateAccountAsync("admin'", "secret", true); using (var store = new AccountStore(path)) { Assert.Null(await store.AuthenticateAsync("admin'", "wrong")); Assert.Null(await store.AuthenticateAsync("missing", "secret")); using var connection = new SqliteConnection(new SqliteConnectionStringBuilder { DataSource = path, Pooling = false }.ToString()); connection.Open(); using var command = connection.CreateCommand(); command.CommandText = "SELECT last_login FROM accounts WHERE id=$id"; command.Parameters.AddWithValue("$id", account.Id); Assert.Equal(0L, command.ExecuteScalar()); Account authenticated = Assert.IsType(await store.AuthenticateAsync("admin'", "secret")); Assert.Equal(account.Id, authenticated.Id); Assert.True(authenticated.IsAdmin); Assert.True(authenticated.LastLogin > 0); } } finally { Directory.Delete(directory, true); } } [Fact] public void VersionTwoDatabaseMigratesDredAsDisabled() { string directory = Path.Combine(Path.GetTempPath(), "voicecat-v2-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(directory); string path = Path.Combine(directory, "voicecat.db"); try { using (var store = new AccountStore(path)) store.LoadChannels(); using (var connection = new SqliteConnection(new SqliteConnectionStringBuilder { DataSource = path, Pooling = false }.ToString())) { connection.Open(); using var command = connection.CreateCommand(); command.CommandText = "ALTER TABLE channels DROP COLUMN audio_dred; UPDATE server_meta SET value='2' WHERE key='schema_version';"; command.ExecuteNonQuery(); } using (var migrated = new AccountStore(path)) Assert.All(migrated.LoadChannels(), channel => Assert.False(channel.Audio.Dred)); using var verify = new SqliteConnection(new SqliteConnectionStringBuilder { DataSource = path, Pooling = false }.ToString()); verify.Open(); using var query = verify.CreateCommand(); query.CommandText = "SELECT value FROM server_meta WHERE key='schema_version'"; Assert.Equal("3", query.ExecuteScalar()); } finally { Directory.Delete(directory, true); } } }