Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
947 changes: 472 additions & 475 deletions src/SchematicHQ.Client.Test/Cache/LocalCacheTests.cs

Large diffs are not rendered by default.

15 changes: 5 additions & 10 deletions src/SchematicHQ.Client.Test/Cache/RedisCacheTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Moq;
using NUnit.Framework;
using SchematicHQ.Client.Cache;
using SchematicHQ.Client.Datastream;
Expand Down Expand Up @@ -33,7 +28,7 @@ public void Constructor_WithRedisCacheConfig_ParsesCorrectly()
{
try
{
var cache = new RedisCache<string>(config);
var cache = new RedisCache(config);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Failed to connect to Redis"))
{
Expand Down Expand Up @@ -67,7 +62,7 @@ public void Constructor_WithRedisCacheConfig_MultipleEndpoints()
{
try
{
var cache = new RedisCache<string>(config);
var cache = new RedisCache(config);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Failed to connect to Redis"))
{
Expand All @@ -82,7 +77,7 @@ public void Constructor_WithRedisCacheConfig_MultipleEndpoints()
public void Constructor_WithRedisCacheConfig_NullConfig_ThrowsArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new RedisCache<string>((RedisCacheConfig)null));
Assert.Throws<ArgumentNullException>(() => new RedisCache((RedisCacheConfig)null));
}

[Test]
Expand All @@ -101,7 +96,7 @@ public void Constructor_WithRedisCacheClusterConfig_AppliesClusterSettings()
// Act & Assert
try
{
var cache = new RedisCache<string>(clusterConfig);
var cache = new RedisCache(clusterConfig);
// If we get here without Redis running, that's fine - the config was accepted
Assert.Pass("Cluster configuration was accepted and RedisCache was created");
}
Expand All @@ -127,7 +122,7 @@ public void Constructor_WithRedisCacheConfig_EmptyEndpoints_ThrowsArgumentExcept
};

// Act & Assert
var ex = Assert.Throws<ArgumentException>(() => new RedisCache<string>(config));
var ex = Assert.Throws<InvalidOperationException>(() => new RedisCache(config));
Assert.That(ex.Message, Contains.Substring("Redis endpoints cannot be null or empty"));
}

Expand Down
72 changes: 35 additions & 37 deletions src/SchematicHQ.Client.Test/Datastream/CompanyMetricsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public class CompanyMetricsTests : IDisposable

// Client and dependencies
private DatastreamClient _client;
private ICacheProvider<RulesengineCompany> _companyCache;
private ICacheProvider<string> _companyLookupCache;
private ICacheProvider _companyCache;
private ICacheProvider _companyLookupCache;

[SetUp]
public void Setup()
Expand All @@ -39,11 +39,9 @@ public void Setup()
var (client, _, _, _) = DatastreamClientTestFactory.CreateClientWithMocks();
_client = client;
// Use reflection to get the private cache fields
var cacheField = typeof(DatastreamClient).GetField("_companyCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
_companyCache = (ICacheProvider<RulesengineCompany>?)cacheField?.GetValue(_client) ?? throw new Exception("Could not get company cache");

var lookupCacheField = typeof(DatastreamClient).GetField("_companyLookupCache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
_companyLookupCache = (ICacheProvider<string>?)lookupCacheField?.GetValue(_client) ?? throw new Exception("Could not get company lookup cache");
var cacheField = typeof(DatastreamClient).GetField("_cacheProvider", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
_companyCache = (ICacheProvider?)cacheField?.GetValue(_client) ?? throw new Exception("Could not get company cache");
_companyLookupCache = (ICacheProvider?)cacheField?.GetValue(_client) ?? throw new Exception("Could not get company lookup cache");
}

[TearDown]
Expand Down Expand Up @@ -80,7 +78,7 @@ private string CompanyIdCacheKey(string id)
/// <summary>
/// Helper method to set up a company in cache using both layers
/// </summary>
private void SetupCompanyInCache(string companyJson)
private async Task SetupCompanyInCache(string companyJson)
{
var company = JsonSerializer.Deserialize<RulesengineCompany>(companyJson, new JsonSerializerOptions
{
Expand All @@ -91,34 +89,34 @@ private void SetupCompanyInCache(string companyJson)

// Layer 1: Store company object at ID key
var idKey = CompanyIdCacheKey(company.Id);
_companyCache.Set(idKey, company);
await _companyCache.Set(idKey, company);

// Layer 2: Store company ID at each resource key
if (company.Keys != null)
{
foreach (var key in company.Keys)
{
var resourceKey = ResourceKeyToCacheKey(key.Key, key.Value);
_companyLookupCache.Set(resourceKey, company.Id);
await _companyLookupCache.Set(resourceKey, company.Id);
}
}
}

/// <summary>
/// Helper method to get a company from cache using two-step lookup
/// </summary>
private RulesengineCompany? GetCompanyFromCache(Dictionary<string, string> keys)
private async Task<RulesengineCompany?> GetCompanyFromCache(Dictionary<string, string> keys)
{
if (keys.Count == 0) return null;

foreach (var key in keys)
{
var resourceKey = ResourceKeyToCacheKey(key.Key, key.Value);
var companyId = _companyLookupCache.Get(resourceKey);
var companyId = await _companyLookupCache.Get<string>(resourceKey);
if (companyId != null)
{
var idKey = CompanyIdCacheKey(companyId);
var company = _companyCache.Get(idKey);
var company = await _companyCache.Get<RulesengineCompany>(idKey);
if (company != null) return company;
}
}
Expand All @@ -128,10 +126,10 @@ private void SetupCompanyInCache(string companyJson)
/// <summary>
/// Helper method to update company metrics using the Datastream client
/// </summary>
private bool UpdateCompanyMetrics(Dictionary<string, string> companyKeys, string metricName, string period, int quantity = 1)
private async Task<bool> UpdateCompanyMetrics(Dictionary<string, string> companyKeys, string metricName, string period, int quantity = 1)
{
// Get the company from cache
var company = GetCompanyFromCache(companyKeys);
var company = await GetCompanyFromCache(companyKeys);
if (company == null || company.Metrics == null) return false;

// Find metrics matching the event name (metric name)
Expand All @@ -146,18 +144,18 @@ private bool UpdateCompanyMetrics(Dictionary<string, string> companyKeys, string

// Save the updated company back to cache using two-layer approach
var idKey = CompanyIdCacheKey(company.Id);
_companyCache.Set(idKey, company);
await _companyCache.Set(idKey, company);
foreach (var key in companyKeys)
{
var resourceKey = ResourceKeyToCacheKey(key.Key, key.Value);
_companyLookupCache.Set(resourceKey, company.Id);
await _companyLookupCache.Set(resourceKey, company.Id);
}

return true;
}

[Test]
public void UpdateCompanyMetrics_WithNoMatchingMetric_ReturnsFalse()
public async Task UpdateCompanyMetrics_WithNoMatchingMetric_ReturnsFalse()
{
// Arrange
var companyJson = @"{
Expand Down Expand Up @@ -192,17 +190,17 @@ public void UpdateCompanyMetrics_WithNoMatchingMetric_ReturnsFalse()
]
}";

SetupCompanyInCache(companyJson);
await SetupCompanyInCache(companyJson);

// Act
var result = UpdateCompanyMetrics(SingleCompanyKey, "metric2", "all_time");
var result = await UpdateCompanyMetrics(SingleCompanyKey, "metric2", "all_time");

// Assert
Assert.That(result, Is.False, "Should return false when no matching metric is found");
}

[Test]
public void UpdateCompanyMetrics_WithMatchingMetric_UpdatesValueWithDefaultQuantity()
public async Task UpdateCompanyMetrics_WithMatchingMetric_UpdatesValueWithDefaultQuantity()
{
// Arrange
var companyJson = @"{
Expand Down Expand Up @@ -237,14 +235,14 @@ public void UpdateCompanyMetrics_WithMatchingMetric_UpdatesValueWithDefaultQuant
]
}";

SetupCompanyInCache(companyJson);
await SetupCompanyInCache(companyJson);

// Act
var result = UpdateCompanyMetrics(SingleCompanyKey, "metric1", "all_time");
var result = await UpdateCompanyMetrics(SingleCompanyKey, "metric1", "all_time");

// Assert
Assert.That(result, Is.True, "Should return true when metric is updated");
var company = GetCompanyFromCache(SingleCompanyKey);
var company = await GetCompanyFromCache(SingleCompanyKey);
Assert.That(company, Is.Not.Null);

var metric = company?.Metrics?.FirstOrDefault(m => m.EventSubtype == "metric1" && m.Period.Value == "all_time");
Expand All @@ -253,7 +251,7 @@ public void UpdateCompanyMetrics_WithMatchingMetric_UpdatesValueWithDefaultQuant
}

[Test]
public void UpdateCompanyMetrics_WithSpecificQuantity_UpdatesMetricValue()
public async Task UpdateCompanyMetrics_WithSpecificQuantity_UpdatesMetricValue()
{
// Arrange
var companyJson = @"{
Expand Down Expand Up @@ -288,14 +286,14 @@ public void UpdateCompanyMetrics_WithSpecificQuantity_UpdatesMetricValue()
]
}";

SetupCompanyInCache(companyJson);
await SetupCompanyInCache(companyJson);

// Act
var result = UpdateCompanyMetrics(SingleCompanyKey, "metric1", "all_time", 5);
var result = await UpdateCompanyMetrics(SingleCompanyKey, "metric1", "all_time", 5);

// Assert
Assert.That(result, Is.True, "Should return true when metric is updated");
var company = GetCompanyFromCache(SingleCompanyKey);
var company = await GetCompanyFromCache(SingleCompanyKey);
Assert.That(company, Is.Not.Null);

var metric = company?.Metrics?.FirstOrDefault(m => m.EventSubtype == "metric1" && m.Period.Value == "all_time");
Expand All @@ -304,7 +302,7 @@ public void UpdateCompanyMetrics_WithSpecificQuantity_UpdatesMetricValue()
}

[Test]
public void UpdateCompanyMetrics_WithMultipleKeys_UpdatesAllCacheEntries()
public async Task UpdateCompanyMetrics_WithMultipleKeys_UpdatesAllCacheEntries()
{
// Arrange
var companyJson = @"{
Expand Down Expand Up @@ -340,24 +338,24 @@ public void UpdateCompanyMetrics_WithMultipleKeys_UpdatesAllCacheEntries()
]
}";

SetupCompanyInCache(companyJson);
await SetupCompanyInCache(companyJson);

// Act
var result = UpdateCompanyMetrics(MultipleCompanyKeys, "metric1", "all_time", 5);
var result = await UpdateCompanyMetrics(MultipleCompanyKeys, "metric1", "all_time", 5);

// Assert
Assert.That(result, Is.True, "Should return true when metrics are updated");

// Check company retrieved by primary key
var companyByPrimary = GetCompanyFromCache(new Dictionary<string, string> { ["company_id"] = "12345" });
var companyByPrimary = await GetCompanyFromCache(new Dictionary<string, string> { ["company_id"] = "12345" });
Assert.That(companyByPrimary, Is.Not.Null);

var metricByPrimary = companyByPrimary?.Metrics?.FirstOrDefault(m => m.EventSubtype == "metric1" && m.Period.Value == "all_time");
Assert.That(metricByPrimary, Is.Not.Null);
Assert.That(metricByPrimary?.Value, Is.EqualTo(105));

// Check company retrieved by secondary key
var companyBySecondary = GetCompanyFromCache(new Dictionary<string, string> { ["secondary_id"] = "secondary123" });
var companyBySecondary = await GetCompanyFromCache(new Dictionary<string, string> { ["secondary_id"] = "secondary123" });
Assert.That(companyBySecondary, Is.Not.Null);

var metricBySecondary = companyBySecondary?.Metrics?.FirstOrDefault(m => m.EventSubtype == "metric1");
Expand All @@ -366,7 +364,7 @@ public void UpdateCompanyMetrics_WithMultipleKeys_UpdatesAllCacheEntries()
}

[Test]
public void UpdateCompanyMetrics_WithMultipleMetrics_UpdatesAllMatchingMetrics()
public async Task UpdateCompanyMetrics_WithMultipleMetrics_UpdatesAllMatchingMetrics()
{
// Arrange
var companyJson = @"{
Expand Down Expand Up @@ -421,14 +419,14 @@ public void UpdateCompanyMetrics_WithMultipleMetrics_UpdatesAllMatchingMetrics()
]
}";

SetupCompanyInCache(companyJson);
await SetupCompanyInCache(companyJson);

// Act
var result = UpdateCompanyMetrics(SingleCompanyKey, "metric1", "current_month", 10);
var result = await UpdateCompanyMetrics(SingleCompanyKey, "metric1", "current_month", 10);

// Assert
Assert.That(result, Is.True, "Should return true when metric is updated");
var company = GetCompanyFromCache(SingleCompanyKey);
var company = await GetCompanyFromCache(SingleCompanyKey);
Assert.That(company, Is.Not.Null);

// All metric1 metrics should be updated regardless of period
Expand Down
Loading