Skip to content

Commit

Permalink
feat(experiments): ability to force experiments
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberhck committed Jul 11, 2020
1 parent 9ab5fca commit 345945b
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 23 deletions.
64 changes: 64 additions & 0 deletions Sdk.Test/FeatureClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ public void Setup()
var mockWorker = new Mock<IFeatureWorker>();
mockWorker.Setup(x => x.GetRunningFeatures()).Returns(runningFeatures);
var mockUserData = new Mock<IUserDataRepo>();
mockUserData.Setup(x => x.GetExperimentsForcedA()).Returns(new List<string>
{
"ALL_A-1",
"ALL_A-2",
"ALL_A-3",
});
mockUserData.Setup(x => x.GetExperimentsForcedB()).Returns(new List<string>
{
"ALL_B-1",
"ALL_B-2",
"ALL_B-3",
});
mockUserData.Setup(x => x.GetUserId()).Returns(GetUuid);
_userDataRepo = mockUserData.Object;
_featureWorker = mockWorker.Object;
Expand Down Expand Up @@ -127,5 +139,57 @@ public void TestFeatureWithAllBAlwaysReturnsB()
Assert.AreEqual(0, dictionary['A']);
Assert.AreEqual(1000000, dictionary['B']);
}

[Test]
public void TestFeatureWithOverrideExperimentsToA()
{
var dictionary = new Dictionary<char, int> {['A'] = 0, ['B'] = 0, ['X'] = 0, ['Z'] = 0};
var client = new FeatureClient(_featureWorker, _userDataRepo);
for (var i = 0; i < 100; i++)
{
var variant = client.GetVariant("ALL_A-1");
dictionary[variant] = dictionary[variant] + 1;
}
for (var i = 0; i < 100; i++)
{
var variant = client.GetVariant("ALL_A-2");
dictionary[variant] = dictionary[variant] + 1;
}
for (var i = 0; i < 100; i++)
{
var variant = client.GetVariant("ALL_A-3");
dictionary[variant] = dictionary[variant] + 1;
}
Assert.AreEqual(300, dictionary['A']);
Assert.AreEqual(0, dictionary['B']);
Assert.AreEqual(0, dictionary['X']);
Assert.AreEqual(0, dictionary['Z']);
}

[Test]
public void TestFeatureWithOverrideExperimentsToB()
{
var dictionary = new Dictionary<char, int> {['A'] = 0, ['B'] = 0, ['X'] = 0, ['Z'] = 0};
var client = new FeatureClient(_featureWorker, _userDataRepo);
for (var i = 0; i < 100; i++)
{
var variant = client.GetVariant("ALL_B-1");
dictionary[variant] = dictionary[variant] + 1;
}
for (var i = 0; i < 100; i++)
{
var variant = client.GetVariant("ALL_B-2");
dictionary[variant] = dictionary[variant] + 1;
}
for (var i = 0; i < 100; i++)
{
var variant = client.GetVariant("ALL_B-3");
dictionary[variant] = dictionary[variant] + 1;
}
Assert.AreEqual(300, dictionary['B']);
Assert.AreEqual(0, dictionary['A']);
Assert.AreEqual(0, dictionary['X']);
Assert.AreEqual(0, dictionary['Z']);
}
}
}
31 changes: 8 additions & 23 deletions Sdk/FeatureContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Threading.Tasks;
using FossApps.FeatureManager;
using FossApps.FeatureManager.Models;
using Microsoft.Extensions.DependencyInjection;

namespace Fossapps.FeatureManager
{
Expand Down Expand Up @@ -60,11 +59,6 @@ private async Task<List<RunningFeature>> GetFeatures()
}
}

public interface IUserDataRepo
{
public string GetUserId();
}

// this is scoped
public class FeatureClient
{
Expand All @@ -83,6 +77,14 @@ private RunningFeature GetFeatureById(string featId)
}
public char GetVariant(string featId)
{
if (_userDataRepo.GetExperimentsForcedB().Any(x => x == featId))
{
return 'B';
}
if (_userDataRepo.GetExperimentsForcedA().Any(x => x == featId))
{
return 'A';
}
var feature = GetFeatureById(featId);

if (feature == null || !feature.Allocation.HasValue || string.IsNullOrEmpty(feature.RunToken) || string.IsNullOrEmpty(feature.FeatureToken))
Expand Down Expand Up @@ -117,21 +119,4 @@ private static int GetBucket(string userToken, string bucketToken, int numberOfB
return (int) (number % (ulong) numberOfBuckets) + 1;
}
}

public static class SetupFeatures
{
public static void SetupFeatureClients<TUserDataImplementation>(this IServiceCollection collection, string endpoint, TimeSpan syncInterval) where TUserDataImplementation : class, IUserDataRepo
{
var worker = new FeatureWorker(endpoint, syncInterval);
worker.Init();
collection.AddScoped<IUserDataRepo, TUserDataImplementation>();
collection.AddSingleton<IFeatureWorker>(worker);
collection.AddScoped<FeatureClient>();
}

public static bool IsFeatureOn(this FeatureClient instance, string featId)
{
return instance.GetVariant(featId) == 'B';
}
}
}
1 change: 1 addition & 0 deletions Sdk/Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.21" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.5" />
</ItemGroup>
Expand Down
22 changes: 22 additions & 0 deletions Sdk/SetupClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using Microsoft.Extensions.DependencyInjection;

namespace Fossapps.FeatureManager
{
public static class SetupClient
{
public static void SetupFeatureClients<TUserDataImplementation>(this IServiceCollection collection, string endpoint, TimeSpan syncInterval) where TUserDataImplementation : class, IUserDataRepo
{
var worker = new FeatureWorker(endpoint, syncInterval);
worker.Init();
collection.AddScoped<IUserDataRepo, TUserDataImplementation>();
collection.AddSingleton<IFeatureWorker>(worker);
collection.AddScoped<FeatureClient>();
}

public static bool IsFeatureOn(this FeatureClient instance, string featId)
{
return instance.GetVariant(featId) == 'B';
}
}
}
51 changes: 51 additions & 0 deletions Sdk/UserDataRepo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Http;

namespace Fossapps.FeatureManager
{
public interface IUserDataRepo
{
public string GetUserId();

public IEnumerable<string> GetExperimentsForcedA()
{
return new List<string>();
}

public IEnumerable<string> GetExperimentsForcedB()
{
return new List<string>();
}
}
public abstract class UserDataRepoBase : IUserDataRepo
{
private readonly IHttpContextAccessor _contextAccessor;
public abstract string GetUserId();

protected UserDataRepoBase(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}

public IEnumerable<string> GetExperimentsForcedA()
{
if (!_contextAccessor.HttpContext.Request.Headers.TryGetValue("X-Forced-Features-A", out var features))
{
return new List<string>();
}

return features.ToString().Split(",").ToList();
}

public IEnumerable<string> GetExperimentsForcedB()
{
if (!_contextAccessor.HttpContext.Request.Headers.TryGetValue("X-Forced-Features-B", out var features))
{
return new List<string>();
}

return features.ToString().Split(",").ToList();
}
}
}

0 comments on commit 345945b

Please # to comment.