@velocitycareerlabs/server-careerwallet 1.25.0-dev-build.12642c864

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/.localdev.env +47 -0
  2. package/.standalone.env +13 -0
  3. package/LICENSE +248 -0
  4. package/jest.config.js +20 -0
  5. package/migrate-mongo.config.js +25 -0
  6. package/migrations/20211017180227-create-personas.js +478 -0
  7. package/migrations/20211026185916-create-vanessa.js +79 -0
  8. package/migrations/20211026185917-update-personas.js +30 -0
  9. package/migrations/20211108124410-remove-surplus-personas.js +33 -0
  10. package/migrations/20211108132353-fix-vanessa-and-sheila.js +25 -0
  11. package/migrations/20220222123110-add-career-wallet-app-config.js +6 -0
  12. package/migrations/20220411104157-add-min-app-versions.js +15 -0
  13. package/migrations/20220419131726-create-nicole-flores-persona.js +63 -0
  14. package/migrations/20220515114034-update-persona-id-credentials.js +628 -0
  15. package/migrations/20220608093743-disable-mainnet-holderapp-id-verification.js +21 -0
  16. package/migrations/20220609063708-enable-mainnet-holderapp-id-verification.js +21 -0
  17. package/migrations/20220623091507-add-push-url.js +43 -0
  18. package/migrations/20220624133205-set-min-app-versions-to-11.js +16 -0
  19. package/migrations/20220710125326-set-min-app-version-to-0.10.7.js +16 -0
  20. package/migrations/20220811103500-add-verification-service-disclosure-deeplink.js +45 -0
  21. package/migrations/20220811123751-add-holderapp-dids-to-config.js +74 -0
  22. package/migrations/20220818072306-add-holderapp-endpoints-to-config.js +21 -0
  23. package/migrations/20220825090656-update-deeplink.js +46 -0
  24. package/migrations/20221003151823-app-config-add-public-verification-api.js +18 -0
  25. package/migrations/20221116085242-add-holderapp-sdk-to-config.js +15 -0
  26. package/migrations/20221121091030-update-holderapp-deeplink.js +19 -0
  27. package/migrations/20221128103425-update-holderapp-presentation-template.js +19 -0
  28. package/migrations/20221221091436-app-config-add-oauth.js +13 -0
  29. package/migrations/20221226205900-app-config-add-presentation-extension-api.js +18 -0
  30. package/migrations/20230120113141-update-holderapp-cashSiquence.js +15 -0
  31. package/migrations/20230123084103-update-holderapp-cacheSequence.js +15 -0
  32. package/migrations/20230214083430-update-holderapp-cache-Sequence.js +15 -0
  33. package/migrations/20230225173335-set-min-app-version-1.5.1.js +30 -0
  34. package/migrations/20230323120629-add-sunil-singh-persona.js +87 -0
  35. package/migrations/20230329081529-add-personas-by-env.js +294 -0
  36. package/migrations/20230329103219-remove-unused-personas.js +14 -0
  37. package/migrations/20230504090208-disable-yoti-migration.js +21 -0
  38. package/migrations/20230504123425-set-min-app-version-1.8.1.js +38 -0
  39. package/migrations/20230504185047-enable-yoti-migration.js +21 -0
  40. package/migrations/20230524053203-add-devices-index.js +16 -0
  41. package/migrations/20230704000002-add-common-holder-endpoints-to-config.js +16 -0
  42. package/migrations/20230704000003-add-linkedin-holder-endpoints-to-config.js +18 -0
  43. package/migrations/20230704104055-update-push-url-and-yoti-url.js +18 -0
  44. package/migrations/20230705000001-add-liburl-to-holderapp-config.js +20 -0
  45. package/migrations/20230814113134-app-config-add-oauth-client-id.js +18 -0
  46. package/migrations/20230821154136-yoti-new-session-url-fix.js +19 -0
  47. package/migrations/20230907134442-update-holderapp-cache-Sequence.js +15 -0
  48. package/migrations/20230919180000-set-holderapp-min-app-versions-1.15.0.js +16 -0
  49. package/migrations/20231011083137-update-holderapp-cache-sequence-6.js +15 -0
  50. package/migrations/20231102083252-set-holderapp-min-app-versions-1.14.0.js +16 -0
  51. package/migrations/20231108202229-set-holderapp-min-app-versions-1.15.0.js +16 -0
  52. package/migrations/20231115143332-holderapp-isDirectIssuerCheckOn-true.js +16 -0
  53. package/migrations/20231120100020-insert-didKeyMetadatum-for-old-accounts.js +59 -0
  54. package/migrations/20231207142742-remove-devices-from-accounts.js +46 -0
  55. package/migrations/20231226090805-app-config-set-direct-issuer-check.js +13 -0
  56. package/migrations/202312271524-set-holderapp-app-version-1.15.1.js +16 -0
  57. package/migrations/20232211171700-app-config-update-base-urls.js +18 -0
  58. package/migrations/20240102093506-holderapp-revert-version-1.15.0.js +16 -0
  59. package/migrations/202401041618111-holderapp-isDebugOn-false.js +17 -0
  60. package/migrations/20240131095122-test-personas-add-did.js +102 -0
  61. package/migrations/202402051547000-update-sdk.js +18 -0
  62. package/migrations/20240206101448-update-personas-vc-to-v2.js +137 -0
  63. package/migrations/202402061233000-set-xVnfProtocolVersion-2.js +13 -0
  64. package/migrations/202402081240000-set-xVnfProtocolVersion-1.js +13 -0
  65. package/migrations/202402131319-set-holderapp-min-versions-1.17.js +16 -0
  66. package/migrations/202402141152-set-holderapp-min-version-1.16.js +16 -0
  67. package/migrations/20240221123501-transform-account-keys-to-stringified-jwk.js +81 -0
  68. package/migrations/202402290955-update-holderapp-to-verifyMyCreds.js +36 -0
  69. package/migrations/20240311134223-update-keyid-persona.js +66 -0
  70. package/migrations/20240312141618-vl-7409-persona-update-maria-williams.js +82 -0
  71. package/migrations/202403181733000-remove-devices-from-all-accounts.js +27 -0
  72. package/migrations/20240401091041-set-holderapp-min-version-1.18.1.js +16 -0
  73. package/migrations/202404071847-app-config-update-yoti-url.js +19 -0
  74. package/migrations/20240724063405-vl-3827-new-yoti-session-url.js +19 -0
  75. package/migrations/20240731112302-vl-8160-add-persona-keys.js +101 -0
  76. package/migrations/20240911115206-vanessa-lin-id-credentials.js +57 -0
  77. package/migrations/202409201219-add-isWalletAvailable-field.js +13 -0
  78. package/migrations/202409221012-set-isWalletAvailable-true.js +13 -0
  79. package/migrations/20240922114643-holderapp-isWalletAvailable-refactor.js +36 -0
  80. package/migrations/20240923132213-update-android-ios-app-config.js +36 -0
  81. package/migrations/20240926061732-inc-cacheSequence-to-7.js +15 -0
  82. package/migrations/20240926073339-inc-cacheSequence-to-7-fix.js +18 -0
  83. package/migrations/202410081217-adam-smith-id-credntials.js +57 -0
  84. package/migrations/202410101243-adam_smith-id-credential.js +57 -0
  85. package/migrations/20241015124307-persona-key-id-type-fix.js +26 -0
  86. package/migrations/202501291027000-set-holderapp-did-web.js +37 -0
  87. package/migrations/environments/dev.env +34 -0
  88. package/migrations/environments/localdev.env +22 -0
  89. package/migrations/environments/prod.env +18 -0
  90. package/migrations/environments/qa.env +54 -0
  91. package/migrations/environments/staging.env +31 -0
  92. package/migrations/environments/test.env +18 -0
  93. package/package.json +84 -0
  94. package/src/assets/category-icons/assessment.png +0 -0
  95. package/src/assets/category-icons/badge.png +0 -0
  96. package/src/assets/category-icons/certification.png +0 -0
  97. package/src/assets/category-icons/education.png +0 -0
  98. package/src/assets/category-icons/employment.png +0 -0
  99. package/src/assets/category-icons/gig.png +0 -0
  100. package/src/assets/category-icons/identity.png +0 -0
  101. package/src/assets/category-icons/pharmacy.png +0 -0
  102. package/src/assets/category-icons/training.png +0 -0
  103. package/src/assets/credentialCategories.json +119 -0
  104. package/src/config/config.js +156 -0
  105. package/src/controllers/api/v0.6/accounts/autohooks.js +36 -0
  106. package/src/controllers/api/v0.6/accounts/controller.js +288 -0
  107. package/src/controllers/api/v0.6/accounts/schemas/careerwallet-accounts-didkeymetadatum-response.schema.js +41 -0
  108. package/src/controllers/api/v0.6/accounts/schemas/careerwallet-accounts-request.schema.js +49 -0
  109. package/src/controllers/api/v0.6/accounts/schemas/careerwallet-accounts-response.schema.js +74 -0
  110. package/src/controllers/api/v0.6/accounts/schemas/careerwallet-get-account-response.schema.js +72 -0
  111. package/src/controllers/api/v0.6/accounts/schemas/index.js +6 -0
  112. package/src/controllers/api/v0.6/careerwallet/appconfig/controller.js +25 -0
  113. package/src/controllers/api/v0.6/careerwallet/appconfig/repo.js +40 -0
  114. package/src/controllers/api/v0.6/careerwallet/autohooks.js +5 -0
  115. package/src/controllers/api/v0.6/careerwallet/consents/controller.js +66 -0
  116. package/src/controllers/api/v0.6/careerwallet/consents/latest/autohooks.js +7 -0
  117. package/src/controllers/api/v0.6/careerwallet/consents/latest/controller.js +76 -0
  118. package/src/controllers/api/v0.6/careerwallet/consents/repo.js +24 -0
  119. package/src/controllers/api/v0.6/careerwallet/consents/schemas/careerwallet-consent-response.schema.js +23 -0
  120. package/src/controllers/api/v0.6/careerwallet/consents/schemas/index.js +3 -0
  121. package/src/controllers/api/v0.6/create_did_key/autohooks.js +10 -0
  122. package/src/controllers/api/v0.6/create_did_key/controller.js +84 -0
  123. package/src/controllers/api/v0.6/create_did_key/schemas/index.js +4 -0
  124. package/src/controllers/api/v0.6/create_did_key/schemas/jwk-did-request.schema.js +20 -0
  125. package/src/controllers/api/v0.6/create_did_key/schemas/jwk-did-response.schema.js +41 -0
  126. package/src/controllers/api/v0.6/create_jwk/autohooks.js +10 -0
  127. package/src/controllers/api/v0.6/create_jwk/controller.js +46 -0
  128. package/src/controllers/api/v0.6/create_jwk/schemas/index.js +3 -0
  129. package/src/controllers/api/v0.6/create_jwk/schemas/jwk-response.schema.js +33 -0
  130. package/src/controllers/api/v0.6/credential-categories/controller.js +35 -0
  131. package/src/controllers/api/v0.6/devices/autohooks.js +11 -0
  132. package/src/controllers/api/v0.6/devices/controller.js +323 -0
  133. package/src/controllers/api/v0.6/devices/repo.js +27 -0
  134. package/src/controllers/api/v0.6/devices/schemas/device.schema.json +43 -0
  135. package/src/controllers/api/v0.6/devices/schemas/index.js +3 -0
  136. package/src/controllers/api/v0.6/feedback/controller.js +24 -0
  137. package/src/controllers/api/v0.6/feedback/schemas/index.js +3 -0
  138. package/src/controllers/api/v0.6/feedback/schemas/new-feedback.schema.js +47 -0
  139. package/src/controllers/api/v0.6/jwt/autohooks.js +10 -0
  140. package/src/controllers/api/v0.6/jwt/controller.js +44 -0
  141. package/src/controllers/api/v0.6/jwt/schemas/index.js +4 -0
  142. package/src/controllers/api/v0.6/jwt/schemas/jwt-request.schema.js +40 -0
  143. package/src/controllers/api/v0.6/jwt/schemas/jwt-response.schema.js +14 -0
  144. package/src/controllers/api/v0.6/oauth/controller.js +131 -0
  145. package/src/controllers/api/v0.6/push/controller.js +296 -0
  146. package/src/controllers/api/v0.6/push/firebase-initializer.js +21 -0
  147. package/src/controllers/api/v0.6/push/notification-types.js +37 -0
  148. package/src/controllers/api/v0.6/push/push-gateway-auth.js +123 -0
  149. package/src/controllers/api/v0.6/push/repo.js +24 -0
  150. package/src/controllers/api/v0.6/verification-offers/repo.js +25 -0
  151. package/src/controllers/api/v0.6/verify/autohooks.js +15 -0
  152. package/src/controllers/api/v0.6/verify/controller.js +348 -0
  153. package/src/controllers/api/v0.6/verify/repo.js +23 -0
  154. package/src/controllers/api/v0.6/verify/verification-credential-types.js +6 -0
  155. package/src/controllers/jwt/autohooks.js +10 -0
  156. package/src/controllers/jwt/controller.js +106 -0
  157. package/src/controllers/jwt/schemas/index.js +31 -0
  158. package/src/controllers/jwt/schemas/jwt-decode.response.200.schema.json +20 -0
  159. package/src/controllers/jwt/schemas/jwt-decode.schema.json +15 -0
  160. package/src/controllers/jwt/schemas/jwt-sign.response.200.schema.json +14 -0
  161. package/src/controllers/jwt/schemas/jwt-sign.schema.json +40 -0
  162. package/src/controllers/jwt/schemas/jwt-verify.response.200.schema.json +17 -0
  163. package/src/controllers/jwt/schemas/jwt-verify.schema.json +19 -0
  164. package/src/controllers/reference/autohooks.js +5 -0
  165. package/src/controllers/reference/countries/controller.js +81 -0
  166. package/src/controllers/reference/personas/controller.js +91 -0
  167. package/src/controllers/reference/personas/repo.js +29 -0
  168. package/src/controllers/reference/personas/schemas/index.js +3 -0
  169. package/src/controllers/reference/personas/schemas/persona.schema.json +108 -0
  170. package/src/controllers/root/controller.js +27 -0
  171. package/src/entities/accounts/constants.js +18 -0
  172. package/src/entities/accounts/domain/index.js +3 -0
  173. package/src/entities/accounts/domain/merge-scopes.js +9 -0
  174. package/src/entities/accounts/index.js +5 -0
  175. package/src/entities/accounts/repos/accounts.repo.js +64 -0
  176. package/src/entities/accounts/repos/id-token-claims-extension.js +31 -0
  177. package/src/entities/accounts/repos/index.js +3 -0
  178. package/src/entities/devices/constants.js +7 -0
  179. package/src/entities/devices/index.js +3 -0
  180. package/src/entities/feedback/domain/generate-feedback-email.js +23 -0
  181. package/src/entities/feedback/domain/index.js +3 -0
  182. package/src/entities/feedback/index.js +3 -0
  183. package/src/entities/index.js +9 -0
  184. package/src/entities/key-pairs/index.js +4 -0
  185. package/src/entities/key-pairs/orchestrators/generate-jwk.js +28 -0
  186. package/src/entities/key-pairs/orchestrators/get-key-pair.js +58 -0
  187. package/src/entities/key-pairs/orchestrators/index.js +4 -0
  188. package/src/entities/key-pairs/repos/index.js +3 -0
  189. package/src/entities/key-pairs/repos/key-pairs.repo.js +23 -0
  190. package/src/entities/oauth/constants.js +13 -0
  191. package/src/entities/oauth/domain/build-access-token.js +14 -0
  192. package/src/entities/oauth/domain/index.js +10 -0
  193. package/src/entities/oauth/domain/validate-audience.js +13 -0
  194. package/src/entities/oauth/domain/validate-client-id.js +13 -0
  195. package/src/entities/oauth/domain/validate-credential.js +16 -0
  196. package/src/entities/oauth/domain/validate-presentation.js +17 -0
  197. package/src/entities/oauth/domain/validate-refresh-token.js +17 -0
  198. package/src/entities/oauth/domain/validate-scope.js +30 -0
  199. package/src/entities/oauth/domain/verify-presentation.js +24 -0
  200. package/src/entities/oauth/index.js +4 -0
  201. package/src/entities/pushes/domains/errors.js +11 -0
  202. package/src/entities/pushes/domains/index.js +3 -0
  203. package/src/entities/pushes/index.js +3 -0
  204. package/src/entities/verification-code-attempts/index.js +3 -0
  205. package/src/entities/verification-code-attempts/orchestrators/index.js +3 -0
  206. package/src/entities/verification-code-attempts/orchestrators/validate-verification-code-attempts.js +79 -0
  207. package/src/entities/verification-code-attempts/repo.js +99 -0
  208. package/src/entities/verifications/constants.js +8 -0
  209. package/src/entities/verifications/index.js +3 -0
  210. package/src/helpers/caching-constants.js +6 -0
  211. package/src/helpers/index.js +3 -0
  212. package/src/index.js +15 -0
  213. package/src/init-server.js +88 -0
  214. package/src/plugins/index.js +4 -0
  215. package/src/plugins/vcl-verification-version-plugin.js +10 -0
  216. package/src/plugins/verify-access-token-plugin.js +116 -0
  217. package/src/standalone.js +8 -0
  218. package/test/accounts-controller.test.js +893 -0
  219. package/test/accounts-repo.test.js +92 -0
  220. package/test/careerwallet-config-controller.test.js +142 -0
  221. package/test/careerwallet-consents-controller.test.js +409 -0
  222. package/test/create_did_key-controller.test.js +397 -0
  223. package/test/create_jwk-controller.test.js +188 -0
  224. package/test/credential-categories-controller.test.js +29 -0
  225. package/test/credential-icons-controller.test.js +36 -0
  226. package/test/devices-controller.test.js +1025 -0
  227. package/test/factories/accounts-factory.js +15 -0
  228. package/test/factories/career-wallet-config-factory.js +60 -0
  229. package/test/factories/consents-career-wallet-factory.js +21 -0
  230. package/test/factories/devices-factory.js +16 -0
  231. package/test/factories/key-pairs-factory.js +18 -0
  232. package/test/factories/notifications-factory.js +16 -0
  233. package/test/factories/persona-factory.js +17 -0
  234. package/test/factories/refresh-tokens-factory.js +14 -0
  235. package/test/factories/verification-code-attempts-factory.js +17 -0
  236. package/test/factories/verification-factory.js +15 -0
  237. package/test/factories/verification-offer-factory.js +20 -0
  238. package/test/feedback-controller.test.js +225 -0
  239. package/test/helpers/.env.test +39 -0
  240. package/test/helpers/access-token.js +59 -0
  241. package/test/helpers/careerwallet-build-fastify.js +20 -0
  242. package/test/helpers/yoti.js +96 -0
  243. package/test/id-verification-controller.test.js +27 -0
  244. package/test/jwt-controller.test.js +1519 -0
  245. package/test/oauth-controller.test.js +639 -0
  246. package/test/push-controller.test.js +733 -0
  247. package/test/push-gateway-auth.test.js +208 -0
  248. package/test/reference-countries-controller.test.js +45 -0
  249. package/test/reference-personas-controller.test.js +179 -0
  250. package/test/root.test.js +21 -0
  251. package/test/swagger.test.js +21 -0
  252. package/test/verification-controller.test.js +1372 -0
@@ -0,0 +1,733 @@
1
+ const { mongoDb } = require('@spencejs/spence-mongo-repos');
2
+ const { ObjectId } = require('mongodb');
3
+ const { nanoid } = require('nanoid');
4
+ const { errorResponseMatcher } = require('@velocitycareerlabs/tests-helpers');
5
+ const initDevicesFactory = require('./factories/devices-factory');
6
+ const initAccountsFactory = require('./factories/accounts-factory');
7
+ const buildFastify = require('./helpers/careerwallet-build-fastify');
8
+ const { accountsRepoPlugin } = require('../src/entities');
9
+
10
+ const {
11
+ generateNotification,
12
+ } = require('../src/controllers/api/v0.6/push/notification-types');
13
+
14
+ const testDID = 'test-did';
15
+
16
+ const mockSend = jest.fn().mockResolvedValue(true);
17
+ const mockMessaging = jest.fn(() => ({
18
+ send: mockSend,
19
+ }));
20
+ const mockInitializeApp = jest.fn();
21
+ const mockCredential = jest.fn();
22
+
23
+ const ttlSeconds = 60 * 60 * 24;
24
+ const notificationObj = {
25
+ body: 'You have new credential offers.',
26
+ title: 'New credential offer',
27
+ };
28
+ const sendPayloadExpectation = (deviceId, notification, data) => ({
29
+ token: deviceId,
30
+ ...(!!data && { data }),
31
+ notification: notification || notificationObj,
32
+ android: {
33
+ ttl: ttlSeconds * 1000,
34
+ priority: 'high',
35
+ notification: {
36
+ sound: 'default',
37
+ icon: 'https://docs.velocitycareerlabs.io/Logos/vcl-app-push-logo.png',
38
+ },
39
+ },
40
+ apns: {
41
+ headers: {
42
+ 'apns-priority': '5',
43
+ },
44
+ payload: {
45
+ aps: {
46
+ sound: 'default',
47
+ icon: 'https://docs.velocitycareerlabs.io/Logos/vcl-app-push-logo.png',
48
+ 'content-available': 1,
49
+ },
50
+ },
51
+ },
52
+ webpush: {
53
+ headers: {
54
+ TTL: `${ttlSeconds}`,
55
+ Urgency: 'high',
56
+ },
57
+ },
58
+ });
59
+
60
+ jest.mock('../src/controllers/api/v0.6/push/push-gateway-auth', () => ({
61
+ pushGatewayAuth: () => Promise.resolve(),
62
+ }));
63
+
64
+ jest.mock('firebase-admin', () => {
65
+ return {
66
+ initializeApp: mockInitializeApp,
67
+ credential: { cert: mockCredential },
68
+ messaging: mockMessaging,
69
+ };
70
+ });
71
+
72
+ describe('push endpoints', () => {
73
+ let fastify;
74
+ let accountsRepo;
75
+ let persistDevices;
76
+ let persistAccounts;
77
+ let device;
78
+ let account;
79
+ let pushToken;
80
+
81
+ beforeAll(async () => {
82
+ fastify = buildFastify();
83
+ await fastify.ready();
84
+ accountsRepo = await accountsRepoPlugin(fastify)({
85
+ log: fastify.log,
86
+ config: fastify.config,
87
+ });
88
+ ({ persistAccounts } = initAccountsFactory(fastify));
89
+ ({ persistDevices } = initDevicesFactory(fastify));
90
+ });
91
+
92
+ afterAll(async () => {
93
+ await mongoDb().collection('devices').deleteMany({});
94
+ await mongoDb().collection('accounts').deleteMany({});
95
+ await mongoDb().collection('notifications').deleteMany({});
96
+
97
+ await fastify.close();
98
+ });
99
+
100
+ beforeEach(() => {
101
+ jest.clearAllMocks();
102
+ });
103
+
104
+ describe('trigger push on oracle', () => {
105
+ beforeEach(async () => {
106
+ pushToken = nanoid();
107
+ account = await persistAccounts({
108
+ pushTokens: {
109
+ [pushToken]: {
110
+ did: testDID,
111
+ date: new Date(),
112
+ },
113
+ },
114
+ });
115
+ device = await persistDevices({
116
+ accountId: new ObjectId(account._id),
117
+ pushActivated: true,
118
+ });
119
+ });
120
+
121
+ it('should return 400 when id is missing', async () => {
122
+ const response = await fastify.injectJson({
123
+ method: 'POST',
124
+ url: '/api/v0.6/push',
125
+ payload: {
126
+ pushToken: device.pushToken,
127
+ data: {
128
+ notificationType: 'NewOffersReady',
129
+ },
130
+ },
131
+ });
132
+
133
+ expect(response.statusCode).toEqual(400);
134
+ });
135
+
136
+ it('should return 400 when notificationType is missing', async () => {
137
+ const response = await fastify.injectJson({
138
+ method: 'POST',
139
+ url: '/api/v0.6/push',
140
+ payload: {
141
+ id: new ObjectId(),
142
+ pushToken: device.pushToken,
143
+ data: {},
144
+ },
145
+ });
146
+
147
+ expect(response.statusCode).toEqual(400);
148
+ });
149
+
150
+ it('should return 400 when notificationType is not correct', async () => {
151
+ const response = await fastify.injectJson({
152
+ method: 'POST',
153
+ url: '/api/v0.6/push',
154
+ payload: {
155
+ id: new ObjectId(),
156
+ pushToken: device.pushToken,
157
+ data: {
158
+ notificationType: 'WrongType',
159
+ },
160
+ },
161
+ });
162
+
163
+ expect(response.statusCode).toEqual(400);
164
+ });
165
+
166
+ it('should run the generateNotification function without errors', async () => {
167
+ const data = {};
168
+ const message = undefined;
169
+ const notification = generateNotification(data, message);
170
+
171
+ expect(notification).toEqual(undefined);
172
+ });
173
+ it('should generate a correct notification accordingly to the NewOffersReady type', async () => {
174
+ [
175
+ {
176
+ body: 'You have new credential offers.',
177
+ count: 4,
178
+ },
179
+ {
180
+ body: 'You have new credential offers.',
181
+ count: '1',
182
+ },
183
+ ].forEach(({ body, count }) => {
184
+ const data = {
185
+ notificationType: 'NewOffersReady',
186
+ credentialTypes: ['EducationDegree', 'PastEmploymentPosition'],
187
+ count,
188
+ };
189
+ const message =
190
+ 'This custom message is gonna be ignored for this notification type';
191
+ const notification = generateNotification(data, message);
192
+
193
+ expect(notification).toEqual({
194
+ body,
195
+ title: 'New credential offer',
196
+ });
197
+ });
198
+ });
199
+
200
+ it('should generate a correct notification accordingly to the CredentialReplaced type', async () => {
201
+ const data = {
202
+ notificationType: 'CredentialReplaced',
203
+ credentialTypes: ['EducationDegree', 'PastEmploymentPosition'],
204
+ };
205
+ const message = undefined;
206
+ const notification = generateNotification(data, message);
207
+
208
+ expect(notification).toEqual({
209
+ body: 'Your EducationDegree credential can be updated. Tap here to receive the update.',
210
+ title: 'Updated credential available',
211
+ });
212
+ });
213
+
214
+ it('should generate a correct notification accordingly to the CredentialReplaced type with the custom message', async () => {
215
+ const data = {
216
+ notificationType: 'CredentialReplaced',
217
+ credentialTypes: ['EducationDegree', 'PastEmploymentPosition'],
218
+ };
219
+ const message =
220
+ 'This custom message is gonna be showed instead of the deafault one.';
221
+ const notification = generateNotification(data, message);
222
+
223
+ expect(notification).toEqual({
224
+ body: 'This custom message is gonna be showed instead of the deafault one.',
225
+ title: 'Updated credential available',
226
+ });
227
+ });
228
+
229
+ it('should generate a correct notification accordingly to the CredentialRevoked type', async () => {
230
+ const data = {
231
+ notificationType: 'CredentialRevoked',
232
+ credentialTypes: [
233
+ 'CurrentEmploymentPosition',
234
+ 'PastEmploymentPosition',
235
+ ],
236
+ };
237
+ const message = undefined;
238
+ const notification = generateNotification(data, message);
239
+
240
+ expect(notification).toEqual({
241
+ body: 'Your CurrentEmploymentPosition credential has been revoked.',
242
+ title: 'Credential revoked',
243
+ });
244
+ });
245
+
246
+ it('should generate a correct notification accordingly to the PresentationVerified type', async () => {
247
+ const data = {
248
+ notificationType: 'PresentationVerified',
249
+ credentialTypes: ['EducationDegree'],
250
+ };
251
+ const message = undefined;
252
+ const notification = generateNotification(data, message);
253
+
254
+ expect(notification).toEqual({
255
+ body: 'Credentials that you shared have been verified',
256
+ title: 'Credentials verified',
257
+ });
258
+ });
259
+
260
+ it('should return 400 when the data property is missing', async () => {
261
+ const response = await fastify.injectJson({
262
+ method: 'POST',
263
+ url: '/api/v0.6/push',
264
+ payload: {
265
+ id: new ObjectId(),
266
+ pushToken: device.pushToken,
267
+ },
268
+ });
269
+
270
+ expect(response.statusCode).toEqual(400);
271
+ });
272
+
273
+ it('should return 400 when pushToken is missing', async () => {
274
+ const response = await fastify.injectJson({
275
+ method: 'POST',
276
+ url: '/api/v0.6/push',
277
+ payload: {
278
+ id: new ObjectId(),
279
+ data: {
280
+ notificationType: 'NewOffersReady',
281
+ },
282
+ },
283
+ });
284
+
285
+ expect(response.statusCode).toEqual(400);
286
+ });
287
+
288
+ it('should return 204 when notification is sent', async () => {
289
+ const filter = {
290
+ [`pushTokens.${pushToken}`]: { $exists: true },
291
+ };
292
+ const accountWithUsedPushToken = await accountsRepo.findOne({
293
+ filter,
294
+ });
295
+ expect(accountWithUsedPushToken).toBeDefined();
296
+ const response = await fastify.injectJson({
297
+ method: 'POST',
298
+ url: '/api/v0.6/push',
299
+ payload: {
300
+ id: new ObjectId(),
301
+ pushToken,
302
+ data: {
303
+ notificationType: 'NewOffersReady',
304
+ },
305
+ },
306
+ });
307
+ expect(response.statusCode).toEqual(204);
308
+ expect(mockMessaging).toHaveBeenCalledTimes(1);
309
+ expect(mockSend).toHaveBeenCalledTimes(1);
310
+ expect(mockSend).toHaveBeenCalledWith(
311
+ sendPayloadExpectation(device.deviceId, null, {
312
+ count: '0',
313
+ credentialTypes: '[]',
314
+ notificationId: expect.any(String),
315
+ notificationType: 'NewOffersReady',
316
+ })
317
+ );
318
+ const accountWithoutUsedPushToken = await accountsRepo.findOne({
319
+ filter,
320
+ });
321
+ expect(accountWithoutUsedPushToken).toStrictEqual(
322
+ accountWithUsedPushToken
323
+ );
324
+ });
325
+
326
+ it('should have the notificationId in data when notification sent', async () => {
327
+ const response = await fastify.injectJson({
328
+ method: 'POST',
329
+ url: '/api/v0.6/push',
330
+ payload: {
331
+ id: new ObjectId(),
332
+ pushToken,
333
+ data: {
334
+ count: 2,
335
+ notificationType: 'NewOffersReady',
336
+ credentialTypes: ['EducationDegree', 'PastEmploymentPosition'],
337
+ },
338
+ },
339
+ });
340
+
341
+ expect(response.statusCode).toEqual(204);
342
+ expect(mockMessaging).toHaveBeenCalledTimes(1);
343
+ expect(mockSend).toHaveBeenCalledTimes(1);
344
+ expect(mockSend).toHaveBeenCalledWith(
345
+ sendPayloadExpectation(device.deviceId, null, {
346
+ count: '2',
347
+ credentialTypes: '["EducationDegree","PastEmploymentPosition"]',
348
+ notificationId: expect.stringMatching(/^[0-9a-fA-F]{24}$/),
349
+ notificationType: 'NewOffersReady',
350
+ })
351
+ );
352
+ });
353
+
354
+ it('should have a saved notification on 204 when notification is sent', async () => {
355
+ await mongoDb().collection('notifications').deleteMany({});
356
+
357
+ const response = await fastify.injectJson({
358
+ method: 'POST',
359
+ url: '/api/v0.6/push',
360
+ payload: {
361
+ id: new ObjectId(),
362
+ pushToken,
363
+ data: {
364
+ count: 1,
365
+ notificationType: 'NewOffersReady',
366
+ },
367
+ },
368
+ });
369
+ const notifications = await mongoDb()
370
+ .collection('notifications')
371
+ .find({})
372
+ .toArray();
373
+
374
+ expect(notifications.length).toEqual(1);
375
+ expect(notifications[0].data.notificationType).toEqual('NewOffersReady');
376
+ expect(response.statusCode).toEqual(204);
377
+ expect(mockMessaging).toHaveBeenCalledTimes(1);
378
+ expect(mockSend).toHaveBeenCalledTimes(1);
379
+ });
380
+
381
+ it('should have a saved notification on 204 and send notification when there is no pushActivated flag', async () => {
382
+ await mongoDb().collection('notifications').deleteMany({});
383
+ const response = await fastify.injectJson({
384
+ method: 'POST',
385
+ url: '/api/v0.6/push',
386
+ payload: {
387
+ id: new ObjectId(),
388
+ pushToken,
389
+ data: {
390
+ notificationType: 'NewOffersReady',
391
+ },
392
+ },
393
+ });
394
+ const notifications = await mongoDb()
395
+ .collection('notifications')
396
+ .find({})
397
+ .toArray();
398
+
399
+ expect(notifications.length).toEqual(1);
400
+ expect(notifications[0].data.notificationType).toEqual('NewOffersReady');
401
+ expect(response.statusCode).toEqual(204);
402
+
403
+ expect(mockMessaging).toHaveBeenCalledTimes(1);
404
+ expect(mockSend).toHaveBeenCalledTimes(1);
405
+ });
406
+
407
+ it('should have a saved notification on 204 and send notification when pushActivated flag is true', async () => {
408
+ await mongoDb().collection('notifications').deleteMany({});
409
+ const pushTokenForPushActivated = nanoid();
410
+ const accountForPushActivated = await persistAccounts({
411
+ pushTokens: {
412
+ [pushTokenForPushActivated]: {
413
+ did: testDID,
414
+ date: new Date(),
415
+ },
416
+ },
417
+ });
418
+ await persistDevices({
419
+ accountId: new ObjectId(accountForPushActivated._id),
420
+ pushActivated: true,
421
+ });
422
+ const response = await fastify.injectJson({
423
+ method: 'POST',
424
+ url: '/api/v0.6/push',
425
+ payload: {
426
+ id: new ObjectId(),
427
+ pushToken: pushTokenForPushActivated,
428
+ data: {
429
+ notificationType: 'NewOffersReady',
430
+ },
431
+ },
432
+ });
433
+ const notifications = await mongoDb()
434
+ .collection('notifications')
435
+ .find({})
436
+ .toArray();
437
+
438
+ expect(notifications.length).toEqual(1);
439
+ expect(notifications[0].data.notificationType).toEqual('NewOffersReady');
440
+ expect(response.statusCode).toEqual(204);
441
+
442
+ expect(mockMessaging).toHaveBeenCalledTimes(1);
443
+ expect(mockSend).toHaveBeenCalledTimes(1);
444
+ });
445
+
446
+ it('should have a saved notification on 204 but not send notification when pushActivated flag is false', async () => {
447
+ await mongoDb().collection('notifications').deleteMany({});
448
+ const pushTokenForNonPushAxtivated = nanoid();
449
+ const accountForNonPushActivated = await persistAccounts({
450
+ pushTokens: {
451
+ [pushTokenForNonPushAxtivated]: {
452
+ did: testDID,
453
+ date: new Date(),
454
+ },
455
+ },
456
+ });
457
+ await persistDevices({
458
+ accountId: new ObjectId(accountForNonPushActivated._id),
459
+ pushActivated: false,
460
+ });
461
+ const response = await fastify.injectJson({
462
+ method: 'POST',
463
+ url: '/api/v0.6/push',
464
+ payload: {
465
+ id: new ObjectId(),
466
+ pushToken: pushTokenForNonPushAxtivated,
467
+ data: {
468
+ notificationType: 'NewOffersReady',
469
+ },
470
+ },
471
+ });
472
+ const notifications = await mongoDb()
473
+ .collection('notifications')
474
+ .find({})
475
+ .toArray();
476
+
477
+ expect(notifications.length).toEqual(1);
478
+ expect(notifications[0].data.notificationType).toEqual('NewOffersReady');
479
+ expect(response.statusCode).toEqual(204);
480
+
481
+ expect(mockMessaging).toHaveBeenCalledTimes(0);
482
+ expect(mockSend).toHaveBeenCalledTimes(0);
483
+ });
484
+
485
+ it('should have a saved notifications on 204 and send notifications when pushActivated flag is true for multiple devices', async () => {
486
+ await mongoDb().collection('notifications').deleteMany({});
487
+ const pushTokenForPushActivated = nanoid();
488
+ const accountForPushActivated = await persistAccounts({
489
+ pushTokens: {
490
+ [pushTokenForPushActivated]: {
491
+ did: testDID,
492
+ date: `${new Date()}`,
493
+ },
494
+ },
495
+ });
496
+ const deviceFirst = await persistDevices({
497
+ accountId: new ObjectId(accountForPushActivated._id),
498
+ pushActivated: true,
499
+ });
500
+ const deviceSecond = await persistDevices({
501
+ accountId: new ObjectId(accountForPushActivated._id),
502
+ pushActivated: true,
503
+ });
504
+
505
+ // Another account to check that push notification goes only for first one
506
+ const anotherPushTokenForPushActivated = nanoid();
507
+ const anotherAccountForPushActivated = await persistAccounts({
508
+ pushTokens: {
509
+ [anotherPushTokenForPushActivated]: {
510
+ did: testDID,
511
+ date: `${new Date()}`,
512
+ },
513
+ },
514
+ });
515
+ await persistDevices({
516
+ accountId: new ObjectId(anotherAccountForPushActivated._id),
517
+ pushActivated: true,
518
+ });
519
+
520
+ const response = await fastify.injectJson({
521
+ method: 'POST',
522
+ url: '/api/v0.6/push',
523
+ payload: {
524
+ id: new ObjectId(),
525
+ pushToken: pushTokenForPushActivated,
526
+ data: {
527
+ notificationType: 'NewOffersReady',
528
+ },
529
+ },
530
+ });
531
+ const notifications = await mongoDb()
532
+ .collection('notifications')
533
+ .find({})
534
+ .toArray();
535
+
536
+ expect(notifications.length).toEqual(2);
537
+ expect(notifications[0].data.notificationType).toEqual('NewOffersReady');
538
+ expect(notifications[1].data.notificationType).toEqual('NewOffersReady');
539
+ expect(response.statusCode).toEqual(204);
540
+
541
+ expect(mockMessaging).toHaveBeenCalledTimes(2);
542
+ expect(mockSend).toHaveBeenCalledTimes(2);
543
+ expect(mockSend).toHaveBeenNthCalledWith(
544
+ 1,
545
+ sendPayloadExpectation(deviceSecond.deviceId, null, {
546
+ count: '0',
547
+ credentialTypes: '[]',
548
+ notificationId: expect.any(String),
549
+ notificationType: 'NewOffersReady',
550
+ })
551
+ );
552
+ expect(mockSend).toHaveBeenNthCalledWith(
553
+ 2,
554
+ sendPayloadExpectation(deviceFirst.deviceId, null, {
555
+ count: '0',
556
+ credentialTypes: '[]',
557
+ notificationId: expect.any(String),
558
+ notificationType: 'NewOffersReady',
559
+ })
560
+ );
561
+
562
+ const firstAccount = await accountsRepo.findOne({
563
+ filter: {
564
+ [`pushTokens.${pushTokenForPushActivated}`]: { $exists: true },
565
+ },
566
+ });
567
+ const secondAccount = await accountsRepo.findOne({
568
+ filter: {
569
+ [`pushTokens.${anotherPushTokenForPushActivated}`]: { $exists: true },
570
+ },
571
+ });
572
+ expect(firstAccount).toStrictEqual({
573
+ ...accountForPushActivated,
574
+ _id: new ObjectId(accountForPushActivated._id),
575
+ createdAt: expect.any(Date),
576
+ updatedAt: expect.any(Date),
577
+ });
578
+ expect(secondAccount).toStrictEqual({
579
+ ...anotherAccountForPushActivated,
580
+ _id: new ObjectId(anotherAccountForPushActivated._id),
581
+ createdAt: expect.any(Date),
582
+ updatedAt: expect.any(Date),
583
+ });
584
+ });
585
+
586
+ it('should have a saved notification on 204 and send notification when pushActivated flag is true for old devices with pushToken', async () => {
587
+ await mongoDb().collection('notifications').deleteMany({});
588
+ await mongoDb().collection('accounts').deleteMany({});
589
+ await persistDevices({
590
+ pushActivated: true,
591
+ pushToken,
592
+ });
593
+ const response = await fastify.injectJson({
594
+ method: 'POST',
595
+ url: '/api/v0.6/push',
596
+ payload: {
597
+ id: new ObjectId(),
598
+ pushToken,
599
+ data: {
600
+ notificationType: 'NewOffersReady',
601
+ },
602
+ },
603
+ });
604
+ const notifications = await mongoDb()
605
+ .collection('notifications')
606
+ .find({})
607
+ .toArray();
608
+
609
+ expect(notifications.length).toEqual(1);
610
+ expect(notifications[0].data.notificationType).toEqual('NewOffersReady');
611
+ expect(response.statusCode).toEqual(204);
612
+
613
+ expect(mockMessaging).toHaveBeenCalledTimes(1);
614
+ expect(mockSend).toHaveBeenCalledTimes(1);
615
+ });
616
+
617
+ it('should return 404 when device not found', async () => {
618
+ const response = await fastify.injectJson({
619
+ method: 'POST',
620
+ url: '/api/v0.6/push',
621
+ payload: {
622
+ id: new ObjectId(),
623
+ pushToken: 'some-token',
624
+ data: {
625
+ notificationType: 'NewOffersReady',
626
+ },
627
+ },
628
+ });
629
+ expect(response.statusCode).toEqual(404);
630
+ expect(response.json).toEqual(
631
+ errorResponseMatcher({
632
+ errorCode: 'missing_error_code',
633
+ error: 'Not Found',
634
+ message: 'No device registered for token some-token',
635
+ statusCode: 404,
636
+ })
637
+ );
638
+ expect(mockInitializeApp).toHaveBeenCalledTimes(0);
639
+ expect(mockMessaging).toHaveBeenCalledTimes(0);
640
+ expect(mockSend).toHaveBeenCalledTimes(0);
641
+ });
642
+
643
+ it('should return 502 when firebase.send throws with an error', async () => {
644
+ mockSend.mockRejectedValueOnce(new Error('test sendToDevice error'));
645
+ const response = await fastify.injectJson({
646
+ method: 'POST',
647
+ url: '/api/v0.6/push',
648
+ payload: {
649
+ id: new ObjectId(),
650
+ pushToken,
651
+ data: {
652
+ notificationType: 'NewOffersReady',
653
+ },
654
+ },
655
+ });
656
+ expect(response.statusCode).toEqual(204);
657
+ });
658
+
659
+ it('should return 200 generate is called', async () => {
660
+ const filter = {
661
+ [`pushTokens.${pushToken}`]: { $exists: true },
662
+ };
663
+ const accountWithUsedPushToken = await accountsRepo.findOne({
664
+ filter,
665
+ });
666
+ expect(accountWithUsedPushToken).toBeDefined();
667
+ const response = await fastify.injectJson({
668
+ method: 'POST',
669
+ url: '/api/v0.6/push/generate',
670
+ payload: {
671
+ id: new ObjectId(),
672
+ pushToken,
673
+ content: {
674
+ title: 'notification title',
675
+ body: 'notification body',
676
+ },
677
+ },
678
+ });
679
+ expect(response.statusCode).toEqual(200);
680
+ expect(mockMessaging).toHaveBeenCalledTimes(1);
681
+ expect(mockSend).toHaveBeenCalledTimes(1);
682
+ expect(mockSend).toHaveBeenCalledWith(
683
+ sendPayloadExpectation(
684
+ device.deviceId,
685
+ {
686
+ body: 'notification body',
687
+ title: 'notification title',
688
+ },
689
+ null
690
+ )
691
+ );
692
+ const accountWithoutUsedPushToken = await accountsRepo.findOne({
693
+ filter,
694
+ });
695
+ expect(accountWithoutUsedPushToken).toStrictEqual(
696
+ accountWithUsedPushToken
697
+ );
698
+ });
699
+
700
+ it('should return 200 generate is called for old devices with pushToken', async () => {
701
+ await mongoDb().collection('accounts').deleteMany({});
702
+ const newDevice = await persistDevices({
703
+ pushActivated: true,
704
+ pushToken,
705
+ });
706
+ const response = await fastify.injectJson({
707
+ method: 'POST',
708
+ url: '/api/v0.6/push/generate',
709
+ payload: {
710
+ id: new ObjectId(),
711
+ pushToken,
712
+ content: {
713
+ title: 'notification title',
714
+ body: 'notification body',
715
+ },
716
+ },
717
+ });
718
+ expect(response.statusCode).toEqual(200);
719
+ expect(mockMessaging).toHaveBeenCalledTimes(1);
720
+ expect(mockSend).toHaveBeenCalledTimes(1);
721
+ expect(mockSend).toHaveBeenCalledWith(
722
+ sendPayloadExpectation(
723
+ newDevice.deviceId,
724
+ {
725
+ body: 'notification body',
726
+ title: 'notification title',
727
+ },
728
+ null
729
+ )
730
+ );
731
+ });
732
+ });
733
+ });