Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.

Commit d29f0eb

Browse files
committed
Squashed 'dotnet-server-sdk-shared-tests/' changes from ea2fb6f..1a27958
1a27958 use SDK 6.2.0 59a3648 add standard tests for big segment stores (#3) git-subtree-dir: dotnet-server-sdk-shared-tests git-subtree-split: 1a27958efe673c97bb0f8ddecdbce11c7cfcb20b
1 parent a29464c commit d29f0eb

File tree

5 files changed

+326
-2
lines changed

5 files changed

+326
-2
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ jobs:
1111
steps:
1212
- checkout
1313
- run: dotnet restore
14-
- run: dotnet build LaunchDarkly.ServerSdk.SharedTests
14+
- run: dotnet build LaunchDarkly.ServerSdk.SharedTests -f netstandard2.0
1515
- run: dotnet test LaunchDarkly.ServerSdk.SharedTests.Tests/LaunchDarkly.ServerSdk.SharedTests.Tests.csproj
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using LaunchDarkly.Sdk.Server.Interfaces;
4+
using Xunit.Abstractions;
5+
6+
using static LaunchDarkly.Sdk.Server.Interfaces.BigSegmentStoreTypes;
7+
8+
namespace LaunchDarkly.Sdk.Server.SharedTests.BigSegmentStore
9+
{
10+
public class BigSegmentStoreBaseTestsTest : BigSegmentStoreBaseTests
11+
{
12+
// This runs BigSegmentStoreBaseTests against a mock store implementation that is known to
13+
// behave as expected, to verify that the test suite logic has the correct expectations.
14+
15+
protected override BigSegmentStoreTestConfig Configuration =>
16+
new BigSegmentStoreTestConfig
17+
{
18+
StoreFactoryFunc = CreateStoreFactory,
19+
ClearDataAction = ClearData,
20+
SetMetadataAction = SetMetadata,
21+
SetSegmentsAction = SetSegments
22+
};
23+
24+
private class DataSet {
25+
public StoreMetadata? Metadata = null;
26+
public Dictionary<string, IMembership> Memberships = new Dictionary<string, IMembership>();
27+
}
28+
29+
private readonly Dictionary<string, DataSet> _allData = new Dictionary<string, DataSet>();
30+
31+
public BigSegmentStoreBaseTestsTest(ITestOutputHelper testOutput) : base(testOutput)
32+
{
33+
}
34+
35+
private IBigSegmentStoreFactory CreateStoreFactory(string prefix) =>
36+
new MockStoreFactory(GetOrCreateDataSet(prefix));
37+
38+
private Task ClearData(string prefix)
39+
{
40+
var data = GetOrCreateDataSet(prefix);
41+
data.Metadata = null;
42+
data.Memberships.Clear();
43+
return Task.CompletedTask;
44+
}
45+
46+
private Task SetMetadata(string prefix, StoreMetadata metadata)
47+
{
48+
GetOrCreateDataSet(prefix).Metadata = metadata;
49+
return Task.CompletedTask;
50+
}
51+
52+
private Task SetSegments(string prefix, string userHashKey,
53+
IEnumerable<string> includedSegmentRefs, IEnumerable<string> excludedSegmentRefs)
54+
{
55+
GetOrCreateDataSet(prefix).Memberships[userHashKey] =
56+
NewMembershipFromSegmentRefs(includedSegmentRefs, excludedSegmentRefs);
57+
return Task.CompletedTask;
58+
}
59+
60+
private DataSet GetOrCreateDataSet(string prefix)
61+
{
62+
if (!_allData.ContainsKey(prefix))
63+
{
64+
_allData[prefix] = new DataSet();
65+
}
66+
return _allData[prefix];
67+
}
68+
69+
private class MockStoreFactory : IBigSegmentStoreFactory
70+
{
71+
private readonly DataSet _data;
72+
73+
public MockStoreFactory(DataSet data)
74+
{
75+
_data = data;
76+
}
77+
78+
public IBigSegmentStore CreateBigSegmentStore(LdClientContext context) =>
79+
new MockStore(_data);
80+
}
81+
82+
private class MockStore : IBigSegmentStore
83+
{
84+
private readonly DataSet _data;
85+
86+
public MockStore(DataSet data)
87+
{
88+
_data = data;
89+
}
90+
91+
public void Dispose() { }
92+
93+
public Task<IMembership> GetMembershipAsync(string userHash)
94+
{
95+
if (_data.Memberships.TryGetValue(userHash, out var result))
96+
{
97+
return Task.FromResult(result);
98+
}
99+
return Task.FromResult((IMembership)null);
100+
}
101+
102+
public Task<StoreMetadata?> GetMetadataAsync()
103+
{
104+
return Task.FromResult(_data.Metadata);
105+
}
106+
}
107+
}
108+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
using System.Threading.Tasks;
2+
using LaunchDarkly.Logging;
3+
using LaunchDarkly.Sdk.Server.Interfaces;
4+
using Xunit;
5+
using Xunit.Abstractions;
6+
7+
using static LaunchDarkly.Sdk.Server.Interfaces.BigSegmentStoreTypes;
8+
9+
namespace LaunchDarkly.Sdk.Server.SharedTests.BigSegmentStore
10+
{
11+
/// <summary>
12+
/// A configurable Xunit test class for all implementations of <c>IBigSegmentStore</c>.
13+
/// </summary>
14+
/// <remarks>
15+
/// <para>
16+
/// Each implementation of those interfaces should define a test class that is a subclass of this
17+
/// class for their implementation type, and run it in the unit tests for their project.
18+
/// </para>
19+
/// <para>
20+
/// You must override the <see cref="Configuration"/> property to provide details specific to
21+
/// your implementation type.
22+
/// </para>
23+
/// </remarks>
24+
public abstract class BigSegmentStoreBaseTests
25+
{
26+
/// <summary>
27+
/// Override this method to create the configuration for the test suite.
28+
/// </summary>
29+
protected abstract BigSegmentStoreTestConfig Configuration { get; }
30+
31+
private const string prefix = "testprefix";
32+
private const string fakeUserHash = "userhash";
33+
private const string segmentRef1 = "key1", segmentRef2 = "key2", segmentRef3 = "key3";
34+
private static readonly string[] allSegmentRefs = new string[] { segmentRef1, segmentRef2, segmentRef3 };
35+
36+
private readonly ILogAdapter _testLogging;
37+
38+
protected BigSegmentStoreBaseTests()
39+
{
40+
_testLogging = Logs.None;
41+
}
42+
43+
protected BigSegmentStoreBaseTests(ITestOutputHelper testOutput)
44+
{
45+
_testLogging = TestLogging.TestOutputAdapter(testOutput);
46+
}
47+
48+
private IBigSegmentStore MakeStore()
49+
{
50+
var context = new LdClientContext(new BasicConfiguration("sdk-key", false, _testLogging.Logger("")),
51+
LaunchDarkly.Sdk.Server.Configuration.Default("sdk-key"));
52+
return Configuration.StoreFactoryFunc(prefix).CreateBigSegmentStore(context);
53+
}
54+
55+
private async Task<IBigSegmentStore> MakeEmptyStore()
56+
{
57+
var store = MakeStore();
58+
try
59+
{
60+
await Configuration.ClearDataAction(prefix);
61+
}
62+
catch
63+
{
64+
store.Dispose();
65+
throw;
66+
}
67+
return store;
68+
}
69+
70+
[Fact]
71+
public async void MissingMetadata()
72+
{
73+
using (var store = await MakeEmptyStore())
74+
{
75+
Assert.Null(await store.GetMetadataAsync());
76+
}
77+
}
78+
79+
[Fact]
80+
public async void ValidMetadata()
81+
{
82+
using (var store = await MakeEmptyStore())
83+
{
84+
var metadata = new StoreMetadata { LastUpToDate = UnixMillisecondTime.Now };
85+
await Configuration.SetMetadataAction(prefix, metadata);
86+
87+
var result = await store.GetMetadataAsync();
88+
Assert.NotNull(result);
89+
Assert.Equal(metadata.LastUpToDate, result.Value.LastUpToDate);
90+
}
91+
}
92+
93+
[Fact]
94+
public async void MembershipNotFound()
95+
{
96+
using (var store = await MakeEmptyStore())
97+
{
98+
var membership = await store.GetMembershipAsync(fakeUserHash);
99+
100+
// Either null or an empty membership is allowed in this case
101+
if (membership != null)
102+
{
103+
AssertEqualMembership(NewMembershipFromSegmentRefs(null, null), membership);
104+
}
105+
}
106+
}
107+
108+
[Theory]
109+
[InlineData(new string[] { segmentRef1 }, new string[] { })]
110+
[InlineData(new string[] { segmentRef1, segmentRef2 }, new string[] { })]
111+
[InlineData(new string[] { }, new string[] { segmentRef1 })]
112+
[InlineData(new string[] { }, new string[] { segmentRef1, segmentRef2 })]
113+
[InlineData(new string[] { segmentRef1, segmentRef2 }, new string[] { segmentRef2, segmentRef3 })]
114+
public async void MembershipFound(string[] includes, string[] excludes)
115+
{
116+
using (var store = await MakeEmptyStore())
117+
{
118+
await Configuration.SetSegmentsAction(prefix, fakeUserHash, includes, excludes);
119+
120+
var membership = await store.GetMembershipAsync(fakeUserHash);
121+
122+
AssertEqualMembership(NewMembershipFromSegmentRefs(includes, excludes), membership);
123+
}
124+
}
125+
126+
private static void AssertEqualMembership(IMembership expected, IMembership actual)
127+
{
128+
if (actual.GetType().FullName.StartsWith("LaunchDarkly.Sdk.Server.Internal.BigSegments.MembershipBuilder"))
129+
{
130+
// The store implementation is using our standard membership types, so we can rely on the
131+
// standard equality test for those
132+
Assert.Equal(expected, actual);
133+
}
134+
else
135+
{
136+
// The store implementation has implemented IMembership in some other way, so we have to
137+
// check for the inclusion or exclusion of specific keys
138+
foreach (var segmentRef in allSegmentRefs)
139+
{
140+
if (actual.CheckMembership(segmentRef) != expected.CheckMembership(segmentRef))
141+
{
142+
Assert.True(false, string.Format("expected membership for {0} to be {1} but was {2}",
143+
segmentRef, expected.CheckMembership(segmentRef), actual.CheckMembership(segmentRef)));
144+
}
145+
}
146+
}
147+
}
148+
}
149+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using LaunchDarkly.Sdk.Server.Interfaces;
4+
5+
using static LaunchDarkly.Sdk.Server.Interfaces.BigSegmentStoreTypes;
6+
7+
namespace LaunchDarkly.Sdk.Server.SharedTests.BigSegmentStore
8+
{
9+
/// <summary>
10+
/// A function that takes a prefix string and returns a configured factory for
11+
/// your implementation of <c>IBigSegmentStore</c>.
12+
/// </summary>
13+
/// <remarks>
14+
/// If the prefix string is null or "", it should use the default prefix defined by the
15+
/// data store implementation. The factory must include any necessary configuration that
16+
/// may be appropriate for the test environment (for instance, pointing it to a database
17+
/// instance that has been set up for the tests).
18+
/// </remarks>
19+
/// <param name="prefix">the database prefix</param>
20+
/// <returns>a configured factory</returns>
21+
public delegate IBigSegmentStoreFactory StoreFactoryFunc(string prefix);
22+
23+
/// <summary>
24+
/// An asynchronous function that removes all data from the underlying
25+
/// data store for the specified prefix string.
26+
/// </summary>
27+
/// <param name="prefix">the database prefix</param>
28+
/// <returns>an asynchronous task</returns>
29+
public delegate Task ClearDataAction(string prefix);
30+
31+
/// <summary>
32+
/// An asynchronous function that updates the store metadata to the specified values.
33+
/// This must be provided separately by the test code because the store interface used by
34+
/// the SDK has no update methods.
35+
/// </summary>
36+
/// <param name="prefix">the database prefix</param>
37+
/// <param name="metadata">the data to write to the store</param>
38+
/// <returns>an asynchronous task</returns>
39+
public delegate Task SetMetadataAction(string prefix, StoreMetadata metadata);
40+
41+
/// <summary>
42+
/// An asynchronous function that updates the membership state for a user in the store.
43+
/// This must be provided separately by the test code because the store interface used by
44+
/// the SDK has no update methods.
45+
/// </summary>
46+
/// <param name="prefix">the database prefix</param>
47+
/// <param name="userHashKey">the hashed user key</param>
48+
/// <param name="includedSegmentRefs">segment references to be included</param>
49+
/// <param name="excludedSegmentRefs">segment references to be excluded</param>
50+
/// <returns>an asynchronous task</returns>
51+
public delegate Task SetSegmentsAction(string prefix, string userHashKey,
52+
IEnumerable<string> includedSegmentRefs, IEnumerable<string> excludedSegmentRefs);
53+
54+
/// <summary>
55+
/// Configuration for <see cref="BigSegmentStoreBaseTests"/>.
56+
/// </summary>
57+
public sealed class BigSegmentStoreTestConfig
58+
{
59+
public StoreFactoryFunc StoreFactoryFunc { get; set; }
60+
61+
public ClearDataAction ClearDataAction { get; set; }
62+
63+
public SetMetadataAction SetMetadataAction { get; set; }
64+
65+
public SetSegmentsAction SetSegmentsAction { get; set; }
66+
}
67+
}

LaunchDarkly.ServerSdk.SharedTests/LaunchDarkly.ServerSdk.SharedTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</PropertyGroup>
1414

1515
<ItemGroup>
16-
<PackageReference Include="LaunchDarkly.ServerSdk" Version="[6.0.0,7.0.0)" />
16+
<PackageReference Include="LaunchDarkly.ServerSdk" Version="[6.2.0,7.0.0)" />
1717
<PackageReference Include="LaunchDarkly.Logging" Version="[1.0.1,]" />
1818
<PackageReference Include="xunit" Version="2.4.1" />
1919
</ItemGroup>

0 commit comments

Comments
 (0)