Skip to content

Commit f7c9ffd

Browse files
authored
Merge pull request #8 from fossapps/fixes
Fixes
2 parents 1d6dc14 + 97080b5 commit f7c9ffd

File tree

8 files changed

+123
-38
lines changed

8 files changed

+123
-38
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ jobs:
101101
dotnet test
102102
103103
publish:
104-
needs: [ build-docker-image, run-postman-tests ]
104+
needs: [ build-docker-image, run-postman-tests, test-sdk ]
105105
runs-on: ubuntu-latest
106106

107107
steps:

Feature.Manager.Api/FeatureRuns/FeatureRunService.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading.Tasks;
55
using Feature.Manager.Api.FeatureRuns.Exceptions;
66
using Feature.Manager.Api.FeatureRuns.ViewModels;
7+
using Feature.Manager.Api.Features;
78
using Feature.Manager.Api.Features.Exceptions;
89
using Feature.Manager.Api.Features.ViewModels;
910

@@ -20,10 +21,12 @@ public interface IFeatureRunService
2021
public class FeatureRunService : IFeatureRunService
2122
{
2223
private readonly IFeatureRunRepository _featureRunRepository;
24+
private readonly IFeatureRepository _featureRepository;
2325

24-
public FeatureRunService(IFeatureRunRepository featureRunRepository)
26+
public FeatureRunService(IFeatureRunRepository featureRunRepository, IFeatureRepository featureRepository)
2527
{
2628
_featureRunRepository = featureRunRepository;
29+
_featureRepository = featureRepository;
2730
}
2831

2932
public async Task<FeatureRun> CreateFeatureRun(CreateFeatureRunRequest request)
@@ -36,12 +39,21 @@ public async Task<FeatureRun> CreateFeatureRun(CreateFeatureRunRequest request)
3639
throw new FeatureAlreadyRunningException();
3740
}
3841

42+
if (await _featureRepository.FindByFeatId(request.FeatId) == null)
43+
{
44+
throw new FeatureNotFoundException();
45+
}
46+
3947
return await _featureRunRepository.CreateFeatureRun(request);
4048
}
4149
catch (FeatureAlreadyRunningException)
4250
{
4351
throw;
4452
}
53+
catch (FeatureNotFoundException)
54+
{
55+
throw;
56+
}
4557
catch (Exception e)
4658
{
4759
throw new UnknownDbException(e.Message);

Feature.Manager.Api/FeatureRuns/FeatureRunsController.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ public async Task<IActionResult> AddFeatureRun(CreateFeatureRunRequest request)
167167
Title = "Feature is already running"
168168
});
169169
}
170+
catch (FeatureNotFoundException)
171+
{
172+
return BadRequest(new ProblemDetails
173+
{
174+
Title = "feature id is invalid"
175+
});
176+
}
170177
catch (UnknownDbException)
171178
{
172179
return StatusCode(StatusCodes.Status500InternalServerError, new ProblemDetails

Feature.Manager.Api/StartupExtensions/Swagger.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public static void ConfigureSwagger(this IServiceCollection services)
1111
{
1212
services.AddSwaggerGen(c =>
1313
{
14+
c.DescribeAllEnumsAsStrings();
1415
c.CustomOperationIds(e =>
1516
{
1617
var methodName = e.ActionDescriptor.RouteValues["action"];

Feature.Manager.UnitTest/FeatureRuns/FeatureRunCreateService.cs renamed to Feature.Manager.UnitTest/FeatureRuns/FeatureRunCreateServiceTest.cs

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@
44
using Feature.Manager.Api.FeatureRuns;
55
using Feature.Manager.Api.FeatureRuns.Exceptions;
66
using Feature.Manager.Api.FeatureRuns.ViewModels;
7-
using Feature.Manager.Api.Features.ViewModels;
7+
using Feature.Manager.Api.Features;
8+
using Feature.Manager.Api.Features.Exceptions;
89
using Feature.Manager.Api.Uuid;
910
using Moq;
1011
using NUnit.Framework;
1112

1213
namespace Feature.Manager.UnitTest.FeatureRuns
1314
{
14-
public class FeatureRunCreateService
15+
public class FeatureRunCreateServiceTest
1516
{
16-
private Mock<IFeatureRunRepository> _mock;
1717
private FeatureRunService _featureRunService;
18+
private Mock<IFeatureRepository> _featureRepository;
19+
private Mock<IFeatureRunRepository> _mock;
1820
private UuidService _uuidService;
1921

2022
private FeatureRun MakeFeatureRun(StopResult? stopResult, string featId, DateTime? endAt)
@@ -25,17 +27,11 @@ private FeatureRun MakeFeatureRun(StopResult? stopResult, string featId, DateTim
2527
Id = _uuidService.GenerateUuId(),
2628
StartAt = DateTime.Now.Subtract(TimeSpan.FromDays(5)),
2729
RunToken = _uuidService.GenerateUuId(),
28-
FeatId = featId,
30+
FeatId = featId
2931
};
30-
if (stopResult.HasValue)
31-
{
32-
run.StopResult = stopResult.Value;
33-
}
32+
if (stopResult.HasValue) run.StopResult = stopResult.Value;
3433

35-
if (endAt.HasValue)
36-
{
37-
run.EndAt = endAt;
38-
}
34+
if (endAt.HasValue) run.EndAt = endAt;
3935

4036
return run;
4137
}
@@ -51,7 +47,7 @@ public void Setup()
5147
Id = "rand",
5248
FeatId = "APP-2",
5349
RunToken = "run-token",
54-
StartAt = DateTime.Now.Subtract(TimeSpan.FromHours(2)),
50+
StartAt = DateTime.Now.Subtract(TimeSpan.FromHours(2))
5551
});
5652

5753
// APP-1 will have 1 item with no end date, (NO STOP RESULT)
@@ -61,45 +57,65 @@ public void Setup()
6157
// APP-5 will have 1 item with end date BUT with stop result of all B (to show that even if you set it to B, you can still create new runs)
6258
mock.Setup(x => x.GetRunsForFeatureByFeatId("APP-1")).ReturnsAsync(new List<FeatureRun>
6359
{
64-
MakeFeatureRun(null, "APP-1", null),
60+
MakeFeatureRun(null, "APP-1", null)
6561
});
6662
mock.Setup(x => x.GetRunsForFeatureByFeatId("APP-2")).ReturnsAsync(new List<FeatureRun>
6763
{
6864
MakeFeatureRun(StopResult.ChangeSettings, "APP-2", DateTime.Now.Subtract(TimeSpan.FromDays(2))),
69-
MakeFeatureRun(null, "APP-2", null),
65+
MakeFeatureRun(null, "APP-2", null)
7066
});
7167
mock.Setup(x => x.GetRunsForFeatureByFeatId("APP-3")).ReturnsAsync(new List<FeatureRun>
7268
{
7369
MakeFeatureRun(StopResult.ChangeSettings, "APP-3", DateTime.Now.Subtract(TimeSpan.FromDays(2))),
7470
MakeFeatureRun(StopResult.ChangeSettings, "APP-3", DateTime.Now.Subtract(TimeSpan.FromDays(1))),
75-
MakeFeatureRun(StopResult.ChangeSettings, "APP-3", DateTime.Now.Subtract(TimeSpan.FromHours(12))),
71+
MakeFeatureRun(StopResult.ChangeSettings, "APP-3", DateTime.Now.Subtract(TimeSpan.FromHours(12)))
7672
});
7773
mock.Setup(x => x.GetRunsForFeatureByFeatId("APP-4")).ReturnsAsync(new List<FeatureRun>());
7874
mock.Setup(x => x.GetRunsForFeatureByFeatId("APP-5")).ReturnsAsync(new List<FeatureRun>
7975
{
80-
MakeFeatureRun(StopResult.AllB, "APP-5", DateTime.Now.Subtract(TimeSpan.FromHours(12))),
76+
MakeFeatureRun(StopResult.AllB, "APP-5", DateTime.Now.Subtract(TimeSpan.FromHours(12)))
8177
});
78+
mock.Setup(x => x.GetRunsForFeatureByFeatId("APP-19")).ReturnsAsync(new List<FeatureRun>());
8279
_mock = mock;
83-
_featureRunService = new FeatureRunService(_mock.Object);
80+
var featureRepository = new Mock<IFeatureRepository>();
81+
featureRepository.Setup(x => x.FindByFeatId(It.IsAny<string>())).ReturnsAsync((string featId) =>
82+
{
83+
if (featId == "APP-19")
84+
{
85+
return null;
86+
}
87+
return new Api.Features.Feature
88+
{
89+
Description = "asdfasdfasdf",
90+
Hypothesis = "asdfasdfsd",
91+
Id = "asdfasdf",
92+
FeatId = featId,
93+
FeatureToken = "asldf"
94+
};
95+
});
96+
_featureRepository = featureRepository;
97+
_featureRunService = new FeatureRunService(_mock.Object, _featureRepository.Object);
8498
}
8599

86100
[Test]
87101
public async Task TestCannotCreateNewRunIfARunIsAlreadyRunning()
88102
{
89-
Assert.ThrowsAsync<FeatureAlreadyRunningException>(() => _featureRunService.CreateFeatureRun(new CreateFeatureRunRequest
90-
{
91-
Allocation = 100,
92-
EndAt = DateTime.Now.Add(TimeSpan.FromDays(20)),
93-
StartAt = DateTime.Now,
94-
FeatId = "APP-1"
95-
}));
96-
Assert.ThrowsAsync<FeatureAlreadyRunningException>(() => _featureRunService.CreateFeatureRun(new CreateFeatureRunRequest
97-
{
98-
Allocation = 100,
99-
EndAt = DateTime.Now.Add(TimeSpan.FromDays(20)),
100-
StartAt = DateTime.Now,
101-
FeatId = "APP-2"
102-
}));
103+
Assert.ThrowsAsync<FeatureAlreadyRunningException>(() => _featureRunService.CreateFeatureRun(
104+
new CreateFeatureRunRequest
105+
{
106+
Allocation = 100,
107+
EndAt = DateTime.Now.Add(TimeSpan.FromDays(20)),
108+
StartAt = DateTime.Now,
109+
FeatId = "APP-1"
110+
}));
111+
Assert.ThrowsAsync<FeatureAlreadyRunningException>(() => _featureRunService.CreateFeatureRun(
112+
new CreateFeatureRunRequest
113+
{
114+
Allocation = 100,
115+
EndAt = DateTime.Now.Add(TimeSpan.FromDays(20)),
116+
StartAt = DateTime.Now,
117+
FeatId = "APP-2"
118+
}));
103119
}
104120

105121
[Test]
@@ -114,5 +130,18 @@ public async Task TestCreatesNewRunWhenNoFeaturesAreRunning()
114130
});
115131
Assert.NotNull(result);
116132
}
133+
134+
[Test]
135+
public async Task TestCreateFailsIfFeatureDoesNotExist()
136+
{
137+
Assert.ThrowsAsync<FeatureNotFoundException>(() => _featureRunService.CreateFeatureRun(
138+
new CreateFeatureRunRequest
139+
{
140+
Allocation = 100,
141+
EndAt = DateTime.Now.Add(TimeSpan.FromDays(20)),
142+
StartAt = DateTime.Now,
143+
FeatId = "APP-19"
144+
}));
145+
}
117146
}
118-
}
147+
}

Feature.Manager.UnitTest/FeatureRuns/FeatureRunServiceTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public void Setup()
8383
});
8484
mock.Setup(x => x.GetRunsForFeatureByFeatId("APP-2")).ThrowsAsync(new InvalidCastException());
8585
_mock = mock;
86-
_featureRunService = new FeatureRunService(_mock.Object);
86+
_featureRunService = new FeatureRunService(_mock.Object, null);
8787
}
8888

8989
[Test]
@@ -186,7 +186,7 @@ public async Task TestGetRunningFeaturesReturnsFromRepository()
186186
},
187187
};
188188
mock.Setup(x => x.GetRunningFeatures()).ReturnsAsync(mockData);
189-
var systemUnderTest = new FeatureRunService(mock.Object);
189+
var systemUnderTest = new FeatureRunService(mock.Object, null);
190190
var result = await systemUnderTest.GetRunningFeatures();
191191
Assert.AreSame(mockData, result);
192192
}
@@ -196,7 +196,7 @@ public async Task TestGetRunningFeaturesHandlesException()
196196
{
197197
var mock = new Mock<IFeatureRunRepository>();
198198
mock.Setup(x => x.GetRunningFeatures()).ThrowsAsync(new InvalidCastException());
199-
var systemUnderTest = new FeatureRunService(mock.Object);
199+
var systemUnderTest = new FeatureRunService(mock.Object, null);
200200
Assert.ThrowsAsync<UnknownDbException>(() => systemUnderTest.GetRunningFeatures());
201201
}
202202
}

Sdk.Test/FeatureClientTest.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ public void Setup()
3636
FeatureToken = GetUuid(),
3737
RunId = GetUuid(),
3838
RunToken = GetUuid()
39+
},
40+
new RunningFeature
41+
{
42+
Allocation = 50,
43+
FeatureId = "APP-3",
44+
FeatureToken = GetUuid(),
45+
RunId = GetUuid(),
46+
RunStatus = "AllB",
47+
RunToken = GetUuid()
3948
}
4049
};
4150
var mockWorker = new Mock<IFeatureWorker>();
@@ -101,5 +110,22 @@ public void TestFeatureWithAllocationHasLowAllocationBias()
101110
Assert.LessOrEqual(biasPerc, 0.5);
102111
Assert.LessOrEqual(biasWithZPerc, 0.5);
103112
}
113+
114+
[Test]
115+
public void TestFeatureWithAllBAlwaysReturnsB()
116+
{
117+
var dictionary = new Dictionary<char, int> {['A'] = 0, ['B'] = 0, ['X'] = 0, ['Z'] = 0};
118+
var client = new FeatureClient(_featureWorker, _userDataRepo);
119+
for (var i = 0; i < 1000000; i++)
120+
{
121+
var variant = client.GetVariant("APP-3");
122+
dictionary[variant] = dictionary[variant] + 1;
123+
}
124+
125+
Assert.AreEqual(0, dictionary['X']);
126+
Assert.AreEqual(0, dictionary['Z']);
127+
Assert.AreEqual(0, dictionary['A']);
128+
Assert.AreEqual(1000000, dictionary['B']);
129+
}
104130
}
105131
}

Sdk/FeatureContainer.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ public char GetVariant(string featId)
9090
return 'X';
9191
}
9292

93+
if (feature.RunStatus == "AllB")
94+
{
95+
return 'B';
96+
}
97+
9398
if (feature.Allocation.Value == 100 || GetBucket(_userDataRepo.GetUserId(), feature.FeatureToken, 100) <= feature.Allocation)
9499
{
95100
var bucket = GetBucket(_userDataRepo.GetUserId(), feature.RunToken, 2);
@@ -123,5 +128,10 @@ public static void SetupFeatureClients<TUserDataImplementation>(this IServiceCol
123128
collection.AddSingleton<IFeatureWorker>(worker);
124129
collection.AddScoped<FeatureClient>();
125130
}
131+
132+
public static bool IsFeatureOn(this FeatureClient instance, string featId)
133+
{
134+
return instance.GetVariant(featId) == 'B';
135+
}
126136
}
127137
}

0 commit comments

Comments
 (0)