@tantainnovative/ndpr-toolkit 1.0.1 → 1.0.3

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 (212) hide show
  1. package/.claude/settings.local.json +20 -0
  2. package/.eslintrc.json +10 -0
  3. package/.github/workflows/ci.yml +36 -0
  4. package/.github/workflows/nextjs.yml +104 -0
  5. package/.husky/commit-msg +4 -0
  6. package/.husky/pre-commit +4 -0
  7. package/.lintstagedrc.js +4 -0
  8. package/.nvmrc +1 -0
  9. package/.versionrc +17 -0
  10. package/CHANGELOG.md +16 -0
  11. package/CLAUDE.md +90 -0
  12. package/CNAME +1 -0
  13. package/CONTRIBUTING.md +87 -0
  14. package/README.md +84 -431
  15. package/RELEASE-NOTES-v1.0.0.md +140 -0
  16. package/RELEASE-NOTES-v1.0.1.md +69 -0
  17. package/SECURITY.md +21 -0
  18. package/commitlint.config.js +36 -0
  19. package/components.json +21 -0
  20. package/eslint.config.mjs +16 -0
  21. package/jest.config.js +31 -0
  22. package/jest.setup.js +15 -0
  23. package/next.config.js +15 -0
  24. package/next.config.ts +62 -0
  25. package/package.json +70 -52
  26. package/packages/ndpr-toolkit/README.md +467 -0
  27. package/packages/ndpr-toolkit/jest.config.js +23 -0
  28. package/packages/ndpr-toolkit/package-lock.json +8197 -0
  29. package/packages/ndpr-toolkit/package.json +71 -0
  30. package/packages/ndpr-toolkit/rollup.config.js +34 -0
  31. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentBanner.test.tsx +119 -0
  32. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentManager.test.tsx +122 -0
  33. package/packages/ndpr-toolkit/src/__tests__/components/consent/ConsentStorage.test.tsx +270 -0
  34. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRDashboard.test.tsx +199 -0
  35. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRRequestForm.test.tsx +224 -0
  36. package/packages/ndpr-toolkit/src/__tests__/components/dsr/DSRTracker.test.tsx +104 -0
  37. package/packages/ndpr-toolkit/src/__tests__/hooks/useConsent.test.tsx +161 -0
  38. package/packages/ndpr-toolkit/src/__tests__/hooks/useDSR.test.tsx +330 -0
  39. package/packages/ndpr-toolkit/src/__tests__/utils/breach.test.ts +149 -0
  40. package/packages/ndpr-toolkit/src/__tests__/utils/consent.test.ts +88 -0
  41. package/packages/ndpr-toolkit/src/__tests__/utils/dpia.test.ts +160 -0
  42. package/packages/ndpr-toolkit/src/__tests__/utils/dsr.test.ts +110 -0
  43. package/packages/ndpr-toolkit/src/__tests__/utils/privacy.test.ts +97 -0
  44. package/packages/ndpr-toolkit/src/components/breach/BreachNotificationManager.tsx +701 -0
  45. package/packages/ndpr-toolkit/src/components/breach/BreachReportForm.tsx +631 -0
  46. package/packages/ndpr-toolkit/src/components/breach/BreachRiskAssessment.tsx +569 -0
  47. package/packages/ndpr-toolkit/src/components/breach/RegulatoryReportGenerator.tsx +496 -0
  48. package/packages/ndpr-toolkit/src/components/consent/ConsentBanner.tsx +270 -0
  49. package/packages/ndpr-toolkit/src/components/consent/ConsentManager.tsx +217 -0
  50. package/packages/ndpr-toolkit/src/components/consent/ConsentStorage.tsx +206 -0
  51. package/packages/ndpr-toolkit/src/components/dpia/DPIAQuestionnaire.tsx +342 -0
  52. package/packages/ndpr-toolkit/src/components/dpia/DPIAReport.tsx +373 -0
  53. package/packages/ndpr-toolkit/src/components/dpia/StepIndicator.tsx +174 -0
  54. package/packages/ndpr-toolkit/src/components/dsr/DSRDashboard.tsx +717 -0
  55. package/packages/ndpr-toolkit/src/components/dsr/DSRRequestForm.tsx +476 -0
  56. package/packages/ndpr-toolkit/src/components/dsr/DSRTracker.tsx +620 -0
  57. package/packages/ndpr-toolkit/src/components/policy/PolicyExporter.tsx +541 -0
  58. package/packages/ndpr-toolkit/src/components/policy/PolicyGenerator.tsx +454 -0
  59. package/packages/ndpr-toolkit/src/components/policy/PolicyPreview.tsx +333 -0
  60. package/packages/ndpr-toolkit/src/hooks/useBreach.ts +409 -0
  61. package/packages/ndpr-toolkit/src/hooks/useConsent.ts +263 -0
  62. package/packages/ndpr-toolkit/src/hooks/useDPIA.ts +457 -0
  63. package/packages/ndpr-toolkit/src/hooks/useDSR.ts +236 -0
  64. package/packages/ndpr-toolkit/src/hooks/usePrivacyPolicy.ts +428 -0
  65. package/{dist/index.d.ts → packages/ndpr-toolkit/src/index.ts} +14 -1
  66. package/packages/ndpr-toolkit/src/setupTests.ts +5 -0
  67. package/packages/ndpr-toolkit/src/types/breach.ts +283 -0
  68. package/packages/ndpr-toolkit/src/types/consent.ts +111 -0
  69. package/packages/ndpr-toolkit/src/types/dpia.ts +236 -0
  70. package/packages/ndpr-toolkit/src/types/dsr.ts +192 -0
  71. package/packages/ndpr-toolkit/src/types/index.ts +42 -0
  72. package/packages/ndpr-toolkit/src/types/privacy.ts +246 -0
  73. package/packages/ndpr-toolkit/src/utils/breach.ts +122 -0
  74. package/packages/ndpr-toolkit/src/utils/consent.ts +51 -0
  75. package/packages/ndpr-toolkit/src/utils/dpia.ts +104 -0
  76. package/packages/ndpr-toolkit/src/utils/dsr.ts +77 -0
  77. package/packages/ndpr-toolkit/src/utils/privacy.ts +100 -0
  78. package/packages/ndpr-toolkit/tsconfig.json +23 -0
  79. package/postcss.config.mjs +5 -0
  80. package/public/NDPR TOOLKIT.svg +1 -0
  81. package/public/favicon/android-chrome-192x192.png +0 -0
  82. package/public/favicon/android-chrome-512x512.png +0 -0
  83. package/public/favicon/apple-touch-icon.png +0 -0
  84. package/public/favicon/favicon-16x16.png +0 -0
  85. package/public/favicon/favicon-32x32.png +0 -0
  86. package/public/favicon/site.webmanifest +1 -0
  87. package/public/file.svg +1 -0
  88. package/public/globe.svg +1 -0
  89. package/public/ndpr-toolkit-logo.svg +108 -0
  90. package/public/next.svg +1 -0
  91. package/public/vercel.svg +1 -0
  92. package/public/window.svg +1 -0
  93. package/src/__tests__/example.test.ts +13 -0
  94. package/src/__tests__/requestService.test.ts +57 -0
  95. package/src/app/accessibility.css +70 -0
  96. package/src/app/docs/components/DocLayout.tsx +267 -0
  97. package/src/app/docs/components/breach-notification/page.tsx +797 -0
  98. package/src/app/docs/components/consent-management/page.tsx +576 -0
  99. package/src/app/docs/components/data-subject-rights/page.tsx +511 -0
  100. package/src/app/docs/components/dpia-questionnaire/layout.tsx +15 -0
  101. package/src/app/docs/components/dpia-questionnaire/metadata.ts +31 -0
  102. package/src/app/docs/components/dpia-questionnaire/page.tsx +666 -0
  103. package/src/app/docs/components/hooks/page.tsx +305 -0
  104. package/src/app/docs/components/page.tsx +84 -0
  105. package/src/app/docs/components/privacy-policy-generator/page.tsx +634 -0
  106. package/src/app/docs/guides/breach-notification-process/components/BestPractices.tsx +123 -0
  107. package/src/app/docs/guides/breach-notification-process/components/ImplementationSteps.tsx +328 -0
  108. package/src/app/docs/guides/breach-notification-process/components/Introduction.tsx +28 -0
  109. package/src/app/docs/guides/breach-notification-process/components/NotificationTimeline.tsx +91 -0
  110. package/src/app/docs/guides/breach-notification-process/components/Resources.tsx +118 -0
  111. package/src/app/docs/guides/breach-notification-process/page.tsx +39 -0
  112. package/src/app/docs/guides/conducting-dpia/page.tsx +593 -0
  113. package/src/app/docs/guides/data-subject-requests/page.tsx +666 -0
  114. package/src/app/docs/guides/managing-consent/page.tsx +738 -0
  115. package/src/app/docs/guides/ndpr-compliance-checklist/components/ComplianceChecklist.tsx +296 -0
  116. package/src/app/docs/guides/ndpr-compliance-checklist/components/ImplementationTools.tsx +145 -0
  117. package/src/app/docs/guides/ndpr-compliance-checklist/components/Introduction.tsx +33 -0
  118. package/src/app/docs/guides/ndpr-compliance-checklist/components/KeyRequirements.tsx +99 -0
  119. package/src/app/docs/guides/ndpr-compliance-checklist/components/Resources.tsx +159 -0
  120. package/src/app/docs/guides/ndpr-compliance-checklist/page.tsx +38 -0
  121. package/src/app/docs/guides/page.tsx +67 -0
  122. package/src/app/docs/layout.tsx +15 -0
  123. package/src/app/docs/metadata.ts +31 -0
  124. package/src/app/docs/page.tsx +572 -0
  125. package/src/app/favicon.ico +0 -0
  126. package/src/app/globals.css +123 -0
  127. package/src/app/layout.tsx +37 -0
  128. package/src/app/ndpr-demos/breach/page.tsx +354 -0
  129. package/src/app/ndpr-demos/consent/page.tsx +366 -0
  130. package/src/app/ndpr-demos/dpia/page.tsx +495 -0
  131. package/src/app/ndpr-demos/dsr/page.tsx +280 -0
  132. package/src/app/ndpr-demos/page.tsx +73 -0
  133. package/src/app/ndpr-demos/policy/page.tsx +771 -0
  134. package/src/app/page.tsx +452 -0
  135. package/src/components/ErrorBoundary.tsx +90 -0
  136. package/src/components/breach-notification/BreachNotificationForm.tsx +479 -0
  137. package/src/components/consent/ConsentBanner.tsx +159 -0
  138. package/src/components/data-subject-rights/DataSubjectRequestForm.tsx +419 -0
  139. package/src/components/docs/DocLayout.tsx +289 -0
  140. package/src/components/docs/index.ts +2 -0
  141. package/src/components/dpia/DPIAQuestionnaire.tsx +483 -0
  142. package/src/components/privacy-policy/PolicyGenerator.tsx +1062 -0
  143. package/src/components/privacy-policy/data.ts +98 -0
  144. package/src/components/privacy-policy/shared/CheckboxField.tsx +38 -0
  145. package/src/components/privacy-policy/shared/CheckboxGroup.tsx +85 -0
  146. package/src/components/privacy-policy/shared/FormField.tsx +79 -0
  147. package/src/components/privacy-policy/shared/StepIndicator.tsx +86 -0
  148. package/src/components/privacy-policy/steps/CustomSectionsStep.tsx +335 -0
  149. package/src/components/privacy-policy/steps/DataCollectionStep.tsx +231 -0
  150. package/src/components/privacy-policy/steps/DataSharingStep.tsx +418 -0
  151. package/src/components/privacy-policy/steps/OrganizationInfoStep.tsx +202 -0
  152. package/src/components/privacy-policy/steps/PolicyPreviewStep.tsx +172 -0
  153. package/src/components/ui/Badge.tsx +46 -0
  154. package/src/components/ui/Button.tsx +59 -0
  155. package/src/components/ui/Card.tsx +92 -0
  156. package/src/components/ui/Checkbox.tsx +57 -0
  157. package/src/components/ui/FormField.tsx +50 -0
  158. package/src/components/ui/Input.tsx +38 -0
  159. package/src/components/ui/Loading.tsx +201 -0
  160. package/src/components/ui/Select.tsx +42 -0
  161. package/src/components/ui/TextArea.tsx +38 -0
  162. package/src/components/ui/label.tsx +24 -0
  163. package/src/components/ui/switch.tsx +31 -0
  164. package/src/components/ui/tabs.tsx +66 -0
  165. package/src/hooks/useConsent.ts +64 -0
  166. package/src/hooks/useLoadingState.ts +85 -0
  167. package/src/lib/consentService.ts +137 -0
  168. package/src/lib/dpiaQuestions.ts +148 -0
  169. package/src/lib/requestService.ts +75 -0
  170. package/src/lib/sanitize.ts +108 -0
  171. package/src/lib/storage.ts +222 -0
  172. package/src/lib/utils.ts +6 -0
  173. package/src/types/html-to-docx.d.ts +30 -0
  174. package/src/types/index.ts +72 -0
  175. package/tailwind.config.ts +65 -0
  176. package/tsconfig.json +41 -0
  177. package/dist/components/breach/BreachNotificationManager.d.ts +0 -62
  178. package/dist/components/breach/BreachReportForm.d.ts +0 -66
  179. package/dist/components/breach/BreachRiskAssessment.d.ts +0 -50
  180. package/dist/components/breach/RegulatoryReportGenerator.d.ts +0 -94
  181. package/dist/components/consent/ConsentBanner.d.ts +0 -79
  182. package/dist/components/consent/ConsentManager.d.ts +0 -73
  183. package/dist/components/consent/ConsentStorage.d.ts +0 -41
  184. package/dist/components/dpia/DPIAQuestionnaire.d.ts +0 -70
  185. package/dist/components/dpia/DPIAReport.d.ts +0 -40
  186. package/dist/components/dpia/StepIndicator.d.ts +0 -64
  187. package/dist/components/dsr/DSRDashboard.d.ts +0 -58
  188. package/dist/components/dsr/DSRRequestForm.d.ts +0 -74
  189. package/dist/components/dsr/DSRTracker.d.ts +0 -56
  190. package/dist/components/policy/PolicyExporter.d.ts +0 -65
  191. package/dist/components/policy/PolicyGenerator.d.ts +0 -54
  192. package/dist/components/policy/PolicyPreview.d.ts +0 -71
  193. package/dist/hooks/useBreach.d.ts +0 -97
  194. package/dist/hooks/useConsent.d.ts +0 -63
  195. package/dist/hooks/useDPIA.d.ts +0 -92
  196. package/dist/hooks/useDSR.d.ts +0 -72
  197. package/dist/hooks/usePrivacyPolicy.d.ts +0 -87
  198. package/dist/index.esm.js +0 -2
  199. package/dist/index.esm.js.map +0 -1
  200. package/dist/index.js +0 -2
  201. package/dist/index.js.map +0 -1
  202. package/dist/setupTests.d.ts +0 -2
  203. package/dist/types/breach.d.ts +0 -239
  204. package/dist/types/consent.d.ts +0 -95
  205. package/dist/types/dpia.d.ts +0 -196
  206. package/dist/types/dsr.d.ts +0 -162
  207. package/dist/types/privacy.d.ts +0 -204
  208. package/dist/utils/breach.d.ts +0 -14
  209. package/dist/utils/consent.d.ts +0 -10
  210. package/dist/utils/dpia.d.ts +0 -12
  211. package/dist/utils/dsr.d.ts +0 -11
  212. package/dist/utils/privacy.d.ts +0 -12
@@ -0,0 +1,330 @@
1
+ import { renderHook, act } from '@testing-library/react';
2
+ import { useDSR } from '../../hooks/useDSR';
3
+ import React from 'react';
4
+ import { DSRRequest, RequestType } from '../../types/dsr';
5
+
6
+ // Mock localStorage
7
+ const mockLocalStorage = (() => {
8
+ let store: Record<string, string> = {};
9
+ return {
10
+ getItem: jest.fn((key: string) => store[key] || null),
11
+ setItem: jest.fn((key: string, value: string) => {
12
+ store[key] = value.toString();
13
+ }),
14
+ removeItem: jest.fn((key: string) => {
15
+ delete store[key];
16
+ }),
17
+ clear: jest.fn(() => {
18
+ store = {};
19
+ }),
20
+ };
21
+ })();
22
+
23
+ Object.defineProperty(window, 'localStorage', {
24
+ value: mockLocalStorage,
25
+ });
26
+
27
+ describe('useDSR', () => {
28
+ beforeEach(() => {
29
+ mockLocalStorage.clear();
30
+ jest.clearAllMocks();
31
+ });
32
+
33
+ const mockRequestTypes: RequestType[] = [
34
+ {
35
+ id: 'access',
36
+ name: 'Access Request',
37
+ description: 'Request to access your personal data',
38
+ estimatedCompletionTime: 30,
39
+ requiresAdditionalInfo: false
40
+ },
41
+ {
42
+ id: 'erasure',
43
+ name: 'Erasure Request',
44
+ description: 'Request to delete your personal data',
45
+ estimatedCompletionTime: 45,
46
+ requiresAdditionalInfo: false
47
+ },
48
+ {
49
+ id: 'rectification',
50
+ name: 'Rectification Request',
51
+ description: 'Request to correct your personal data',
52
+ estimatedCompletionTime: 15,
53
+ requiresAdditionalInfo: true
54
+ }
55
+ ];
56
+
57
+ it('should initialize with empty requests array', () => {
58
+ const { result } = renderHook(() => useDSR({
59
+ requestTypes: mockRequestTypes,
60
+ storageKey: 'test-dsr',
61
+ useLocalStorage: true
62
+ }));
63
+
64
+ expect(result.current.requests).toEqual([]);
65
+ });
66
+
67
+ it('should submit a new DSR request', () => {
68
+ const { result } = renderHook(() => useDSR({
69
+ requestTypes: mockRequestTypes,
70
+ storageKey: 'test-dsr',
71
+ useLocalStorage: true
72
+ }));
73
+
74
+ act(() => {
75
+ result.current.submitRequest({
76
+ type: 'access',
77
+ subject: {
78
+ name: 'John Doe',
79
+ email: 'john@example.com',
80
+ },
81
+ createdAt: Date.now(),
82
+ description: 'I want to access my data',
83
+ });
84
+ });
85
+
86
+ expect(result.current.requests.length).toBe(1);
87
+ expect(result.current.requests[0].type).toBe('access');
88
+ expect(result.current.requests[0].status).toBe('pending');
89
+ expect(result.current.requests[0].subject.name).toBe('John Doe');
90
+ expect(result.current.requests[0].id).toBeDefined();
91
+ expect(result.current.requests[0].createdAt).toBeDefined();
92
+ expect(result.current.requests[0].updatedAt).toBeDefined();
93
+ });
94
+
95
+ it('should get a request by ID', () => {
96
+ const { result } = renderHook(() => useDSR({
97
+ requestTypes: mockRequestTypes,
98
+ storageKey: 'test-dsr',
99
+ useLocalStorage: true
100
+ }));
101
+
102
+ // Initialize requestId with a default value
103
+ let requestId: string = '';
104
+
105
+ act(() => {
106
+ const request = result.current.submitRequest({
107
+ type: 'access',
108
+ subject: {
109
+ name: 'John Doe',
110
+ email: 'john@example.com',
111
+ },
112
+ createdAt: Date.now(),
113
+ });
114
+ requestId = request.id;
115
+ });
116
+
117
+ const retrievedRequest = result.current.getRequest(requestId);
118
+ expect(retrievedRequest).toBeDefined();
119
+ expect(retrievedRequest?.id).toBe(requestId);
120
+ });
121
+
122
+ it('should update a request', () => {
123
+ const { result } = renderHook(() => useDSR({
124
+ requestTypes: mockRequestTypes,
125
+ storageKey: 'test-dsr',
126
+ useLocalStorage: true
127
+ }));
128
+
129
+ let requestId = '';
130
+
131
+ act(() => {
132
+ const request = result.current.submitRequest({
133
+ type: 'access',
134
+ subject: {
135
+ name: 'John Doe',
136
+ email: 'john@example.com',
137
+ },
138
+ createdAt: Date.now(),
139
+ });
140
+ requestId = request.id;
141
+ });
142
+
143
+ act(() => {
144
+ result.current.updateRequest(requestId!, {
145
+ status: 'inProgress',
146
+ internalNotes: [
147
+ {
148
+ timestamp: Date.now(),
149
+ author: 'Admin',
150
+ note: 'Working on this request',
151
+ },
152
+ ],
153
+ });
154
+ });
155
+
156
+ const updatedRequest = result.current.getRequest(requestId!);
157
+ expect(updatedRequest?.status).toBe('inProgress');
158
+ expect(updatedRequest?.internalNotes?.length).toBe(1);
159
+ expect(updatedRequest?.internalNotes?.[0].author).toBe('Admin');
160
+ });
161
+
162
+ it('should clear all requests', () => {
163
+ const { result } = renderHook(() => useDSR({
164
+ requestTypes: mockRequestTypes,
165
+ storageKey: 'test-dsr',
166
+ useLocalStorage: true
167
+ }));
168
+
169
+ act(() => {
170
+ result.current.submitRequest({
171
+ type: 'access',
172
+ subject: {
173
+ name: 'John Doe',
174
+ email: 'john@example.com',
175
+ },
176
+ createdAt: Date.now(),
177
+ });
178
+ });
179
+
180
+ expect(result.current.requests.length).toBe(1);
181
+
182
+ act(() => {
183
+ result.current.clearRequests();
184
+ });
185
+
186
+ expect(result.current.requests.length).toBe(0);
187
+ });
188
+
189
+ it('should filter requests by type', () => {
190
+ const { result } = renderHook(() => useDSR({
191
+ requestTypes: mockRequestTypes,
192
+ storageKey: 'test-dsr',
193
+ useLocalStorage: true
194
+ }));
195
+
196
+ act(() => {
197
+ result.current.submitRequest({
198
+ type: 'access',
199
+ subject: {
200
+ name: 'John Doe',
201
+ email: 'john@example.com',
202
+ },
203
+ createdAt: Date.now(),
204
+ });
205
+
206
+ result.current.submitRequest({
207
+ type: 'erasure',
208
+ subject: {
209
+ name: 'Jane Smith',
210
+ email: 'jane@example.com',
211
+ },
212
+ createdAt: Date.now(),
213
+ });
214
+
215
+ result.current.submitRequest({
216
+ type: 'access',
217
+ subject: {
218
+ name: 'Bob Johnson',
219
+ email: 'bob@example.com',
220
+ },
221
+ createdAt: Date.now(),
222
+ });
223
+ });
224
+
225
+ const accessRequests = result.current.getRequestsByType('access');
226
+ expect(accessRequests.length).toBe(2);
227
+ expect(accessRequests[0].subject.name).toBe('John Doe');
228
+ expect(accessRequests[1].subject.name).toBe('Bob Johnson');
229
+
230
+ const erasureRequests = result.current.getRequestsByType('erasure');
231
+ expect(erasureRequests.length).toBe(1);
232
+ expect(erasureRequests[0].subject.name).toBe('Jane Smith');
233
+ });
234
+
235
+ it('should filter requests by status', () => {
236
+ const { result } = renderHook(() => useDSR({
237
+ requestTypes: mockRequestTypes,
238
+ storageKey: 'test-dsr',
239
+ useLocalStorage: true
240
+ }));
241
+
242
+ let requestId: string = '';
243
+
244
+ act(() => {
245
+ result.current.submitRequest({
246
+ type: 'access',
247
+ subject: {
248
+ name: 'John Doe',
249
+ email: 'john@example.com',
250
+ },
251
+ createdAt: Date.now(),
252
+ });
253
+
254
+ const request = result.current.submitRequest({
255
+ type: 'erasure',
256
+ subject: {
257
+ name: 'Jane Smith',
258
+ email: 'jane@example.com',
259
+ },
260
+ createdAt: Date.now(),
261
+ });
262
+ requestId = request.id;
263
+
264
+ result.current.submitRequest({
265
+ type: 'rectification',
266
+ subject: {
267
+ name: 'Bob Johnson',
268
+ email: 'bob@example.com',
269
+ },
270
+ createdAt: Date.now(),
271
+ });
272
+ });
273
+
274
+ act(() => {
275
+ result.current.updateRequest(requestId!, {
276
+ status: 'completed',
277
+ });
278
+ });
279
+
280
+ const pendingRequests = result.current.getRequestsByStatus('pending');
281
+ expect(pendingRequests.length).toBe(2);
282
+
283
+ const completedRequests = result.current.getRequestsByStatus('completed');
284
+ expect(completedRequests.length).toBe(1);
285
+ expect(completedRequests[0].subject.name).toBe('Jane Smith');
286
+ });
287
+
288
+ it('should format a request', () => {
289
+ const { result } = renderHook(() => useDSR({
290
+ requestTypes: mockRequestTypes,
291
+ storageKey: 'test-dsr',
292
+ useLocalStorage: true
293
+ }));
294
+
295
+ let request: DSRRequest | null = null;
296
+
297
+ act(() => {
298
+ request = result.current.submitRequest({
299
+ type: 'access',
300
+ subject: {
301
+ name: 'John Doe',
302
+ email: 'john@example.com',
303
+ },
304
+ createdAt: Date.now(),
305
+ });
306
+ });
307
+
308
+ expect(request).not.toBeNull();
309
+
310
+ if (request) {
311
+ const formattedRequest = result.current.formatRequest(request);
312
+ expect(formattedRequest).toBeDefined();
313
+ expect(formattedRequest.requestType).toBe('access');
314
+ expect(formattedRequest.dataSubject.name).toBe('John Doe');
315
+ }
316
+ });
317
+
318
+ it('should get request type by ID', () => {
319
+ const { result } = renderHook(() => useDSR({
320
+ requestTypes: mockRequestTypes,
321
+ storageKey: 'test-dsr',
322
+ useLocalStorage: true
323
+ }));
324
+
325
+ const requestType = result.current.getRequestType('access');
326
+ expect(requestType).toBeDefined();
327
+ expect(requestType?.name).toBe('Access Request');
328
+ expect(requestType?.estimatedCompletionTime).toBe(30);
329
+ });
330
+ });
@@ -0,0 +1,149 @@
1
+ import { calculateBreachSeverity } from '../../utils/breach';
2
+ import { BreachReport, RiskAssessment } from '../../types/breach';
3
+
4
+ describe('calculateBreachSeverity', () => {
5
+ const breachReport: BreachReport = {
6
+ id: 'breach-123',
7
+ title: 'Database Breach',
8
+ description: 'Unauthorized access to customer database',
9
+ category: 'unauthorized_access',
10
+ discoveredAt: 1620000000000,
11
+ occurredAt: 1619900000000,
12
+ reportedAt: 1620010000000,
13
+ reporter: {
14
+ name: 'John Smith',
15
+ email: 'john@example.com',
16
+ department: 'IT Security',
17
+ phone: '1234567890'
18
+ },
19
+ affectedSystems: ['customer-db', 'payment-system'],
20
+ dataTypes: ['personal', 'financial'],
21
+ estimatedAffectedSubjects: 1000,
22
+ status: 'contained'
23
+ };
24
+
25
+ it('should calculate high severity for sensitive data and large number of affected subjects', () => {
26
+ const assessment: RiskAssessment = {
27
+ id: 'risk-123',
28
+ breachId: 'breach-123',
29
+ assessor: {
30
+ name: 'John Doe',
31
+ role: 'DPO',
32
+ email: 'dpo@example.com'
33
+ },
34
+ assessedAt: 1620000000000,
35
+ confidentialityImpact: 4,
36
+ integrityImpact: 3,
37
+ availabilityImpact: 3,
38
+ harmLikelihood: 4,
39
+ harmSeverity: 4,
40
+ overallRiskScore: 16,
41
+ riskLevel: 'high',
42
+ risksToRightsAndFreedoms: true,
43
+ highRisksToRightsAndFreedoms: true,
44
+ justification: 'High risk due to sensitive financial data'
45
+ };
46
+
47
+ const result = calculateBreachSeverity(breachReport, assessment);
48
+
49
+ expect(result.severityLevel).toBe('high');
50
+ expect(result.notificationRequired).toBe(true);
51
+ expect(result.urgentNotificationRequired).toBe(true);
52
+ expect(result.timeframeHours).toBe(72);
53
+ expect(result.justification).toBe('High risk due to sensitive financial data');
54
+ });
55
+
56
+ it('should calculate medium severity for non-sensitive data with medium impact', () => {
57
+ const assessment: RiskAssessment = {
58
+ id: 'risk-456',
59
+ breachId: 'breach-456',
60
+ assessor: {
61
+ name: 'Jane Smith',
62
+ role: 'Security Officer',
63
+ email: 'security@example.com'
64
+ },
65
+ assessedAt: 1620100000000,
66
+ confidentialityImpact: 3,
67
+ integrityImpact: 2,
68
+ availabilityImpact: 2,
69
+ harmLikelihood: 3,
70
+ harmSeverity: 3,
71
+ overallRiskScore: 9,
72
+ riskLevel: 'medium',
73
+ risksToRightsAndFreedoms: true,
74
+ highRisksToRightsAndFreedoms: false,
75
+ justification: 'Medium risk due to personal data exposure'
76
+ };
77
+
78
+ const result = calculateBreachSeverity(breachReport, assessment);
79
+
80
+ expect(result.severityLevel).toBe('medium');
81
+ expect(result.notificationRequired).toBe(true);
82
+ expect(result.urgentNotificationRequired).toBe(false);
83
+ expect(result.timeframeHours).toBe(72);
84
+ expect(result.justification).toBe('Medium risk due to personal data exposure');
85
+ });
86
+
87
+ it('should calculate low severity for contained breach with low impact', () => {
88
+ const assessment: RiskAssessment = {
89
+ id: 'risk-789',
90
+ breachId: 'breach-789',
91
+ assessor: {
92
+ name: 'Alex Johnson',
93
+ role: 'Compliance Manager',
94
+ email: 'compliance@example.com'
95
+ },
96
+ assessedAt: 1620200000000,
97
+ confidentialityImpact: 1,
98
+ integrityImpact: 1,
99
+ availabilityImpact: 2,
100
+ harmLikelihood: 2,
101
+ harmSeverity: 1,
102
+ overallRiskScore: 2,
103
+ riskLevel: 'low',
104
+ risksToRightsAndFreedoms: false,
105
+ highRisksToRightsAndFreedoms: false,
106
+ justification: 'Low risk due to minimal data exposure'
107
+ };
108
+
109
+ const result = calculateBreachSeverity(breachReport, assessment);
110
+
111
+ expect(result.severityLevel).toBe('low');
112
+ expect(result.notificationRequired).toBe(false);
113
+ expect(result.urgentNotificationRequired).toBe(false);
114
+ expect(result.timeframeHours).toBe(72);
115
+ expect(result.justification).toBe('Low risk due to minimal data exposure');
116
+ });
117
+
118
+ it('should calculate severity based on breach report when no assessment is provided', () => {
119
+ // Create a breach report with sensitive data and large scale
120
+ const sensitiveBreachReport: BreachReport = {
121
+ ...breachReport,
122
+ dataTypes: ['personal', 'financial', 'health'],
123
+ estimatedAffectedSubjects: 5000,
124
+ status: 'ongoing'
125
+ };
126
+
127
+ const result = calculateBreachSeverity(sensitiveBreachReport);
128
+
129
+ // With 3 severity factors (sensitive data, large scale, ongoing), it should be critical
130
+ expect(result.severityLevel).toBe('critical');
131
+ expect(result.notificationRequired).toBe(true);
132
+ expect(result.urgentNotificationRequired).toBe(true);
133
+ expect(result.timeframeHours).toBe(72);
134
+ expect(result.justification).toContain('Critical risk');
135
+ });
136
+
137
+ it('should always require notification for ongoing breaches', () => {
138
+ const ongoingBreachReport: BreachReport = {
139
+ ...breachReport,
140
+ status: 'ongoing',
141
+ dataTypes: ['personal']
142
+ };
143
+
144
+ const result = calculateBreachSeverity(ongoingBreachReport);
145
+
146
+ expect(result.notificationRequired).toBe(true);
147
+ expect(result.justification).toContain('Medium risk');
148
+ });
149
+ });
@@ -0,0 +1,88 @@
1
+ import { validateConsent } from '../../utils/consent';
2
+ import { ConsentSettings } from '../../types/consent';
3
+
4
+ describe('validateConsent', () => {
5
+ it('should validate valid consent settings', () => {
6
+ const settings: ConsentSettings = {
7
+ consents: {
8
+ necessary: true,
9
+ analytics: false,
10
+ marketing: true
11
+ },
12
+ timestamp: Date.now(),
13
+ version: '1.0',
14
+ method: 'banner',
15
+ hasInteracted: true
16
+ };
17
+
18
+ const result = validateConsent(settings);
19
+ expect(result.valid).toBe(true);
20
+ expect(result.errors).toEqual([]);
21
+ });
22
+
23
+ it('should invalidate settings with missing consents', () => {
24
+ const settings: ConsentSettings = {
25
+ consents: {},
26
+ timestamp: Date.now(),
27
+ version: '1.0',
28
+ method: 'banner',
29
+ hasInteracted: true
30
+ };
31
+
32
+ const result = validateConsent(settings);
33
+ expect(result.valid).toBe(false);
34
+ expect(result.errors).toContain('Consent settings must include at least one consent option');
35
+ });
36
+
37
+ it('should invalidate settings with missing timestamp', () => {
38
+ const settings = {
39
+ consents: { necessary: true },
40
+ version: '1.0',
41
+ method: 'banner',
42
+ hasInteracted: true
43
+ } as unknown as ConsentSettings;
44
+
45
+ const result = validateConsent(settings);
46
+ expect(result.valid).toBe(false);
47
+ expect(result.errors).toContain('Consent timestamp is required');
48
+ });
49
+
50
+ it('should invalidate settings with missing version', () => {
51
+ const settings = {
52
+ consents: { necessary: true },
53
+ timestamp: Date.now(),
54
+ method: 'banner',
55
+ hasInteracted: true
56
+ } as unknown as ConsentSettings;
57
+
58
+ const result = validateConsent(settings);
59
+ expect(result.valid).toBe(false);
60
+ expect(result.errors).toContain('Consent version is required');
61
+ });
62
+
63
+ it('should invalidate settings with missing method', () => {
64
+ const settings = {
65
+ consents: { necessary: true },
66
+ timestamp: Date.now(),
67
+ version: '1.0',
68
+ hasInteracted: true
69
+ } as unknown as ConsentSettings;
70
+
71
+ const result = validateConsent(settings);
72
+ expect(result.valid).toBe(false);
73
+ expect(result.errors).toContain('Consent collection method is required');
74
+ });
75
+
76
+ it('should invalidate settings with missing hasInteracted', () => {
77
+ const settings = {
78
+ consents: { necessary: true },
79
+ timestamp: Date.now(),
80
+ version: '1.0',
81
+ method: 'banner'
82
+ } as unknown as ConsentSettings;
83
+
84
+ const result = validateConsent(settings);
85
+ expect(result.valid).toBe(false);
86
+ expect(result.errors).toContain('User interaction status is required');
87
+ });
88
+ });