-
Notifications
You must be signed in to change notification settings - Fork 20
feat: Added ODPSegmentManager #321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
d9db9a5
WIP Initial SegmentManager commit
mikechu-optimizely e5fcd9a
WIP Initial commit fixes
mikechu-optimizely 61d3e0f
WIP Initial commit fixes
mikechu-optimizely c4db70a
Finish OdpSegmentManager & interface
mikechu-optimizely b79861e
WIP unit tests starts
mikechu-optimizely bc60e1f
Unit tests & Segment Manager edits
mikechu-optimizely 38281ab
Merge branch 'master' into mike/odp-segment-manager
mikechu-optimizely 164b9d6
Merge branch 'master' into mike/odp-segment-manager
mikechu-optimizely 4f62cd7
Fix merge issues; Add unit test
mikechu-optimizely a614e9f
Lint fixes
mikechu-optimizely a42ed21
Lint fixes?
mikechu-optimizely 7efe971
Lint fixes??
mikechu-optimizely 8b4f002
Lint fixes???
mikechu-optimizely 3d27572
Remove re-added IOdpConfig.cs
mikechu-optimizely 1c5a914
Add internal doc
mikechu-optimizely 9d61e43
PR code review revisions
mikechu-optimizely 40f2fdf
Update unit test
mikechu-optimizely 3f91f81
Update OptimizelySDK/Odp/OdpSegmentManager.cs
mikechu-optimizely be49cb1
Pull request code revisions
mikechu-optimizely 892cd3f
Remove time complexity looping/Linq
mikechu-optimizely 77d56dd
Small refactor
mikechu-optimizely eedf603
Use OrderedDictionary
mikechu-optimizely ec81354
Use OrderedDictionary
mikechu-optimizely fe6a6d8
Merge remote-tracking branch 'origin/mike/odp-segment-manager' into m…
mikechu-optimizely File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
/* | ||
* Copyright 2022 Optimizely | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
using Moq; | ||
using NUnit.Framework; | ||
using OptimizelySDK.AudienceConditions; | ||
using OptimizelySDK.ErrorHandler; | ||
using OptimizelySDK.Logger; | ||
using OptimizelySDK.Odp; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net; | ||
|
||
namespace OptimizelySDK.Tests.OdpTests | ||
{ | ||
[TestFixture] | ||
public class OdpSegmentManagerTest | ||
{ | ||
private const string API_KEY = "S0m3Ap1KEy4U"; | ||
private const string API_HOST = "https://odp-host.example.com"; | ||
private const string FS_USER_ID = "some_valid_user_id"; | ||
|
||
private static readonly string expectedCacheKey = $"fs_user_id-$-{FS_USER_ID}"; | ||
|
||
private static readonly List<string> segmentsToCheck = new List<string> | ||
{ | ||
"segment1", | ||
"segment2", | ||
}; | ||
|
||
private OdpConfig _odpConfig; | ||
private Mock<IOdpSegmentApiManager> _mockApiManager; | ||
private Mock<ILogger> _mockLogger; | ||
private Mock<ICache<List<string>>> _mockCache; | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
_odpConfig = new OdpConfig(API_KEY, API_HOST, segmentsToCheck); | ||
|
||
_mockApiManager = new Mock<IOdpSegmentApiManager>(); | ||
|
||
_mockLogger = new Mock<ILogger>(); | ||
_mockLogger.Setup(i => i.Log(It.IsAny<LogLevel>(), It.IsAny<string>())); | ||
|
||
_mockCache = new Mock<ICache<List<string>>>(); | ||
} | ||
|
||
[Test] | ||
public void ShouldFetchSegmentsOnCacheMiss() | ||
{ | ||
var keyCollector = new List<string>(); | ||
_mockCache.Setup(c => c.Lookup(Capture.In(keyCollector))). | ||
Returns(default(List<string>)); | ||
_mockApiManager.Setup(a => a.FetchSegments(It.IsAny<string>(), It.IsAny<string>(), | ||
It.IsAny<OdpUserKeyType>(), It.IsAny<string>(), It.IsAny<List<string>>())). | ||
Returns(segmentsToCheck.ToArray()); | ||
var manager = new OdpSegmentManager(_odpConfig, _mockApiManager.Object, | ||
Constants.DEFAULT_MAX_CACHE_SIZE, null, _mockLogger.Object, _mockCache.Object); | ||
|
||
var segments = manager.FetchQualifiedSegments(FS_USER_ID); | ||
|
||
var cacheKey = keyCollector.FirstOrDefault(); | ||
Assert.AreEqual(expectedCacheKey, cacheKey); | ||
_mockCache.Verify(c => c.Reset(), Times.Never); | ||
_mockCache.Verify(c => c.Lookup(cacheKey), Times.Once); | ||
_mockLogger.Verify(l => | ||
l.Log(LogLevel.DEBUG, "ODP Cache Miss. Making a call to ODP Server."), Times.Once); | ||
_mockApiManager.Verify( | ||
a => a.FetchSegments( | ||
API_KEY, | ||
API_HOST, | ||
OdpUserKeyType.FS_USER_ID, | ||
FS_USER_ID, | ||
_odpConfig.SegmentsToCheck), Times.Once); | ||
_mockCache.Verify(c => c.Save(cacheKey, It.IsAny<List<string>>()), Times.Once); | ||
Assert.AreEqual(segmentsToCheck, segments); | ||
} | ||
|
||
[Test] | ||
public void ShouldFetchSegmentsSuccessOnCacheHit() | ||
{ | ||
var keyCollector = new List<string>(); | ||
_mockCache.Setup(c => c.Lookup(Capture.In(keyCollector))).Returns(segmentsToCheck); | ||
_mockApiManager.Setup(a => a.FetchSegments(It.IsAny<string>(), It.IsAny<string>(), | ||
It.IsAny<OdpUserKeyType>(), It.IsAny<string>(), It.IsAny<List<string>>())); | ||
var manager = new OdpSegmentManager(_odpConfig, _mockApiManager.Object, | ||
Constants.DEFAULT_MAX_CACHE_SIZE, null, _mockLogger.Object, _mockCache.Object); | ||
|
||
var segments = manager.FetchQualifiedSegments(FS_USER_ID); | ||
|
||
var cacheKey = keyCollector.FirstOrDefault(); | ||
Assert.AreEqual(expectedCacheKey, cacheKey); | ||
_mockCache.Verify(c => c.Reset(), Times.Never); | ||
_mockCache.Verify(c => c.Lookup(cacheKey), Times.Once); | ||
_mockLogger.Verify(l => | ||
l.Log(LogLevel.DEBUG, "ODP Cache Hit. Returning segments from Cache."), Times.Once); | ||
_mockApiManager.Verify( | ||
a => a.FetchSegments(It.IsAny<string>(), It.IsAny<string>(), | ||
It.IsAny<OdpUserKeyType>(), It.IsAny<string>(), It.IsAny<List<string>>()), | ||
Times.Never); | ||
_mockCache.Verify(c => c.Save(expectedCacheKey, It.IsAny<List<string>>()), Times.Never); | ||
Assert.AreEqual(segmentsToCheck, segments); | ||
} | ||
|
||
[Test] | ||
public void ShouldHandleFetchSegmentsWithError() | ||
{ | ||
// OdpSegmentApiManager.FetchSegments() return null on any error | ||
_mockApiManager.Setup(a => a.FetchSegments(It.IsAny<string>(), It.IsAny<string>(), | ||
It.IsAny<OdpUserKeyType>(), It.IsAny<string>(), It.IsAny<List<string>>())). | ||
Returns(null as string[]); | ||
var manager = new OdpSegmentManager(_odpConfig, _mockApiManager.Object, | ||
Constants.DEFAULT_MAX_CACHE_SIZE, null, _mockLogger.Object, _mockCache.Object); | ||
|
||
var segments = manager.FetchQualifiedSegments(FS_USER_ID); | ||
|
||
_mockCache.Verify(c => c.Reset(), Times.Never); | ||
_mockCache.Verify(c => c.Lookup(expectedCacheKey), Times.Once); | ||
_mockApiManager.Verify( | ||
a => a.FetchSegments(It.IsAny<string>(), It.IsAny<string>(), | ||
It.IsAny<OdpUserKeyType>(), It.IsAny<string>(), It.IsAny<List<string>>()), | ||
Times.Once); | ||
_mockCache.Verify(c => c.Save(expectedCacheKey, It.IsAny<List<string>>()), Times.Never); | ||
Assert.IsNull(segments); | ||
} | ||
|
||
[Test] | ||
public void ShouldLogAndReturnAnEmptySetWhenNoSegmentsToCheck() | ||
{ | ||
var odpConfig = new OdpConfig(API_KEY, API_HOST, new List<string>(0)); | ||
var manager = new OdpSegmentManager(odpConfig, _mockApiManager.Object, | ||
Constants.DEFAULT_MAX_CACHE_SIZE, null, _mockLogger.Object, _mockCache.Object); | ||
|
||
var segments = manager.FetchQualifiedSegments(FS_USER_ID); | ||
|
||
Assert.IsTrue(segments.Count == 0); | ||
_mockLogger.Verify( | ||
l => l.Log(LogLevel.DEBUG, | ||
"No Segments are used in the project, Not Fetching segments. Returning empty list."), | ||
Times.Once); | ||
} | ||
|
||
[Test] | ||
public void ShouldLogAndReturnNullWhenOdpConfigNotReady() | ||
{ | ||
var mockOdpConfig = new Mock<OdpConfig>(API_KEY, API_HOST, new List<string>(0)); | ||
mockOdpConfig.Setup(o => o.IsReady()).Returns(false); | ||
var manager = new OdpSegmentManager(mockOdpConfig.Object, _mockApiManager.Object, | ||
Constants.DEFAULT_MAX_CACHE_SIZE, null, _mockLogger.Object, _mockCache.Object); | ||
|
||
var segments = manager.FetchQualifiedSegments(FS_USER_ID); | ||
|
||
Assert.IsNull(segments); | ||
_mockLogger.Verify( | ||
l => l.Log(LogLevel.WARN, Constants.ODP_NOT_INTEGRATED_MESSAGE), | ||
Times.Once); | ||
} | ||
|
||
[Test] | ||
public void ShouldIgnoreCache() | ||
{ | ||
var manager = new OdpSegmentManager(_odpConfig, _mockApiManager.Object, | ||
Constants.DEFAULT_MAX_CACHE_SIZE, null, _mockLogger.Object, _mockCache.Object); | ||
|
||
manager.FetchQualifiedSegments(FS_USER_ID, new List<OdpSegmentOption> | ||
{ | ||
OdpSegmentOption.IgnoreCache, | ||
}); | ||
|
||
_mockCache.Verify(c => c.Reset(), Times.Never); | ||
_mockCache.Verify(c => c.Lookup(It.IsAny<string>()), Times.Never); | ||
_mockApiManager.Verify( | ||
a => a.FetchSegments(It.IsAny<string>(), It.IsAny<string>(), | ||
It.IsAny<OdpUserKeyType>(), It.IsAny<string>(), It.IsAny<List<string>>()), | ||
Times.Once); | ||
_mockCache.Verify(c => c.Save(expectedCacheKey, It.IsAny<List<string>>()), Times.Never); | ||
} | ||
|
||
[Test] | ||
public void ShouldResetCache() | ||
{ | ||
var manager = new OdpSegmentManager(_odpConfig, _mockApiManager.Object, | ||
Constants.DEFAULT_MAX_CACHE_SIZE, null, _mockLogger.Object, _mockCache.Object); | ||
|
||
manager.FetchQualifiedSegments(FS_USER_ID, new List<OdpSegmentOption> | ||
{ | ||
OdpSegmentOption.ResetCache, | ||
}); | ||
|
||
_mockCache.Verify(c => c.Reset(), Times.Once); | ||
_mockCache.Verify(c => c.Lookup(It.IsAny<string>()), Times.Once); | ||
_mockApiManager.Verify( | ||
a => a.FetchSegments(It.IsAny<string>(), It.IsAny<string>(), | ||
It.IsAny<OdpUserKeyType>(), It.IsAny<string>(), It.IsAny<List<string>>()), | ||
Times.Once); | ||
_mockCache.Verify(c => c.Save(expectedCacheKey, It.IsAny<List<string>>()), Times.Once); | ||
} | ||
|
||
[Test] | ||
public void ShouldMakeValidCacheKey() | ||
{ | ||
var keyCollector = new List<string>(); | ||
_mockCache.Setup(c => c.Lookup(Capture.In(keyCollector))); | ||
var manager = new OdpSegmentManager(_odpConfig, _mockApiManager.Object, | ||
Constants.DEFAULT_MAX_CACHE_SIZE, null, _mockLogger.Object, _mockCache.Object); | ||
|
||
manager.FetchQualifiedSegments(FS_USER_ID); | ||
|
||
var cacheKey = keyCollector.FirstOrDefault(); | ||
Assert.AreEqual(expectedCacheKey, cacheKey); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* Copyright 2022 Optimizely | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
using System.Collections.Generic; | ||
|
||
namespace OptimizelySDK.Odp | ||
{ | ||
/// <summary> | ||
/// Interface to schedule connections to ODP for audience segmentation and caches the results. | ||
/// </summary> | ||
public interface IOdpSegmentManager | ||
{ | ||
/// <summary> | ||
/// Attempts to fetch and return a list of a user's qualified segments from the local segments cache. | ||
/// If no cached data exists for the target user, this fetches and caches data from the ODP server instead. | ||
/// </summary> | ||
/// <param name="fsUserId">The FS User ID identifying the user</param> | ||
/// <param name="options">An array of OptimizelySegmentOption used to ignore and/or reset the cache.</param> | ||
/// <returns>Qualified segments for the user from the cache or the ODP server if the cache is empty.</returns> | ||
List<string> FetchQualifiedSegments(string fsUserId, List<OdpSegmentOption> options = null); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.