Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47e72ee1a7 | |||
| f3de5324db | |||
| 4dd6db886f | |||
| 4b82db8ea2 | |||
| 9e7f26f7a6 |
@@ -13,7 +13,7 @@ To ensure high-quality and consistent commits, please follow these guidelines:
|
||||
3. **Test your changes**
|
||||
- Run all unit tests before committing.
|
||||
- Add or update xUnit tests as needed.
|
||||
- Use FluentAssertions for assertions and Moq for mocking in tests.
|
||||
- Use AwesomeAssertions for assertions and Moq for mocking in tests.
|
||||
|
||||
4. **Review your changes**
|
||||
- Double-check for accidental debug code or commented-out code.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Speckle.InterfaceGenerator;
|
||||
|
||||
namespace Speckle.Sdk.Caching;
|
||||
|
||||
/// <summary>
|
||||
/// This mocks away the file system operations for testing purposes.
|
||||
/// </summary>
|
||||
[GenerateAutoInterface]
|
||||
public class FileSystem : IFileSystem
|
||||
{
|
||||
public bool DirectoryExists(string path) => Directory.Exists(path);
|
||||
|
||||
public void CreateDirectory(string path) => Directory.CreateDirectory(path);
|
||||
|
||||
public IEnumerable<string> EnumerateFiles(string path) => Directory.EnumerateFiles(path);
|
||||
|
||||
public void DeleteFile(string path) => File.Delete(path);
|
||||
|
||||
public long GetFileSize(string path) => new FileInfo(path).Length;
|
||||
|
||||
public string Combine(params string[] paths) => Path.Combine(paths);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Caching;
|
||||
|
||||
/// <summary>
|
||||
/// This class manages the cache for model data, providing methods to get stream paths, clear the cache, and calculate cache size.
|
||||
/// </summary>
|
||||
[GenerateAutoInterface]
|
||||
public class ModelCacheManager(ILogger<ModelCacheManager> logger, IFileSystem fileSystem) : IModelCacheManager
|
||||
{
|
||||
private const string DATA_FOLDER = "Projects";
|
||||
private static readonly string s_basePath = SpecklePathProvider.UserSpeckleFolderPath;
|
||||
|
||||
private static string CacheFolder => Path.Combine(s_basePath, DATA_FOLDER);
|
||||
|
||||
public string GetStreamPath(string streamId) => GetDbPath(streamId);
|
||||
|
||||
public static string GetDbPath(string streamId)
|
||||
{
|
||||
var db = Path.Combine(CacheFolder, $"{streamId}.db");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(CacheFolder); //ensure dir is there
|
||||
return db;
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException)
|
||||
{
|
||||
throw new TransportException($"Path was invalid or could not be created {db}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearCache()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!fileSystem.DirectoryExists(CacheFolder))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var db in fileSystem.EnumerateFiles(CacheFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
fileSystem.DeleteFile(db);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or NotSupportedException)
|
||||
{
|
||||
logger.LogWarning(ex, "Failed to delete cache file {filePath}", db);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException)
|
||||
{
|
||||
throw new TransportException($"Cache folder could not be cleared: {CacheFolder}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public long GetCacheSize()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!fileSystem.DirectoryExists(CacheFolder))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long size = 0;
|
||||
foreach (var file in fileSystem.EnumerateFiles(CacheFolder))
|
||||
{
|
||||
try
|
||||
{
|
||||
size += fileSystem.GetFileSize(file);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or NotSupportedException)
|
||||
{
|
||||
logger.LogWarning(ex, "Failed to get size for cache file {a}", file);
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException)
|
||||
{
|
||||
throw new TransportException($"Cache folder size could not be determined: {CacheFolder}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +174,7 @@ public sealed class AccountManager(
|
||||
account.id = null!; //TODO this is gross so remove when id is nullable
|
||||
|
||||
RemoveAccount(id);
|
||||
_accountStorage.SaveObject(account.id.NotNull(), JsonConvert.SerializeObject(account));
|
||||
_accountStorage.UpdateObject(account.id.NotNull(), JsonConvert.SerializeObject(account));
|
||||
}
|
||||
|
||||
public IEnumerable<Account> GetAccounts(string serverUrl)
|
||||
@@ -407,7 +407,7 @@ public sealed class AccountManager(
|
||||
{
|
||||
account.isDefault = true;
|
||||
}
|
||||
_accountStorage.SaveObject(account.id, JsonConvert.SerializeObject(account));
|
||||
_accountStorage.UpdateObject(account.id, JsonConvert.SerializeObject(account));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Sdk.Caching;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Serialisation.Utilities;
|
||||
|
||||
namespace Speckle.Sdk.SQLite;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public class SqLiteJsonCacheManagerFactory : ISqLiteJsonCacheManagerFactory
|
||||
public class SqLiteJsonCacheManagerFactory(IModelCacheManager modelCacheManager) : ISqLiteJsonCacheManagerFactory
|
||||
{
|
||||
public const int INITIAL_CONCURRENCY = 4;
|
||||
|
||||
@@ -16,5 +16,5 @@ public class SqLiteJsonCacheManagerFactory : ISqLiteJsonCacheManagerFactory
|
||||
Create(Path.Combine(SpecklePathProvider.UserApplicationDataPath(), "Speckle", $"{scope}.db"), 1);
|
||||
|
||||
public ISqLiteJsonCacheManager CreateFromStream(string streamId) =>
|
||||
Create(SqlitePaths.GetDBPath(streamId), INITIAL_CONCURRENCY);
|
||||
Create(modelCacheManager.GetStreamPath(streamId), INITIAL_CONCURRENCY);
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Serialisation.Utilities;
|
||||
|
||||
public static class SqlitePaths
|
||||
{
|
||||
private const string APPLICATION_NAME = "Speckle";
|
||||
private const string DATA_FOLDER = "Projects";
|
||||
private static readonly string basePath = SpecklePathProvider.UserApplicationDataPath();
|
||||
|
||||
public static string BlobStorageFolder =>
|
||||
SpecklePathProvider.BlobStoragePath(Path.Combine(basePath, APPLICATION_NAME));
|
||||
|
||||
public static string GetDBPath(string streamId)
|
||||
{
|
||||
var dir = Path.Combine(basePath, APPLICATION_NAME, DATA_FOLDER);
|
||||
var db = Path.Combine(dir, $"{streamId}.db");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(dir); //ensure dir is there
|
||||
return db;
|
||||
}
|
||||
catch (Exception ex)
|
||||
when (ex is ArgumentException or IOException or UnauthorizedAccessException or NotSupportedException)
|
||||
{
|
||||
throw new TransportException($"Path was invalid or could not be created {db}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Timers;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Speckle.Sdk.Caching;
|
||||
using Speckle.Sdk.Logging;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Serialisation.Utilities;
|
||||
using Timer = System.Timers.Timer;
|
||||
|
||||
namespace Speckle.Sdk.Transports;
|
||||
@@ -28,7 +28,7 @@ public sealed class SQLiteTransport2 : IDisposable, ICloneable, ITransport, IBlo
|
||||
{
|
||||
_streamId = streamId;
|
||||
|
||||
_rootPath = SqlitePaths.GetDBPath(streamId);
|
||||
_rootPath = ModelCacheManager.GetDbPath(streamId);
|
||||
|
||||
_connectionString = $"Data Source={_rootPath};";
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed class SQLiteTransport2 : IDisposable, ICloneable, ITransport, IBlo
|
||||
private SqliteConnection Connection { get; set; }
|
||||
private readonly SemaphoreSlim _connectionLock = new(1, 1);
|
||||
|
||||
public string BlobStorageFolder => SqlitePaths.BlobStorageFolder;
|
||||
public string BlobStorageFolder => SpecklePathProvider.UserSpeckleFolderPath;
|
||||
|
||||
public void SaveBlob(Blob obj)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Speckle.Newtonsoft.Json;
|
||||
using Speckle.Sdk.Api.GraphQL.Models;
|
||||
using Speckle.Sdk.Credentials;
|
||||
using Speckle.Sdk.Helpers;
|
||||
using Speckle.Sdk.SQLite;
|
||||
using Speckle.Sdk.Testing;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Unit.Credentials;
|
||||
|
||||
public class AccountManagerTests : MoqTest
|
||||
{
|
||||
private class TestAccountFactory : IAccountFactory
|
||||
{
|
||||
public Task<Account> CreateAccount(
|
||||
Uri serverUrl,
|
||||
string speckleToken,
|
||||
string? refreshToken = default,
|
||||
CancellationToken cancellationToken = default
|
||||
) => throw new NotImplementedException();
|
||||
|
||||
public Task<ActiveUserServerInfoResponse> GetUserServerInfo(
|
||||
Uri serverUrl,
|
||||
string? authToken,
|
||||
CancellationToken ct
|
||||
) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private readonly Mock<ISpeckleApplication> _mockApplication;
|
||||
private readonly Mock<ILogger<AccountManager>> _mockLogger;
|
||||
private readonly Mock<IGraphQLClientFactory> _mockGraphQLClientFactory;
|
||||
private readonly Mock<ISpeckleHttp> _mockSpeckleHttp;
|
||||
private readonly IAccountFactory _mockAccountFactory;
|
||||
private readonly Mock<ISqLiteJsonCacheManagerFactory> _mockSqLiteJsonCacheManagerFactory;
|
||||
private readonly Mock<ISqLiteJsonCacheManager> _mockAccountStorage;
|
||||
private readonly Mock<ISqLiteJsonCacheManager> _mockAccountAddLockStorage;
|
||||
|
||||
private readonly AccountManager _accountManager;
|
||||
|
||||
public AccountManagerTests()
|
||||
{
|
||||
_mockApplication = Create<ISpeckleApplication>();
|
||||
_mockLogger = Create<ILogger<AccountManager>>(MockBehavior.Loose);
|
||||
_mockGraphQLClientFactory = Create<IGraphQLClientFactory>();
|
||||
_mockSpeckleHttp = Create<ISpeckleHttp>();
|
||||
_mockAccountFactory = new TestAccountFactory();
|
||||
_mockSqLiteJsonCacheManagerFactory = Create<ISqLiteJsonCacheManagerFactory>();
|
||||
|
||||
_mockAccountStorage = Create<ISqLiteJsonCacheManager>();
|
||||
_mockAccountAddLockStorage = Create<ISqLiteJsonCacheManager>();
|
||||
|
||||
_mockSqLiteJsonCacheManagerFactory.Setup(f => f.CreateForUser("Accounts")).Returns(_mockAccountStorage.Object);
|
||||
_mockSqLiteJsonCacheManagerFactory
|
||||
.Setup(f => f.CreateForUser("AccountAddFlow"))
|
||||
.Returns(_mockAccountAddLockStorage.Object);
|
||||
|
||||
_accountManager = new AccountManager(
|
||||
_mockApplication.Object,
|
||||
_mockLogger.Object,
|
||||
_mockGraphQLClientFactory.Object,
|
||||
_mockSpeckleHttp.Object,
|
||||
_mockAccountFactory,
|
||||
_mockSqLiteJsonCacheManagerFactory.Object
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefaultServerUrl_ReturnsDefaultUrl_WhenNoCustomUrlProvided()
|
||||
{
|
||||
// Act
|
||||
var result = _accountManager.GetDefaultServerUrl();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new Uri(AccountManager.DEFAULT_SERVER_URL), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAccount_ReturnsAccount_WhenExists()
|
||||
{
|
||||
// Arrange
|
||||
var accountId = "test-account-id";
|
||||
var account = CreateTestAccount(accountId);
|
||||
|
||||
_mockAccountStorage
|
||||
.Setup(s => s.GetAllObjects())
|
||||
.Returns(new[] { (accountId, JsonConvert.SerializeObject(account)) });
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetAccount(accountId);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(accountId, result.id);
|
||||
Assert.Equal(account.userInfo.name, result.userInfo.name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAccount_ThrowsException_WhenNotExists()
|
||||
{
|
||||
// Arrange
|
||||
var accountId = "non-existent-id";
|
||||
|
||||
_mockAccountStorage.Setup(s => s.GetAllObjects()).Returns([]);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<SpeckleAccountManagerException>(() => _accountManager.GetAccount(accountId));
|
||||
Assert.Equal($"Account {accountId} not found", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAccounts_StringParameter_CallsUriOverload()
|
||||
{
|
||||
// Arrange
|
||||
var serverUrl = "https://test.speckle.systems";
|
||||
var account = CreateTestAccount("test-account-id");
|
||||
account.serverInfo.url = serverUrl;
|
||||
|
||||
_mockAccountStorage
|
||||
.Setup(s => s.GetAllObjects())
|
||||
.Returns(new[] { (account.id, JsonConvert.SerializeObject(account)) });
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetAccounts(serverUrl).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal(serverUrl, result[0].serverInfo.url);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAccounts_UriParameter_ReturnsMatchingAccounts()
|
||||
{
|
||||
// Arrange
|
||||
var serverUri = new Uri("https://test.speckle.systems");
|
||||
var account = CreateTestAccount("test-account-id");
|
||||
account.serverInfo.url = serverUri.ToString();
|
||||
|
||||
_mockAccountStorage
|
||||
.Setup(s => s.GetAllObjects())
|
||||
.Returns(new[] { (account.id, JsonConvert.SerializeObject(account)) });
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetAccounts(serverUri).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal(serverUri.ToString(), result[0].serverInfo.url);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefaultAccount_ReturnsMarkedDefaultAccount_WhenExists()
|
||||
{
|
||||
// Arrange
|
||||
var defaultAccount = CreateTestAccount("default-account");
|
||||
defaultAccount.isDefault = true;
|
||||
|
||||
var regularAccount = CreateTestAccount("regular-account");
|
||||
|
||||
_mockAccountStorage
|
||||
.Setup(s => s.GetAllObjects())
|
||||
.Returns(
|
||||
new[]
|
||||
{
|
||||
(defaultAccount.id, JsonConvert.SerializeObject(defaultAccount)),
|
||||
(regularAccount.id, JsonConvert.SerializeObject(regularAccount)),
|
||||
}
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetDefaultAccount();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("default-account", result!.id);
|
||||
Assert.True(result.isDefault);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefaultAccount_ReturnsFirstAccount_WhenNoDefaultExists()
|
||||
{
|
||||
// Arrange
|
||||
var account1 = CreateTestAccount("account-1");
|
||||
var account2 = CreateTestAccount("account-2");
|
||||
|
||||
_mockAccountStorage
|
||||
.Setup(s => s.GetAllObjects())
|
||||
.Returns(
|
||||
new[]
|
||||
{
|
||||
(account1.id, JsonConvert.SerializeObject(account1)),
|
||||
(account2.id, JsonConvert.SerializeObject(account2)),
|
||||
}
|
||||
);
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetDefaultAccount();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("account-1", result!.id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefaultAccount_ReturnsNull_WhenNoAccounts()
|
||||
{
|
||||
// Arrange
|
||||
_mockAccountStorage.Setup(s => s.GetAllObjects()).Returns([]);
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetDefaultAccount();
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAccounts_SkipsInvalidAccounts()
|
||||
{
|
||||
// Arrange
|
||||
var validAccount = CreateTestAccount("valid-account");
|
||||
validAccount.isDefault = true;
|
||||
|
||||
var invalidAccount = new Account { id = "invalid-account" };
|
||||
|
||||
var deleteCalled = false;
|
||||
|
||||
_mockAccountStorage
|
||||
.Setup(s => s.GetAllObjects())
|
||||
.Returns(() =>
|
||||
{
|
||||
if (deleteCalled)
|
||||
{
|
||||
return [(validAccount.id, JsonConvert.SerializeObject(validAccount))];
|
||||
}
|
||||
return
|
||||
[
|
||||
(validAccount.id, JsonConvert.SerializeObject(validAccount)),
|
||||
(invalidAccount.id, JsonConvert.SerializeObject(invalidAccount)),
|
||||
];
|
||||
});
|
||||
|
||||
_mockAccountStorage.Setup(s => s.DeleteObject(invalidAccount.id)).Callback(() => deleteCalled = true);
|
||||
// Act
|
||||
var result = _accountManager.GetAccounts().ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal("valid-account", result[0].id);
|
||||
_mockAccountStorage.Verify(s => s.DeleteObject(invalidAccount.id), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveAccount_RemovesAccount()
|
||||
{
|
||||
// Arrange
|
||||
var accountId = "account-to-remove";
|
||||
|
||||
_mockAccountStorage.Setup(s => s.DeleteObject(accountId));
|
||||
_mockAccountStorage.Setup(s => s.GetAllObjects()).Returns([]);
|
||||
|
||||
// Act
|
||||
_accountManager.RemoveAccount(accountId);
|
||||
|
||||
// Assert
|
||||
_mockAccountStorage.Verify(s => s.DeleteObject(accountId), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveAccount_SetsNewDefaultAccount_WhenDefaultRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var defaultAccountId = "default-account";
|
||||
var regularAccountId = "regular-account";
|
||||
|
||||
var regularAccount = CreateTestAccount(regularAccountId);
|
||||
|
||||
_mockAccountStorage.Setup(s => s.DeleteObject(defaultAccountId));
|
||||
_mockAccountStorage
|
||||
.Setup(s => s.GetAllObjects())
|
||||
.Returns(new[] { (regularAccountId, JsonConvert.SerializeObject(regularAccount)) });
|
||||
_mockAccountStorage.Setup(s => s.UpdateObject(regularAccountId, It.IsAny<string>()));
|
||||
|
||||
// Act
|
||||
_accountManager.RemoveAccount(defaultAccountId);
|
||||
|
||||
// Assert
|
||||
_mockAccountStorage.Verify(s => s.DeleteObject(defaultAccountId), Times.Once);
|
||||
_mockAccountStorage.Verify(
|
||||
s => s.UpdateObject(regularAccountId, It.Is<string>(json => json.Contains("\"isDefault\":true"))),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangeDefaultAccount_UpdatesDefaultAccount()
|
||||
{
|
||||
// Arrange
|
||||
var account1 = CreateTestAccount("account-1");
|
||||
account1.isDefault = true;
|
||||
|
||||
var account2 = CreateTestAccount("account-2");
|
||||
|
||||
_mockAccountStorage
|
||||
.Setup(s => s.GetAllObjects())
|
||||
.Returns(
|
||||
new[]
|
||||
{
|
||||
(account1.id, JsonConvert.SerializeObject(account1)),
|
||||
(account2.id, JsonConvert.SerializeObject(account2)),
|
||||
}
|
||||
);
|
||||
|
||||
_mockAccountStorage.Setup(s => s.UpdateObject(account1.id, It.IsAny<string>()));
|
||||
_mockAccountStorage.Setup(s => s.UpdateObject(account2.id, It.IsAny<string>()));
|
||||
|
||||
// Act
|
||||
_accountManager.ChangeDefaultAccount(account2.id);
|
||||
|
||||
// Assert
|
||||
_mockAccountStorage.Verify(
|
||||
s => s.UpdateObject(account1.id, It.Is<string>(json => json.Contains("\"isDefault\":false"))),
|
||||
Times.Once
|
||||
);
|
||||
_mockAccountStorage.Verify(
|
||||
s => s.UpdateObject(account2.id, It.Is<string>(json => json.Contains("\"isDefault\":true"))),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetLocalIdentifierForAccount_ReturnsIdentifier_WhenAccountExists()
|
||||
{
|
||||
// Arrange
|
||||
var account = CreateTestAccount("test-account");
|
||||
var expectedUri = new Uri($"{account.serverInfo.url}?id={account.userInfo.id}");
|
||||
|
||||
_mockAccountStorage.Setup(s => s.GetAllObjects()).Returns(new[] { ("bad", JsonConvert.SerializeObject(account)) });
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetLocalIdentifierForAccount(account);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(expectedUri, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetLocalIdentifierForAccount_ReturnsNull_WhenAccountDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var account = CreateTestAccount("non-existent-account");
|
||||
|
||||
_mockAccountStorage.Setup(s => s.GetAllObjects()).Returns([]);
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetLocalIdentifierForAccount(account);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAccountForLocalIdentifier_ReturnsAccount_WhenMatches()
|
||||
{
|
||||
// Arrange
|
||||
var account = CreateTestAccount("test-account");
|
||||
var localIdentifier = new Uri($"{account.serverInfo.url}?id={account.userInfo.id}");
|
||||
|
||||
_mockAccountStorage.Setup(s => s.GetAllObjects()).Returns(new[] { ("bad", JsonConvert.SerializeObject(account)) });
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetAccountForLocalIdentifier(localIdentifier);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(account.id, result!.id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAccountForLocalIdentifier_ReturnsNull_WhenNoMatch()
|
||||
{
|
||||
// Arrange
|
||||
var account = CreateTestAccount("test-account");
|
||||
var localIdentifier = new Uri("https://different.url?u=different-user");
|
||||
|
||||
_mockAccountStorage.Setup(s => s.GetAllObjects()).Returns(new[] { ("bad", JsonConvert.SerializeObject(account)) });
|
||||
|
||||
// Act
|
||||
var result = _accountManager.GetAccountForLocalIdentifier(localIdentifier);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
// Helper method to create a test account
|
||||
private static Account CreateTestAccount(string id)
|
||||
{
|
||||
return new Account
|
||||
{
|
||||
id = id,
|
||||
token = "test-token",
|
||||
refreshToken = "refresh-token",
|
||||
isDefault = false,
|
||||
isOnline = true,
|
||||
userInfo = new UserInfo
|
||||
{
|
||||
id = "user-id",
|
||||
name = "Test User",
|
||||
email = "test@example.com",
|
||||
company = "Test Company",
|
||||
},
|
||||
serverInfo = new ServerInfo
|
||||
{
|
||||
name = "Test Server",
|
||||
url = "https://test.speckle.systems",
|
||||
company = "Speckle",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Speckle.Sdk.Caching;
|
||||
using Speckle.Sdk.Testing;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Unit;
|
||||
|
||||
public class ModelCacheManagerMockTests : MoqTest
|
||||
{
|
||||
private readonly Mock<IFileSystem> _fileSystemMock;
|
||||
private readonly ModelCacheManager _manager;
|
||||
|
||||
public ModelCacheManagerMockTests()
|
||||
{
|
||||
Mock<ILogger<ModelCacheManager>> loggerMock = Create<ILogger<ModelCacheManager>>(MockBehavior.Loose);
|
||||
_fileSystemMock = Create<IFileSystem>();
|
||||
_manager = new ModelCacheManager(loggerMock.Object, _fileSystemMock.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearCache_ShouldNotDeleteFiles_WhenDirectoryDoesNotExist()
|
||||
{
|
||||
_fileSystemMock.Setup(fs => fs.DirectoryExists(It.IsAny<string>())).Returns(false);
|
||||
_manager.ClearCache();
|
||||
_fileSystemMock.Verify(fs => fs.EnumerateFiles(It.IsAny<string>()), Times.Never);
|
||||
_fileSystemMock.Verify(fs => fs.DeleteFile(It.IsAny<string>()), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearCache_ShouldDeleteFiles_WhenDirectoryExists()
|
||||
{
|
||||
var files = new List<string> { "file1.db", "file2.db" };
|
||||
_fileSystemMock.Setup(fs => fs.DirectoryExists(It.IsAny<string>())).Returns(true);
|
||||
_fileSystemMock.Setup(fs => fs.EnumerateFiles(It.IsAny<string>())).Returns(files);
|
||||
foreach (var file in files)
|
||||
{
|
||||
_fileSystemMock.Setup(fs => fs.DeleteFile(file));
|
||||
}
|
||||
_manager.ClearCache();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearCache_ShouldLogWarning_WhenDeleteFileThrows()
|
||||
{
|
||||
var files = new List<string> { "file1.db" };
|
||||
_fileSystemMock.Setup(fs => fs.DirectoryExists(It.IsAny<string>())).Returns(true);
|
||||
_fileSystemMock.Setup(fs => fs.EnumerateFiles(It.IsAny<string>())).Returns(files);
|
||||
_fileSystemMock.Setup(fs => fs.DeleteFile(It.IsAny<string>())).Throws<IOException>();
|
||||
_manager.ClearCache();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCacheSize_ShouldReturnZero_WhenDirectoryDoesNotExist()
|
||||
{
|
||||
_fileSystemMock.Setup(fs => fs.DirectoryExists(It.IsAny<string>())).Returns(false);
|
||||
var size = _manager.GetCacheSize();
|
||||
size.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCacheSize_ShouldSumFileSizes()
|
||||
{
|
||||
var files = new List<string> { "file1.db", "file2.db" };
|
||||
_fileSystemMock.Setup(fs => fs.DirectoryExists(It.IsAny<string>())).Returns(true);
|
||||
_fileSystemMock.Setup(fs => fs.EnumerateFiles(It.IsAny<string>())).Returns(files);
|
||||
_fileSystemMock.Setup(fs => fs.GetFileSize("file1.db")).Returns(10);
|
||||
_fileSystemMock.Setup(fs => fs.GetFileSize("file2.db")).Returns(20);
|
||||
var size = _manager.GetCacheSize();
|
||||
size.Should().Be(30);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCacheSize_ShouldLogWarning_WhenGetFileSizeThrows()
|
||||
{
|
||||
var files = new List<string> { "file1.db" };
|
||||
_fileSystemMock.Setup(fs => fs.DirectoryExists(It.IsAny<string>())).Returns(true);
|
||||
_fileSystemMock.Setup(fs => fs.EnumerateFiles(It.IsAny<string>())).Returns(files);
|
||||
_fileSystemMock.Setup(fs => fs.GetFileSize(It.IsAny<string>())).Throws<IOException>();
|
||||
var size = _manager.GetCacheSize();
|
||||
size.Should().Be(0);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Speckle.Sdk.Caching;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Serialisation.Utilities;
|
||||
using Speckle.Sdk.Transports;
|
||||
|
||||
namespace Speckle.Sdk.Tests.Unit.Transports;
|
||||
@@ -13,7 +13,7 @@ public sealed class SQLiteTransport2Tests : TransportTests, IDisposable
|
||||
private SQLiteTransport2? _sqlite;
|
||||
|
||||
private static readonly string s_name = $"test-{Guid.NewGuid()}";
|
||||
private static readonly string s_basePath = SqlitePaths.GetDBPath(s_name);
|
||||
private static readonly string s_basePath = ModelCacheManager.GetDbPath(s_name);
|
||||
|
||||
public SQLiteTransport2Tests()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user