SubscriptionSettingService.spec.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. import 'reflect-metadata'
  2. import { Logger } from 'winston'
  3. import { EncryptionVersion } from '../Encryption/EncryptionVersion'
  4. import { SubscriptionSettingService } from './SubscriptionSettingService'
  5. import { SettingDecrypterInterface } from './SettingDecrypterInterface'
  6. import { SubscriptionSettingRepositoryInterface } from './SubscriptionSettingRepositoryInterface'
  7. import { SubscriptionSetting } from './SubscriptionSetting'
  8. import { UserSubscription } from '../Subscription/UserSubscription'
  9. import { SubscriptionName } from '@standardnotes/common'
  10. import { User } from '../User/User'
  11. import { SettingFactoryInterface } from './SettingFactoryInterface'
  12. import { SubscriptionSettingsAssociationServiceInterface } from './SubscriptionSettingsAssociationServiceInterface'
  13. import { UserSubscriptionRepositoryInterface } from '../Subscription/UserSubscriptionRepositoryInterface'
  14. import { SettingName } from '@standardnotes/settings'
  15. import { SettingInterpreterInterface } from './SettingInterpreterInterface'
  16. describe('SubscriptionSettingService', () => {
  17. let setting: SubscriptionSetting
  18. let user: User
  19. let userSubscription: UserSubscription
  20. let factory: SettingFactoryInterface
  21. let subscriptionSettingRepository: SubscriptionSettingRepositoryInterface
  22. let subscriptionSettingsAssociationService: SubscriptionSettingsAssociationServiceInterface
  23. let settingInterpreter: SettingInterpreterInterface
  24. let settingDecrypter: SettingDecrypterInterface
  25. let userSubscriptionRepository: UserSubscriptionRepositoryInterface
  26. let logger: Logger
  27. const createService = () =>
  28. new SubscriptionSettingService(
  29. factory,
  30. subscriptionSettingRepository,
  31. subscriptionSettingsAssociationService,
  32. settingInterpreter,
  33. settingDecrypter,
  34. userSubscriptionRepository,
  35. logger,
  36. )
  37. beforeEach(() => {
  38. user = {} as jest.Mocked<User>
  39. userSubscription = {
  40. uuid: '1-2-3',
  41. user: Promise.resolve(user),
  42. planName: SubscriptionName.PlusPlan,
  43. } as jest.Mocked<UserSubscription>
  44. setting = {
  45. name: SettingName.NAMES.FileUploadBytesUsed,
  46. } as jest.Mocked<SubscriptionSetting>
  47. factory = {} as jest.Mocked<SettingFactoryInterface>
  48. factory.createSubscriptionSetting = jest.fn().mockReturnValue(setting)
  49. factory.createSubscriptionSettingReplacement = jest.fn().mockReturnValue(setting)
  50. subscriptionSettingRepository = {} as jest.Mocked<SubscriptionSettingRepositoryInterface>
  51. subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(null)
  52. subscriptionSettingRepository.save = jest.fn().mockImplementation((setting) => setting)
  53. userSubscriptionRepository = {} as jest.Mocked<UserSubscriptionRepositoryInterface>
  54. userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([
  55. {
  56. uuid: 's-1-2-3',
  57. } as jest.Mocked<UserSubscription>,
  58. {
  59. uuid: 's-2-3-4',
  60. } as jest.Mocked<UserSubscription>,
  61. ])
  62. subscriptionSettingsAssociationService = {} as jest.Mocked<SubscriptionSettingsAssociationServiceInterface>
  63. subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
  64. new Map([
  65. [
  66. SettingName.NAMES.FileUploadBytesUsed,
  67. {
  68. value: '0',
  69. sensitive: 0,
  70. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  71. replaceable: true,
  72. },
  73. ],
  74. ]),
  75. )
  76. settingInterpreter = {} as jest.Mocked<SettingInterpreterInterface>
  77. settingInterpreter.interpretSettingUpdated = jest.fn()
  78. settingDecrypter = {} as jest.Mocked<SettingDecrypterInterface>
  79. settingDecrypter.decryptSettingValue = jest.fn().mockReturnValue('decrypted')
  80. logger = {} as jest.Mocked<Logger>
  81. logger.debug = jest.fn()
  82. logger.warn = jest.fn()
  83. logger.error = jest.fn()
  84. })
  85. it('should create default settings for a subscription', async () => {
  86. await createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription)
  87. expect(subscriptionSettingRepository.save).toHaveBeenCalledWith(setting)
  88. })
  89. it('should create default settings for a subscription with overrides', async () => {
  90. subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
  91. new Map([
  92. [
  93. SettingName.NAMES.FileUploadBytesUsed,
  94. {
  95. value: '0',
  96. sensitive: 0,
  97. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  98. replaceable: false,
  99. },
  100. ],
  101. [
  102. SettingName.NAMES.FileUploadBytesLimit,
  103. {
  104. value: '345',
  105. sensitive: 0,
  106. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  107. replaceable: true,
  108. },
  109. ],
  110. ]),
  111. )
  112. await createService().applyDefaultSubscriptionSettingsForSubscription(
  113. userSubscription,
  114. new Map([[SettingName.NAMES.FileUploadBytesLimit, '123']]),
  115. )
  116. expect(factory.createSubscriptionSetting).toHaveBeenNthCalledWith(
  117. 1,
  118. {
  119. name: SettingName.NAMES.FileUploadBytesUsed,
  120. sensitive: 0,
  121. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  122. unencryptedValue: '0',
  123. },
  124. {
  125. planName: SubscriptionName.PlusPlan,
  126. user: Promise.resolve(user),
  127. uuid: '1-2-3',
  128. },
  129. )
  130. expect(factory.createSubscriptionSetting).toHaveBeenNthCalledWith(
  131. 2,
  132. {
  133. name: SettingName.NAMES.FileUploadBytesLimit,
  134. sensitive: 0,
  135. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  136. unencryptedValue: '123',
  137. },
  138. {
  139. planName: SubscriptionName.PlusPlan,
  140. user: Promise.resolve(user),
  141. uuid: '1-2-3',
  142. },
  143. )
  144. })
  145. it('should throw error if subscription setting is invalid', async () => {
  146. subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
  147. new Map([
  148. [
  149. 'invalid',
  150. {
  151. value: '0',
  152. sensitive: 0,
  153. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  154. replaceable: true,
  155. },
  156. ],
  157. ]),
  158. )
  159. await expect(createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription)).rejects.toThrow()
  160. })
  161. it('should throw error if setting name is not a subscription setting when applying defaults', async () => {
  162. subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
  163. new Map([
  164. [
  165. SettingName.NAMES.DropboxBackupFrequency,
  166. {
  167. value: '0',
  168. sensitive: 0,
  169. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  170. replaceable: false,
  171. },
  172. ],
  173. ]),
  174. )
  175. await expect(createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription)).rejects.toThrow()
  176. })
  177. it('should reassign existing default settings for a subscription if it is not replaceable', async () => {
  178. subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
  179. new Map([
  180. [
  181. SettingName.NAMES.FileUploadBytesUsed,
  182. {
  183. value: '0',
  184. sensitive: 0,
  185. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  186. replaceable: false,
  187. },
  188. ],
  189. ]),
  190. )
  191. subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(setting)
  192. await createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription)
  193. expect(subscriptionSettingRepository.save).toHaveBeenCalled()
  194. })
  195. it('should create default settings for a subscription if it is not replaceable and not existing', async () => {
  196. subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
  197. new Map([
  198. [
  199. SettingName.NAMES.FileUploadBytesUsed,
  200. {
  201. value: '0',
  202. sensitive: 0,
  203. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  204. replaceable: false,
  205. },
  206. ],
  207. ]),
  208. )
  209. subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(null)
  210. await createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription)
  211. expect(subscriptionSettingRepository.save).toHaveBeenCalledWith(setting)
  212. })
  213. it('should create default settings for a subscription if it is not replaceable and no previous subscription existed', async () => {
  214. subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest.fn().mockReturnValue(
  215. new Map([
  216. [
  217. SettingName.NAMES.FileUploadBytesUsed,
  218. {
  219. value: '0',
  220. sensitive: 0,
  221. serverEncryptionVersion: EncryptionVersion.Unencrypted,
  222. replaceable: false,
  223. },
  224. ],
  225. ]),
  226. )
  227. subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(null)
  228. userSubscriptionRepository.findByUserUuid = jest.fn().mockReturnValue([
  229. {
  230. uuid: '1-2-3',
  231. } as jest.Mocked<UserSubscription>,
  232. ])
  233. await createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription)
  234. expect(subscriptionSettingRepository.save).toHaveBeenCalledWith(setting)
  235. })
  236. it('should not create default settings for a subscription if subscription has no defaults', async () => {
  237. subscriptionSettingsAssociationService.getDefaultSettingsAndValuesForSubscriptionName = jest
  238. .fn()
  239. .mockReturnValue(undefined)
  240. await createService().applyDefaultSubscriptionSettingsForSubscription(userSubscription)
  241. expect(subscriptionSettingRepository.save).not.toHaveBeenCalled()
  242. })
  243. it("should create setting if it doesn't exist", async () => {
  244. const result = await createService().createOrReplace({
  245. userSubscription,
  246. user,
  247. props: {
  248. name: SettingName.NAMES.FileUploadBytesLimit,
  249. unencryptedValue: 'value',
  250. serverEncryptionVersion: 1,
  251. sensitive: false,
  252. },
  253. })
  254. expect(result.status).toEqual('created')
  255. })
  256. it('should throw error if the setting name is not valid', async () => {
  257. await expect(
  258. createService().createOrReplace({
  259. userSubscription,
  260. user,
  261. props: {
  262. name: 'invalid',
  263. unencryptedValue: 'value',
  264. serverEncryptionVersion: 1,
  265. sensitive: false,
  266. },
  267. }),
  268. ).rejects.toThrow()
  269. })
  270. it('should throw error if the setting name is not a subscription setting', async () => {
  271. await expect(
  272. createService().createOrReplace({
  273. userSubscription,
  274. user,
  275. props: {
  276. name: SettingName.NAMES.DropboxBackupFrequency,
  277. unencryptedValue: 'value',
  278. serverEncryptionVersion: 1,
  279. sensitive: false,
  280. },
  281. }),
  282. ).rejects.toThrow()
  283. })
  284. it('should create setting with a given uuid if it does not exist', async () => {
  285. subscriptionSettingRepository.findOneByUuid = jest.fn().mockReturnValue(null)
  286. const result = await createService().createOrReplace({
  287. userSubscription,
  288. user,
  289. props: {
  290. uuid: '1-2-3',
  291. name: SettingName.NAMES.FileUploadBytesLimit,
  292. unencryptedValue: 'value',
  293. serverEncryptionVersion: 1,
  294. sensitive: false,
  295. },
  296. })
  297. expect(result.status).toEqual('created')
  298. })
  299. it('should replace setting if it does exist', async () => {
  300. subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(setting)
  301. const result = await createService().createOrReplace({
  302. userSubscription,
  303. user,
  304. props: {
  305. ...setting,
  306. unencryptedValue: 'value',
  307. serverEncryptionVersion: 1,
  308. },
  309. })
  310. expect(result.status).toEqual('replaced')
  311. })
  312. it('should replace setting with a given uuid if it does exist', async () => {
  313. subscriptionSettingRepository.findOneByUuid = jest.fn().mockReturnValue(setting)
  314. const result = await createService().createOrReplace({
  315. userSubscription,
  316. user,
  317. props: {
  318. ...setting,
  319. uuid: '1-2-3',
  320. unencryptedValue: 'value',
  321. serverEncryptionVersion: 1,
  322. },
  323. })
  324. expect(result.status).toEqual('replaced')
  325. })
  326. it('should find and decrypt the value of a setting for user', async () => {
  327. setting = {
  328. value: 'encrypted',
  329. serverEncryptionVersion: EncryptionVersion.Default,
  330. } as jest.Mocked<SubscriptionSetting>
  331. subscriptionSettingRepository.findLastByNameAndUserSubscriptionUuid = jest.fn().mockReturnValue(setting)
  332. expect(
  333. await createService().findSubscriptionSettingWithDecryptedValue({
  334. userSubscriptionUuid: '2-3-4',
  335. userUuid: '1-2-3',
  336. subscriptionSettingName: SettingName.create(SettingName.NAMES.FileUploadBytesLimit).getValue(),
  337. }),
  338. ).toEqual({
  339. serverEncryptionVersion: 1,
  340. value: 'decrypted',
  341. })
  342. })
  343. it('should throw error when trying to find and decrypt a setting with invalid subscription setting name', async () => {
  344. await expect(
  345. createService().findSubscriptionSettingWithDecryptedValue({
  346. userSubscriptionUuid: '2-3-4',
  347. userUuid: '1-2-3',
  348. subscriptionSettingName: SettingName.create(SettingName.NAMES.DropboxBackupFrequency).getValue(),
  349. }),
  350. ).rejects.toThrow()
  351. })
  352. })