|
1 | 1 | /*
|
2 |
| - * Copyright 2020-2021, 2022-2023 Optimizely and contributors |
| 2 | + * Copyright 2020-2021, 2022-2024 Optimizely and contributors |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
16 | 16 |
|
17 | 17 | using System;
|
18 | 18 | using System.Collections.Generic;
|
19 |
| -using System.Threading; |
| 19 | +using System.Linq; |
20 | 20 | using Castle.Core.Internal;
|
21 | 21 | using Moq;
|
22 | 22 | using NUnit.Framework;
|
@@ -61,6 +61,22 @@ public void SetUp()
|
61 | 61 | Optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object,
|
62 | 62 | LoggerMock.Object, ErrorHandlerMock.Object);
|
63 | 63 | }
|
| 64 | + |
| 65 | + private Mock<UserProfileService> MakeUserProfileServiceMock() |
| 66 | + { |
| 67 | + var projectConfig = DatafileProjectConfig.Create(TestData.Datafile, LoggerMock.Object, |
| 68 | + ErrorHandlerMock.Object); |
| 69 | + var experiment = projectConfig.Experiments[8]; |
| 70 | + var variation = experiment.Variations[0]; |
| 71 | + var decision = new Decision(variation.Id); |
| 72 | + var userProfile = new UserProfile(UserID, new Dictionary<string, Decision> |
| 73 | + { |
| 74 | + { experiment.Id, decision }, |
| 75 | + }); |
| 76 | + var userProfileServiceMock = new Mock<UserProfileService>(); |
| 77 | + userProfileServiceMock.Setup(up => up.Lookup(UserID)).Returns(userProfile.ToMap()); |
| 78 | + return userProfileServiceMock; |
| 79 | + } |
64 | 80 |
|
65 | 81 | [Test]
|
66 | 82 | public void OptimizelyUserContextWithAttributes()
|
@@ -193,7 +209,7 @@ public void SetAttributeToOverrideAttribute()
|
193 | 209 | Assert.AreEqual(user.GetAttributes()["k1"], true);
|
194 | 210 | }
|
195 | 211 |
|
196 |
| - #region decide |
| 212 | + #region Decide |
197 | 213 |
|
198 | 214 | [Test]
|
199 | 215 | public void TestDecide()
|
@@ -409,10 +425,111 @@ public void DecideWhenConfigIsNull()
|
409 | 425 | Assert.IsTrue(TestData.CompareObjects(decision, decisionExpected));
|
410 | 426 | }
|
411 | 427 |
|
412 |
| - #endregion decide |
| 428 | + [Test] |
| 429 | + public void SeparateDecideShouldHaveSameNumberOfUpsSaveOnlyOneLookup() |
| 430 | + { |
| 431 | + var experimentFlagKey = "double_single_variable_feature"; |
| 432 | + var rolloutFlagKey = "boolean_single_variable_feature"; |
| 433 | + var userProfileServiceMock = MakeUserProfileServiceMock(); |
| 434 | + var saveArgsCollector = new List<Dictionary<string, object>>(); |
| 435 | + userProfileServiceMock.Setup(up => up.Save(Capture.In(saveArgsCollector))); |
| 436 | + var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, |
| 437 | + LoggerMock.Object, ErrorHandlerMock.Object, userProfileServiceMock.Object); |
| 438 | + var user = optimizely.CreateUserContext(UserID); |
| 439 | + var expectedUserProfileExperiment = new UserProfile(UserID, new Dictionary<string, Decision> |
| 440 | + { |
| 441 | + { "224", new Decision("280") }, |
| 442 | + { "122238", new Decision("122240") }, |
| 443 | + }); |
| 444 | + var expectedUserProfileRollout = new UserProfile(UserID, new Dictionary<string, Decision> |
| 445 | + { |
| 446 | + { "999", new Decision("99") }, |
| 447 | + { "99999", new Decision("999") }, |
| 448 | + }); |
| 449 | + |
| 450 | + user.Decide(experimentFlagKey); |
| 451 | + user.Decide(rolloutFlagKey); |
| 452 | + |
| 453 | + LoggerMock.Verify( |
| 454 | + l => l.Log(LogLevel.INFO, |
| 455 | + "We were unable to get a user profile map from the UserProfileService."), |
| 456 | + Times.Never); |
| 457 | + LoggerMock.Verify( |
| 458 | + l => l.Log(LogLevel.ERROR, "The UserProfileService returned an invalid map."), |
| 459 | + Times.Never); |
| 460 | + userProfileServiceMock.Verify(l => l.Lookup(UserID), Times.Once); |
| 461 | + userProfileServiceMock.Verify(l => l.Save(It.IsAny<Dictionary<string, object>>()), |
| 462 | + Times.Exactly(2)); |
| 463 | + Assert.AreEqual(saveArgsCollector[0], expectedUserProfileExperiment.ToMap()); |
| 464 | + Assert.AreEqual(saveArgsCollector[1], expectedUserProfileRollout.ToMap()); |
| 465 | + } |
| 466 | + |
| 467 | + [Test] |
| 468 | + public void DecideWithUpsShouldOnlyLookupSaveOnce() |
| 469 | + { |
| 470 | + var flagKeyFromTestDataJson = "double_single_variable_feature"; |
| 471 | + var userProfileServiceMock = MakeUserProfileServiceMock(); |
| 472 | + var saveArgsCollector = new List<Dictionary<string, object>>(); |
| 473 | + userProfileServiceMock.Setup(up => up.Save(Capture.In(saveArgsCollector))); |
| 474 | + var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, |
| 475 | + LoggerMock.Object, ErrorHandlerMock.Object, userProfileServiceMock.Object); |
| 476 | + var user = optimizely.CreateUserContext(UserID); |
| 477 | + var expectedUserProfile = new UserProfile(UserID, new Dictionary<string, Decision> |
| 478 | + { |
| 479 | + { "224", new Decision("280") }, |
| 480 | + { "122238", new Decision("122240") }, |
| 481 | + }); |
| 482 | + |
| 483 | + user.Decide(flagKeyFromTestDataJson); |
| 484 | + |
| 485 | + LoggerMock.Verify( |
| 486 | + l => l.Log(LogLevel.INFO, |
| 487 | + "We were unable to get a user profile map from the UserProfileService."), |
| 488 | + Times.Never); |
| 489 | + LoggerMock.Verify( |
| 490 | + l => l.Log(LogLevel.ERROR, "The UserProfileService returned an invalid map."), |
| 491 | + Times.Never); |
| 492 | + userProfileServiceMock.Verify(l => l.Lookup(UserID), Times.Once); |
| 493 | + userProfileServiceMock.Verify(l => l.Save(It.IsAny<Dictionary<string, object>>()), |
| 494 | + Times.Once); |
| 495 | + Assert.AreEqual(saveArgsCollector.First(), expectedUserProfile.ToMap()); |
| 496 | + } |
| 497 | + |
| 498 | + #endregion Decide |
| 499 | + |
| 500 | + #region DecideForKeys |
413 | 501 |
|
414 |
| - #region decideAll |
| 502 | + [Test] |
| 503 | + public void DecideForKeysWithUpsShouldOnlyLookupSaveOnceWithMultipleFlags() |
| 504 | + { |
| 505 | + var flagKeys = new[] { "double_single_variable_feature", "boolean_feature" }; |
| 506 | + var userProfileServiceMock = MakeUserProfileServiceMock(); |
| 507 | + var saveArgsCollector = new List<Dictionary<string, object>>(); |
| 508 | + userProfileServiceMock.Setup(up => up.Save(Capture.In(saveArgsCollector))); |
| 509 | + var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, |
| 510 | + LoggerMock.Object, ErrorHandlerMock.Object, userProfileServiceMock.Object); |
| 511 | + var userContext = optimizely.CreateUserContext(UserID); |
| 512 | + var expectedUserProfile = new UserProfile(UserID, new Dictionary<string, Decision> |
| 513 | + { |
| 514 | + { "224", new Decision("280") }, |
| 515 | + { "122238", new Decision("122240") }, |
| 516 | + }); |
415 | 517 |
|
| 518 | + userContext.DecideForKeys(flagKeys); |
| 519 | + |
| 520 | + LoggerMock.Verify( |
| 521 | + l => l.Log(LogLevel.INFO, |
| 522 | + "We were unable to get a user profile map from the UserProfileService."), |
| 523 | + Times.Never); |
| 524 | + LoggerMock.Verify( |
| 525 | + l => l.Log(LogLevel.ERROR, "The UserProfileService returned an invalid map."), |
| 526 | + Times.Never); |
| 527 | + userProfileServiceMock.Verify(l => l.Lookup(UserID), Times.Once); |
| 528 | + userProfileServiceMock.Verify(l => l.Save(It.IsAny<Dictionary<string, object>>()), |
| 529 | + Times.Once); |
| 530 | + Assert.AreEqual(saveArgsCollector.First(), expectedUserProfile.ToMap()); |
| 531 | + } |
| 532 | + |
416 | 533 | [Test]
|
417 | 534 | public void DecideForKeysWithOneFlag()
|
418 | 535 | {
|
@@ -442,6 +559,42 @@ public void DecideForKeysWithOneFlag()
|
442 | 559 | new string[0]);
|
443 | 560 | Assert.IsTrue(TestData.CompareObjects(decision, expDecision));
|
444 | 561 | }
|
| 562 | + |
| 563 | + #endregion DecideForKeys |
| 564 | + |
| 565 | + #region DecideAll |
| 566 | + |
| 567 | + [Test] |
| 568 | + public void DecideAllWithUpsShouldOnlyLookupSaveOnce() |
| 569 | + { |
| 570 | + var userProfileServiceMock = MakeUserProfileServiceMock(); |
| 571 | + var saveArgsCollector = new List<Dictionary<string, object>>(); |
| 572 | + userProfileServiceMock.Setup(up => up.Save(Capture.In(saveArgsCollector))); |
| 573 | + var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, |
| 574 | + LoggerMock.Object, ErrorHandlerMock.Object, userProfileServiceMock.Object); |
| 575 | + var user = optimizely.CreateUserContext(UserID); |
| 576 | + var expectedUserProfile = new UserProfile(UserID, new Dictionary<string, Decision> |
| 577 | + { |
| 578 | + { "224", new Decision("280") }, |
| 579 | + { "122238", new Decision("122240") }, |
| 580 | + { "122241", new Decision("122242") }, |
| 581 | + { "122235", new Decision("122236") }, |
| 582 | + { "188880", new Decision("188881") }, |
| 583 | + }); |
| 584 | + |
| 585 | + user.DecideAll(); |
| 586 | + |
| 587 | + LoggerMock.Verify( |
| 588 | + l => l.Log(LogLevel.INFO, |
| 589 | + "We were unable to get a user profile map from the UserProfileService."), |
| 590 | + Times.Never); |
| 591 | + LoggerMock.Verify( |
| 592 | + l => l.Log(LogLevel.ERROR, "The UserProfileService returned an invalid map."), |
| 593 | + Times.Never); |
| 594 | + userProfileServiceMock.Verify(l => l.Lookup(UserID), Times.Once); |
| 595 | + userProfileServiceMock.Verify(l => l.Save(It.IsAny<Dictionary<string,object>>()), Times.Once); |
| 596 | + Assert.AreEqual(saveArgsCollector.First(), expectedUserProfile.ToMap()); |
| 597 | + } |
445 | 598 |
|
446 | 599 | [Test]
|
447 | 600 | public void DecideAllTwoFlag()
|
|
0 commit comments