Skip to content

Commit beacf78

Browse files
committed
- Add comprehensive unit tests
1 parent 3d506e9 commit beacf78

38 files changed

Lines changed: 780 additions & 97 deletions

src/FeatureOne.File/FileRecord.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
using System.Collections.Generic;
2-
using System.Linq;
3-
41
namespace FeatureOne.File
52
{
63
public class FileRecord

src/FeatureOne.File/FileState.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using Microsoft.Extensions.DependencyInjection;
32

43
namespace FeatureOne.File
54
{

src/FeatureOne.SQL/SQLConfiguration.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using FeatureOne.Cache;
32

43
namespace FeatureOne.SQL

src/FeatureOne/Cache/FeatureCache.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Runtime.Caching;
32

43
namespace FeatureOne.Cache

src/FeatureOne/Validation/ConfigurationValidator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Text.RegularExpressions;
55
using FeatureOne.Core;
66
using FeatureOne.Core.Toggles.Conditions;
7-
using FeatureOne;
87

98
namespace FeatureOne.Validation
109
{
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using Moq;
2+
3+
namespace FeatureOne.Tests;
4+
5+
[TestFixture]
6+
public class BackwardCompatibilityTest
7+
{
8+
[Test]
9+
public void Integration_BackwardCompatibility_ExistingFeatures()
10+
{
11+
// Arrange - Test that existing feature configurations still work
12+
var mockProvider = new Mock<IStorageProvider>();
13+
var oldStyleFeature = new Feature(new FeatureName("LegacyFeature"),
14+
new Toggle(Operator.Any, new SimpleCondition { IsEnabled = true }));
15+
16+
mockProvider.Setup(p => p.GetByName("LegacyFeature")).Returns(new[] { oldStyleFeature });
17+
18+
var featureStore = new FeatureStore(mockProvider.Object);
19+
var features = new Features(featureStore);
20+
21+
// Act
22+
var result = features.IsEnabled("LegacyFeature");
23+
24+
// Assert
25+
Assert.That(result, Is.True);
26+
27+
// Also test with claims
28+
var claimsResult = features.IsEnabled("LegacyFeature", new Dictionary<string, string> { { "role", "user" } });
29+
Assert.That(claimsResult, Is.True);
30+
}
31+
32+
[Test]
33+
public void Integration_NewFeature_DateRangeCondition()
34+
{
35+
// Arrange
36+
var mockProvider = new Mock<IStorageProvider>();
37+
38+
// Create a feature with DateRangeCondition
39+
var dateRangeFeature = new Feature(new FeatureName("TimeBasedFeature"),
40+
new Toggle(Operator.Any, new DateRangeCondition
41+
{
42+
StartDate = DateTime.Now.AddDays(-1),
43+
EndDate = DateTime.Now.AddDays(1)
44+
}));
45+
46+
mockProvider.Setup(p => p.GetByName("TimeBasedFeature")).Returns(new[] { dateRangeFeature });
47+
48+
var featureStore = new FeatureStore(mockProvider.Object);
49+
var features = new Features(featureStore);
50+
51+
// Act
52+
var result = features.IsEnabled("TimeBasedFeature");
53+
54+
// Assert - Should be within date range
55+
Assert.That(result, Is.True);
56+
}
57+
58+
[Test]
59+
public void Integration_SecurityFix_ReDoSProtection()
60+
{
61+
// Arrange - Test that the ReDoS fix works in integration
62+
var mockProvider = new Mock<IStorageProvider>();
63+
64+
// Create a feature with a regex that would cause ReDoS in old version
65+
var regexFeature = new Feature(new FeatureName("ReDosProtectedFeature"),
66+
new Toggle(Operator.Any, new RegexCondition
67+
{
68+
Claim = "test",
69+
Expression = @"^([a-zA-Z0-9]+)+$", // Known ReDoS pattern
70+
Timeout = TimeSpan.FromMilliseconds(100)
71+
}));
72+
73+
mockProvider.Setup(p => p.GetByName("ReDosProtectedFeature")).Returns(new[] { regexFeature });
74+
75+
var featureStore = new FeatureStore(mockProvider.Object);
76+
var features = new Features(featureStore);
77+
78+
// Act & Assert - Should not hang and should complete quickly
79+
var startTime = DateTime.Now;
80+
var result = features.IsEnabled("ReDosProtectedFeature", new Dictionary<string, string> { { "test", new string('a', 1000) } });
81+
var endTime = DateTime.Now;
82+
83+
// Should complete quickly (under 1 second) to prove timeout is working
84+
Assert.That((endTime - startTime).TotalMilliseconds, Is.LessThan(1000));
85+
// The result may vary depending on implementation, but the important thing is no hang
86+
}
87+
88+
[Test]
89+
public void Integration_Performance_ConcurrentAccess()
90+
{
91+
// Arrange
92+
var mockProvider = new Mock<IStorageProvider>();
93+
var testFeature = new Feature(new FeatureName("ConcurrentTestFeature"),
94+
new Toggle(Operator.Any, new SimpleCondition { IsEnabled = true }));
95+
96+
mockProvider.Setup(p => p.GetByName("ConcurrentTestFeature")).Returns(new[] { testFeature });
97+
98+
var featureStore = new FeatureStore(mockProvider.Object);
99+
var features = new Features(featureStore);
100+
101+
// Act - Run multiple concurrent evaluations
102+
var tasks = new List<Task<bool>>();
103+
var startTime = DateTime.Now;
104+
105+
for (int i = 0; i < 50; i++)
106+
{
107+
var task = Task.Run(() => features.IsEnabled("ConcurrentTestFeature"));
108+
tasks.Add(task);
109+
}
110+
111+
Task.WaitAll(tasks.ToArray());
112+
var endTime = DateTime.Now;
113+
114+
// Assert - All should return true, and should complete in reasonable time
115+
Assert.That((endTime - startTime).TotalMilliseconds, Is.LessThan(5000)); // Should complete in under 5 seconds
116+
Assert.That(tasks.All(t => t.Result), Is.True);
117+
}
118+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace FeatureOne.Tests;
2+
3+
[TestFixture]
4+
public class ConstantsTest
5+
{
6+
[Test]
7+
public void Constants_DefaultRegExTimeout_ShouldBeReasonable()
8+
{
9+
// Arrange
10+
var timeout = Constants.DefaultRegExTimeout;
11+
12+
// Act & Assert
13+
Assert.That(timeout, Is.EqualTo(TimeSpan.FromSeconds(3)));
14+
Assert.That(timeout.TotalMilliseconds, Is.GreaterThan(0));
15+
}
16+
}

test/FeatureOne.Tests/CustomStoreProvider.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
using FeatureOne.Core;
2-
using FeatureOne.Core.Stores;
3-
using FeatureOne.Core.Toggles.Conditions;
4-
51
namespace FeatureOne.Tests
62
{
73
public class CustomStoreProvider : IStorageProvider
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
namespace FeatureOne.Tests;
2+
3+
[TestFixture]
4+
public class DateRangeConditionTest
5+
{
6+
[Test]
7+
public void DateRangeCondition_WithinRange_ShouldReturnTrue()
8+
{
9+
// Arrange - Create a date range that includes today
10+
var condition = new DateRangeCondition
11+
{
12+
StartDate = DateTime.Now.AddDays(-1),
13+
EndDate = DateTime.Now.AddDays(1)
14+
};
15+
16+
// Act
17+
var result = condition.Evaluate(new Dictionary<string, string>());
18+
19+
// Assert
20+
Assert.That(result, Is.True);
21+
}
22+
23+
[Test]
24+
public void DateRangeCondition_BeforeStartDate_ShouldReturnFalse()
25+
{
26+
// Arrange - Create a date range in the future
27+
var condition = new DateRangeCondition
28+
{
29+
StartDate = DateTime.Now.AddDays(1),
30+
EndDate = DateTime.Now.AddDays(2)
31+
};
32+
33+
// Act
34+
var result = condition.Evaluate(new Dictionary<string, string>());
35+
36+
// Assert
37+
Assert.That(result, Is.False);
38+
}
39+
40+
[Test]
41+
public void DateRangeCondition_AfterEndDate_ShouldReturnFalse()
42+
{
43+
// Arrange - Create a date range in the past
44+
var condition = new DateRangeCondition
45+
{
46+
StartDate = DateTime.Now.AddDays(-2),
47+
EndDate = DateTime.Now.AddDays(-1)
48+
};
49+
50+
// Act
51+
var result = condition.Evaluate(new Dictionary<string, string>());
52+
53+
// Assert
54+
Assert.That(result, Is.False);
55+
}
56+
57+
[Test]
58+
public void DateRangeCondition_NullStartDate_OnlyEndDate()
59+
{
60+
// Arrange - No start date, only end date
61+
var condition = new DateRangeCondition
62+
{
63+
StartDate = null,
64+
EndDate = DateTime.Now.AddDays(1)
65+
};
66+
67+
// Act
68+
var result = condition.Evaluate(new Dictionary<string, string>());
69+
70+
// Assert - Should be within range since there's no start date
71+
Assert.That(result, Is.True);
72+
}
73+
74+
[Test]
75+
public void DateRangeCondition_NullEndDate_OnlyStartDate()
76+
{
77+
// Arrange - No end date, only start date
78+
var condition = new DateRangeCondition
79+
{
80+
StartDate = DateTime.Now.AddDays(-1),
81+
EndDate = null
82+
};
83+
84+
// Act
85+
var result = condition.Evaluate(new Dictionary<string, string>());
86+
87+
// Assert - Should be within range since there's no end date
88+
Assert.That(result, Is.True);
89+
}
90+
91+
[Test]
92+
public void DateRangeCondition_BothDatesNull_ShouldReturnTrue()
93+
{
94+
// Arrange - Both dates null means always enabled
95+
var condition = new DateRangeCondition
96+
{
97+
StartDate = null,
98+
EndDate = null
99+
};
100+
101+
// Act
102+
var result = condition.Evaluate(new Dictionary<string, string>());
103+
104+
// Assert
105+
Assert.That(result, Is.True);
106+
}
107+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Moq;
3+
4+
namespace FeatureOne.Tests;
5+
6+
[TestFixture]
7+
public class DependencyInjectionIntegrationTest
8+
{
9+
[Test]
10+
public void Integration_DependencyInjection()
11+
{
12+
// Arrange - Test that the new DI patterns work in integration
13+
var services = new ServiceCollection();
14+
15+
var mockProvider = new Mock<IStorageProvider>();
16+
var mockLogger = new Mock<IFeatureLogger>();
17+
18+
var testFeature = new Feature(new FeatureName("DIIntegrationTest"),
19+
new Toggle(Operator.Any, new SimpleCondition { IsEnabled = true }));
20+
21+
mockProvider.Setup(p => p.GetByName("DIIntegrationTest")).Returns(new[] { testFeature });
22+
23+
// Use the new constructor with explicit dependencies if available
24+
// If the constructor with explicit dependencies doesn't exist, we'll test the registration
25+
var featureStore = new FeatureStore(mockProvider.Object, mockLogger.Object);
26+
var features = new Features(featureStore, mockLogger.Object);
27+
28+
// Act
29+
var result = features.IsEnabled("DIIntegrationTest");
30+
31+
// Assert
32+
Assert.That(result, Is.True);
33+
34+
// Verify logger was used (not strictly required but good to check)
35+
mockLogger.Verify(l => l.Info(It.IsAny<string>()), Times.AtMost(1));
36+
}
37+
38+
[Test]
39+
public void AddFeatureOne_ExtensionMethod_Works()
40+
{
41+
// Arrange
42+
var services = new ServiceCollection();
43+
var mockProvider = new Mock<IStorageProvider>();
44+
45+
// Act
46+
services.AddFeatureOne(serviceProvider => mockProvider.Object);
47+
48+
// Assert
49+
var serviceProvider = services.BuildServiceProvider();
50+
var features = serviceProvider.GetService<IFeatures>();
51+
52+
Assert.That(features, Is.Not.Null);
53+
}
54+
}

0 commit comments

Comments
 (0)