SplitTestHandlerTests.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. const Path = require('path')
  2. const SandboxedModule = require('sandboxed-module')
  3. const sinon = require('sinon')
  4. const { ObjectId } = require('mongodb')
  5. const { expect } = require('chai')
  6. const MODULE_PATH = Path.join(
  7. __dirname,
  8. '../../../../app/src/Features/SplitTests/SplitTestHandler'
  9. )
  10. describe('SplitTestHandler', function () {
  11. beforeEach(function () {
  12. this.splitTests = [
  13. makeSplitTest('active-test'),
  14. makeSplitTest('legacy-test'),
  15. makeSplitTest('no-analytics-test-1', { analyticsEnabled: false }),
  16. makeSplitTest('no-analytics-test-2', {
  17. analyticsEnabled: false,
  18. versionNumber: 2,
  19. }),
  20. ]
  21. this.UserGetter = {
  22. promises: {
  23. getUser: sinon.stub().resolves(null),
  24. },
  25. }
  26. this.SplitTest = {
  27. find: sinon.stub().returns({
  28. exec: sinon.stub().resolves(this.splitTests),
  29. }),
  30. }
  31. this.SplitTestCache = {
  32. get: sinon.stub().resolves(null),
  33. }
  34. for (const splitTest of this.splitTests) {
  35. this.SplitTestCache.get.withArgs(splitTest.name).resolves(splitTest)
  36. }
  37. this.SplitTestHandler = SandboxedModule.require(MODULE_PATH, {
  38. requires: {
  39. '../User/UserGetter': this.UserGetter,
  40. './SplitTestCache': this.SplitTestCache,
  41. '../../models/SplitTest': { SplitTest: this.SplitTest },
  42. '../User/UserUpdater': {},
  43. '../Analytics/AnalyticsManager': {},
  44. './LocalsHelper': {},
  45. },
  46. })
  47. })
  48. describe('with an existing user', function () {
  49. beforeEach(async function () {
  50. this.user = {
  51. _id: ObjectId(),
  52. splitTests: {
  53. 'active-test': [
  54. {
  55. variantName: 'default',
  56. versionNumber: 1,
  57. assignedAt: 'active-test-assigned-at',
  58. },
  59. ],
  60. 'legacy-test': 'legacy-variant',
  61. 'inactive-test': [{ variantName: 'trythis' }],
  62. 'unknown-test': [{ variantName: 'trythis' }],
  63. 'no-analytics-test-2': [
  64. {
  65. variantName: 'some-variant',
  66. versionNumber: 1,
  67. assignedAt: 'no-analytics-assigned-at',
  68. },
  69. ],
  70. },
  71. }
  72. this.UserGetter.promises.getUser
  73. .withArgs(this.user._id)
  74. .resolves(this.user)
  75. this.assignments =
  76. await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
  77. this.user._id
  78. )
  79. })
  80. it('handles the legacy assignment format', function () {
  81. expect(this.assignments['legacy-test']).to.deep.equal({
  82. variantName: 'variant-1',
  83. phase: 'release',
  84. versionNumber: 1,
  85. })
  86. })
  87. it('returns the current assignment for each active test', function () {
  88. expect(this.assignments['active-test']).to.deep.equal({
  89. variantName: 'variant-1',
  90. phase: 'release',
  91. versionNumber: 1,
  92. assignedAt: 'active-test-assigned-at',
  93. })
  94. })
  95. it('returns the current assignment for tests with analytics disabled', function () {
  96. expect(this.assignments['no-analytics-test-1']).to.deep.equal({
  97. variantName: 'variant-1',
  98. phase: 'release',
  99. versionNumber: 1,
  100. })
  101. })
  102. it('returns the current assignment for tests with analytics disabled that had previous assignments', function () {
  103. expect(this.assignments['no-analytics-test-2']).to.deep.equal({
  104. variantName: 'variant-1',
  105. phase: 'release',
  106. versionNumber: 2,
  107. })
  108. })
  109. it('does not return assignments for unknown tests', function () {
  110. expect(this.assignments).not.to.have.property('unknown-test')
  111. })
  112. })
  113. describe('with an non-existent user', function () {
  114. beforeEach(async function () {
  115. const unknownUserId = ObjectId()
  116. this.assignments =
  117. await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
  118. unknownUserId
  119. )
  120. })
  121. it('returns empty assignments', function () {
  122. expect(this.assignments).to.deep.equal({})
  123. })
  124. })
  125. describe('with a user without assignments', function () {
  126. beforeEach(async function () {
  127. this.user = { _id: ObjectId() }
  128. this.UserGetter.promises.getUser
  129. .withArgs(this.user._id)
  130. .resolves(this.user)
  131. this.assignments =
  132. await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
  133. this.user._id
  134. )
  135. })
  136. it('returns current assignments', function () {
  137. expect(this.assignments).to.deep.equal({
  138. 'active-test': {
  139. phase: 'release',
  140. variantName: 'variant-1',
  141. versionNumber: 1,
  142. },
  143. 'legacy-test': {
  144. phase: 'release',
  145. variantName: 'variant-1',
  146. versionNumber: 1,
  147. },
  148. 'no-analytics-test-1': {
  149. phase: 'release',
  150. variantName: 'variant-1',
  151. versionNumber: 1,
  152. },
  153. 'no-analytics-test-2': {
  154. phase: 'release',
  155. variantName: 'variant-1',
  156. versionNumber: 2,
  157. },
  158. })
  159. })
  160. })
  161. })
  162. function makeSplitTest(name, opts = {}) {
  163. const {
  164. active = true,
  165. analyticsEnabled = active,
  166. phase = 'release',
  167. versionNumber = 1,
  168. } = opts
  169. return {
  170. name,
  171. getCurrentVersion: sinon.stub().returns({
  172. active,
  173. analyticsEnabled,
  174. phase,
  175. versionNumber,
  176. variants: [
  177. {
  178. name: 'variant-1',
  179. rolloutPercent: 100,
  180. rolloutStripes: [{ start: 0, end: 100 }],
  181. },
  182. ],
  183. }),
  184. }
  185. }