n8n-nodes-redactor 3.0.1

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 (61) hide show
  1. package/LICENSE +42 -0
  2. package/README.dev.md +153 -0
  3. package/README.md +443 -0
  4. package/README.npm.md +443 -0
  5. package/dist/nodes/PiiRedactor/PiiRedactor.node.d.ts +5 -0
  6. package/dist/nodes/PiiRedactor/PiiRedactor.node.js +1093 -0
  7. package/dist/nodes/PiiRedactor/__tests__/encryption.test.d.ts +1 -0
  8. package/dist/nodes/PiiRedactor/__tests__/encryption.test.js +200 -0
  9. package/dist/nodes/PiiRedactor/__tests__/engine.test.d.ts +1 -0
  10. package/dist/nodes/PiiRedactor/__tests__/engine.test.js +524 -0
  11. package/dist/nodes/PiiRedactor/__tests__/operations.test.d.ts +1 -0
  12. package/dist/nodes/PiiRedactor/__tests__/operations.test.js +316 -0
  13. package/dist/nodes/PiiRedactor/__tests__/patterns-global.test.d.ts +1 -0
  14. package/dist/nodes/PiiRedactor/__tests__/patterns-global.test.js +427 -0
  15. package/dist/nodes/PiiRedactor/__tests__/patterns.test.d.ts +1 -0
  16. package/dist/nodes/PiiRedactor/__tests__/patterns.test.js +481 -0
  17. package/dist/nodes/PiiRedactor/__tests__/phase1.test.d.ts +1 -0
  18. package/dist/nodes/PiiRedactor/__tests__/phase1.test.js +343 -0
  19. package/dist/nodes/PiiRedactor/__tests__/phase3.test.d.ts +1 -0
  20. package/dist/nodes/PiiRedactor/__tests__/phase3.test.js +275 -0
  21. package/dist/nodes/PiiRedactor/__tests__/phase4.test.d.ts +1 -0
  22. package/dist/nodes/PiiRedactor/__tests__/phase4.test.js +184 -0
  23. package/dist/nodes/PiiRedactor/__tests__/presidio.test.d.ts +1 -0
  24. package/dist/nodes/PiiRedactor/__tests__/presidio.test.js +170 -0
  25. package/dist/nodes/PiiRedactor/__tests__/security.test.d.ts +1 -0
  26. package/dist/nodes/PiiRedactor/__tests__/security.test.js +178 -0
  27. package/dist/nodes/PiiRedactor/__tests__/semantic.test.d.ts +1 -0
  28. package/dist/nodes/PiiRedactor/__tests__/semantic.test.js +319 -0
  29. package/dist/nodes/PiiRedactor/__tests__/vault.test.d.ts +1 -0
  30. package/dist/nodes/PiiRedactor/__tests__/vault.test.js +247 -0
  31. package/dist/nodes/PiiRedactor/audit.d.ts +48 -0
  32. package/dist/nodes/PiiRedactor/audit.js +192 -0
  33. package/dist/nodes/PiiRedactor/classification.d.ts +33 -0
  34. package/dist/nodes/PiiRedactor/classification.js +118 -0
  35. package/dist/nodes/PiiRedactor/context.d.ts +57 -0
  36. package/dist/nodes/PiiRedactor/context.js +260 -0
  37. package/dist/nodes/PiiRedactor/encryption.d.ts +45 -0
  38. package/dist/nodes/PiiRedactor/encryption.js +158 -0
  39. package/dist/nodes/PiiRedactor/engine.d.ts +23 -0
  40. package/dist/nodes/PiiRedactor/engine.js +888 -0
  41. package/dist/nodes/PiiRedactor/injection.d.ts +46 -0
  42. package/dist/nodes/PiiRedactor/injection.js +425 -0
  43. package/dist/nodes/PiiRedactor/names.d.ts +25 -0
  44. package/dist/nodes/PiiRedactor/names.js +188 -0
  45. package/dist/nodes/PiiRedactor/patterns.d.ts +17 -0
  46. package/dist/nodes/PiiRedactor/patterns.js +1742 -0
  47. package/dist/nodes/PiiRedactor/presidio.d.ts +77 -0
  48. package/dist/nodes/PiiRedactor/presidio.js +264 -0
  49. package/dist/nodes/PiiRedactor/profiles.d.ts +47 -0
  50. package/dist/nodes/PiiRedactor/profiles.js +139 -0
  51. package/dist/nodes/PiiRedactor/pseudonymize.d.ts +20 -0
  52. package/dist/nodes/PiiRedactor/pseudonymize.js +203 -0
  53. package/dist/nodes/PiiRedactor/redact.png +0 -0
  54. package/dist/nodes/PiiRedactor/redact.svg +3 -0
  55. package/dist/nodes/PiiRedactor/ropa.d.ts +63 -0
  56. package/dist/nodes/PiiRedactor/ropa.js +70 -0
  57. package/dist/nodes/PiiRedactor/types.d.ts +82 -0
  58. package/dist/nodes/PiiRedactor/types.js +3 -0
  59. package/dist/nodes/PiiRedactor/vault.d.ts +61 -0
  60. package/dist/nodes/PiiRedactor/vault.js +352 -0
  61. package/package.json +87 -0
@@ -0,0 +1,427 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const patterns_1 = require("../patterns");
4
+ function matchAll(patternName, input) {
5
+ const pattern = patterns_1.PII_PATTERNS.find((p) => p.name === patternName);
6
+ if (!pattern)
7
+ throw new Error(`Pattern not found: ${patternName}`);
8
+ const re = new RegExp(pattern.regex.source, pattern.regex.flags);
9
+ const matches = [];
10
+ let m;
11
+ while ((m = re.exec(input)) !== null) {
12
+ if (pattern.validate && !pattern.validate(m[0]))
13
+ continue;
14
+ matches.push(m[0]);
15
+ }
16
+ return matches;
17
+ }
18
+ // ═══════════════════════════════════════════════════════
19
+ // GLOBAL NATIONAL IDs
20
+ // ═══════════════════════════════════════════════════════
21
+ describe('US ITIN', () => {
22
+ test('matches valid ITIN', () => {
23
+ expect(matchAll('itinUS', 'ITIN: 912-70-1234')).toEqual(['912-70-1234']);
24
+ });
25
+ });
26
+ describe('Canadian SIN', () => {
27
+ test('matches valid SIN with Luhn', () => {
28
+ // 046 454 286 passes Luhn
29
+ expect(matchAll('sinCA', '046 454 286')).toEqual(['046 454 286']);
30
+ });
31
+ test('rejects invalid Luhn', () => {
32
+ expect(matchAll('sinCA', '123 456 789')).toEqual([]);
33
+ });
34
+ });
35
+ describe('UK NINO', () => {
36
+ test('matches valid NINO', () => {
37
+ expect(matchAll('ninoUK', 'NINO: AB 12 34 56 C')).toEqual(['AB 12 34 56 C']);
38
+ });
39
+ test('matches without spaces', () => {
40
+ expect(matchAll('ninoUK', 'AB123456C')).toEqual(['AB123456C']);
41
+ });
42
+ });
43
+ describe('German passport', () => {
44
+ test('matches DE passport format', () => {
45
+ expect(matchAll('passportDE', 'Pass: C01X00T47')).toEqual(['C01X00T47']);
46
+ });
47
+ });
48
+ describe('Belgian RRN', () => {
49
+ test('matches with dots', () => {
50
+ expect(matchAll('rrnBE', '85.07.15.001.33')).toEqual(['85.07.15.001.33']);
51
+ });
52
+ test('matches with dashes', () => {
53
+ expect(matchAll('rrnBE', '85-07-15-001-33')).toEqual(['85-07-15-001-33']);
54
+ });
55
+ });
56
+ describe('Swedish Personnummer', () => {
57
+ test('matches 10-digit with dash', () => {
58
+ expect(matchAll('personnummerSE', '850715-1234')).toEqual(['850715-1234']);
59
+ });
60
+ test('matches 12-digit with dash', () => {
61
+ expect(matchAll('personnummerSE', '19850715-1234')).toEqual(['19850715-1234']);
62
+ });
63
+ });
64
+ describe('Norwegian Fødselsnummer', () => {
65
+ test('matches 11-digit format', () => {
66
+ expect(matchAll('fodselsnummerNO', '15077941012')).toEqual(['15077941012']);
67
+ });
68
+ });
69
+ describe('Danish CPR', () => {
70
+ test('matches with dash', () => {
71
+ expect(matchAll('cprDK', 'CPR: 150779-4123')).toEqual(['150779-4123']);
72
+ });
73
+ });
74
+ describe('Finnish HETU', () => {
75
+ test('matches valid HETU', () => {
76
+ expect(matchAll('hetuFI', '150785-123A')).toEqual(['150785-123A']);
77
+ });
78
+ test('matches 2000s format', () => {
79
+ expect(matchAll('hetuFI', '010100A123B')).toEqual(['010100A123B']);
80
+ });
81
+ });
82
+ describe('Portuguese NIF', () => {
83
+ test('matches valid NIF with checksum', () => {
84
+ // 123456789 — let's check: 1*9+2*8+3*7+4*6+5*5+6*4+7*3+8*2 = 9+16+21+24+25+24+21+16 = 156. 156%11=2. 11-2=9. check=9. Last digit is 9. Valid!
85
+ expect(matchAll('nifPT', 'NIF: 123456789')).toEqual(['123456789']);
86
+ });
87
+ });
88
+ describe('Irish PPS', () => {
89
+ test('matches 8-char PPS', () => {
90
+ expect(matchAll('ppsIE', 'PPS: 1234567T')).toEqual(['1234567T']);
91
+ });
92
+ test('matches 9-char PPS', () => {
93
+ expect(matchAll('ppsIE', '1234567TW')).toEqual(['1234567TW']);
94
+ });
95
+ });
96
+ describe('Australian TFN', () => {
97
+ test('matches valid TFN with checksum', () => {
98
+ // 123456782: 1*1+2*4+3*3+4*7+5*5+6*8+7*6+8*9+2*10 = 1+8+9+28+25+48+42+72+20 = 253. 253%11=0. Valid!
99
+ expect(matchAll('tfnAU', '123 456 782')).toEqual(['123 456 782']);
100
+ });
101
+ });
102
+ describe('Australian Medicare', () => {
103
+ test('matches Medicare format', () => {
104
+ expect(matchAll('medicareAU', '2123 45670 1')).toEqual(['2123 45670 1']);
105
+ });
106
+ });
107
+ describe('NZ IRD', () => {
108
+ test('matches IRD format', () => {
109
+ expect(matchAll('irdNZ', '12-345-678')).toEqual(['12-345-678']);
110
+ });
111
+ });
112
+ describe('Singapore NRIC', () => {
113
+ test('matches NRIC format', () => {
114
+ expect(matchAll('nricSG', 'NRIC: S1234567D')).toEqual(['S1234567D']);
115
+ });
116
+ test('matches FIN format', () => {
117
+ expect(matchAll('nricSG', 'FIN: F1234567N')).toEqual(['F1234567N']);
118
+ });
119
+ });
120
+ describe('Indian PAN', () => {
121
+ test('matches valid PAN', () => {
122
+ expect(matchAll('panIN', 'PAN: ABCPD1234E')).toEqual(['ABCPD1234E']);
123
+ });
124
+ });
125
+ describe('Brazilian CPF', () => {
126
+ test('matches formatted CPF', () => {
127
+ expect(matchAll('cpfBR', 'CPF: 123.456.789-09')).toEqual(['123.456.789-09']);
128
+ });
129
+ test('matches unformatted CPF', () => {
130
+ expect(matchAll('cpfBR', '12345678909')).toEqual(['12345678909']);
131
+ });
132
+ });
133
+ describe('Spanish NIF (company)', () => {
134
+ test('matches NIF format', () => {
135
+ expect(matchAll('nifES', 'NIF: B12345678')).toEqual(['B12345678']);
136
+ });
137
+ });
138
+ describe('Italian Carta d\'Identità', () => {
139
+ test('matches CIE format', () => {
140
+ expect(matchAll('cartaIdIT', 'CIE: CA00000AA')).toEqual(['CA00000AA']);
141
+ });
142
+ });
143
+ describe('Driver license (contextual)', () => {
144
+ test('matches with label', () => {
145
+ const result = matchAll('driverLicenseCtx', 'Driver License: A1234567');
146
+ expect(result.length).toBeGreaterThanOrEqual(1);
147
+ });
148
+ test('matches DL abbreviation', () => {
149
+ const result = matchAll('driverLicenseCtx', 'DL# B98765432');
150
+ expect(result.length).toBeGreaterThanOrEqual(1);
151
+ });
152
+ });
153
+ // ═══════════════════════════════════════════════════════
154
+ // FINANCIAL — Extended
155
+ // ═══════════════════════════════════════════════════════
156
+ describe('US ABA routing number', () => {
157
+ test('matches valid ABA with checksum', () => {
158
+ // 021000021: 3*(0+0+0)+7*(2+0+2)+1*(1+0+1) = 0+28+2=30. 30%10=0. Valid!
159
+ expect(matchAll('abaRouting', 'Routing: 021000021')).toEqual(['021000021']);
160
+ });
161
+ });
162
+ describe('UK Sort Code', () => {
163
+ test('matches sort code', () => {
164
+ expect(matchAll('sortCodeUK', 'Sort: 20-00-00')).toEqual(['20-00-00']);
165
+ });
166
+ });
167
+ describe('American Express', () => {
168
+ test('matches Amex format', () => {
169
+ // 378282246310005 passes Luhn
170
+ expect(matchAll('amex', '3782 822463 10005')).toEqual(['3782 822463 10005']);
171
+ });
172
+ });
173
+ // ═══════════════════════════════════════════════════════
174
+ // ENTERPRISE — Infrastructure & Secrets
175
+ // ═══════════════════════════════════════════════════════
176
+ describe('Internal hostnames', () => {
177
+ test('matches .internal domain', () => {
178
+ expect(matchAll('internalHostname', 'server-web-01.internal')).toEqual(['server-web-01.internal']);
179
+ });
180
+ test('matches .corp.local domain', () => {
181
+ expect(matchAll('internalHostname', 'db-prod-03.corp.local')).toEqual(['db-prod-03.corp.local']);
182
+ });
183
+ test('matches .lan domain', () => {
184
+ expect(matchAll('internalHostname', 'nas-backup.lan')).toEqual(['nas-backup.lan']);
185
+ });
186
+ });
187
+ describe('Windows UNC paths', () => {
188
+ test('matches UNC path', () => {
189
+ const result = matchAll('uncPath', '\\\\fileserver\\shared\\docs');
190
+ expect(result.length).toBeGreaterThanOrEqual(1);
191
+ });
192
+ });
193
+ describe('LDAP Distinguished Names', () => {
194
+ test('matches LDAP DN', () => {
195
+ const result = matchAll('ldapDN', 'CN=John Smith,OU=Users,DC=corp,DC=local');
196
+ expect(result.length).toBeGreaterThanOrEqual(1);
197
+ expect(result[0]).toContain('CN=John Smith');
198
+ });
199
+ });
200
+ describe('Active Directory usernames', () => {
201
+ test('matches DOMAIN\\user', () => {
202
+ expect(matchAll('adUsername', 'Login: CORP\\jsmith')).toEqual(['CORP\\jsmith']);
203
+ });
204
+ });
205
+ describe('Private IP addresses', () => {
206
+ test('matches 10.x range', () => {
207
+ expect(matchAll('privateIp', 'Server: 10.0.1.50')).toEqual(['10.0.1.50']);
208
+ });
209
+ test('matches 172.16.x range', () => {
210
+ expect(matchAll('privateIp', 'Gateway: 172.16.0.1')).toEqual(['172.16.0.1']);
211
+ });
212
+ test('matches 192.168.x range', () => {
213
+ expect(matchAll('privateIp', 'LAN: 192.168.1.1')).toEqual(['192.168.1.1']);
214
+ });
215
+ });
216
+ describe('Database connection strings', () => {
217
+ test('matches PostgreSQL URI', () => {
218
+ const result = matchAll('dbConnectionString', 'postgresql://user:pass@localhost:5432/db');
219
+ expect(result.length).toBeGreaterThanOrEqual(1);
220
+ });
221
+ test('matches MongoDB URI', () => {
222
+ const result = matchAll('dbConnectionString', 'mongodb+srv://admin:secret@cluster.mongodb.net/mydb');
223
+ expect(result.length).toBeGreaterThanOrEqual(1);
224
+ });
225
+ test('matches JDBC URI', () => {
226
+ const result = matchAll('dbConnectionString', 'jdbc:mysql://db-server:3306/mydb');
227
+ expect(result.length).toBeGreaterThanOrEqual(1);
228
+ });
229
+ });
230
+ describe('ADO.NET connection strings', () => {
231
+ test('matches Server/Password pattern', () => {
232
+ const result = matchAll('connStringKV', 'Server=myserver;Database=mydb;Password=secret123');
233
+ expect(result.length).toBeGreaterThanOrEqual(1);
234
+ });
235
+ });
236
+ describe('AWS Access Key', () => {
237
+ test('matches AKIA format', () => {
238
+ expect(matchAll('awsAccessKey', 'AKIAIOSFODNN7EXAMPLE')).toEqual(['AKIAIOSFODNN7EXAMPLE']);
239
+ });
240
+ });
241
+ describe('GCP API Key', () => {
242
+ test('matches AIza format', () => {
243
+ const result = matchAll('gcpApiKey', 'AIzaSyC1234567890abcdefghijklmnopqrstuv');
244
+ expect(result.length).toBeGreaterThanOrEqual(1);
245
+ });
246
+ });
247
+ describe('Stripe Key', () => {
248
+ test('matches secret key', () => {
249
+ const result = matchAll('stripeKey', 'sk_live_abc123def456ghi789jkl012');
250
+ expect(result.length).toBeGreaterThanOrEqual(1);
251
+ });
252
+ test('matches test key', () => {
253
+ const result = matchAll('stripeKey', 'sk_test_abc123def456ghi789jkl012');
254
+ expect(result.length).toBeGreaterThanOrEqual(1);
255
+ });
256
+ });
257
+ describe('OpenAI Key', () => {
258
+ test('matches sk- format', () => {
259
+ const result = matchAll('openaiKey', 'sk-abc123def456ghi789jkl012mno345');
260
+ expect(result.length).toBeGreaterThanOrEqual(1);
261
+ });
262
+ });
263
+ describe('GitHub Token', () => {
264
+ test('matches ghp_ format', () => {
265
+ const result = matchAll('githubToken', 'ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef1234');
266
+ expect(result.length).toBeGreaterThanOrEqual(1);
267
+ });
268
+ });
269
+ describe('Slack Token', () => {
270
+ test('matches xoxb- format', () => {
271
+ const result = matchAll('slackToken', 'xoxb-123456789012-abcdefghijklmnop');
272
+ expect(result.length).toBeGreaterThanOrEqual(1);
273
+ });
274
+ });
275
+ describe('Bearer Token', () => {
276
+ test('matches Bearer prefix', () => {
277
+ const result = matchAll('bearerToken', 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.xxx.yyy');
278
+ expect(result.length).toBeGreaterThanOrEqual(1);
279
+ });
280
+ });
281
+ describe('JWT Token', () => {
282
+ test('matches JWT format', () => {
283
+ const result = matchAll('jwtToken', 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N');
284
+ expect(result.length).toBeGreaterThanOrEqual(1);
285
+ });
286
+ });
287
+ describe('PEM Private Key', () => {
288
+ test('matches RSA private key header', () => {
289
+ const result = matchAll('pemPrivateKey', '-----BEGIN RSA PRIVATE KEY-----');
290
+ expect(result.length).toBeGreaterThanOrEqual(1);
291
+ });
292
+ test('matches generic private key header', () => {
293
+ const result = matchAll('pemPrivateKey', '-----BEGIN PRIVATE KEY-----');
294
+ expect(result.length).toBeGreaterThanOrEqual(1);
295
+ });
296
+ });
297
+ describe('SSH Public Key', () => {
298
+ test('matches ssh-rsa key', () => {
299
+ const result = matchAll('sshPublicKey', 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCxyz123456789');
300
+ expect(result.length).toBeGreaterThanOrEqual(1);
301
+ });
302
+ });
303
+ describe('Generic secrets', () => {
304
+ test('matches password= pattern', () => {
305
+ const result = matchAll('genericSecret', 'password=MyS3cretP@ss');
306
+ expect(result.length).toBeGreaterThanOrEqual(1);
307
+ });
308
+ test('matches api_key= pattern', () => {
309
+ const result = matchAll('genericSecret', 'api_key="abcdef123456789"');
310
+ expect(result.length).toBeGreaterThanOrEqual(1);
311
+ });
312
+ test('matches access_token: pattern', () => {
313
+ const result = matchAll('genericSecret', 'access_token: sk_live_abcdefghijklmnop');
314
+ expect(result.length).toBeGreaterThanOrEqual(1);
315
+ });
316
+ });
317
+ describe('Slack Webhook', () => {
318
+ test('matches webhook URL', () => {
319
+ const result = matchAll('slackWebhook', 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX');
320
+ expect(result.length).toBeGreaterThanOrEqual(1);
321
+ });
322
+ });
323
+ // ═══════════════════════════════════════════════════════
324
+ // MEDICAL — Extended
325
+ // ═══════════════════════════════════════════════════════
326
+ describe('DEA Number', () => {
327
+ test('matches DEA format', () => {
328
+ expect(matchAll('deaNumber', 'DEA: AB1234563')).toEqual(['AB1234563']);
329
+ });
330
+ });
331
+ describe('NPI Number', () => {
332
+ test('matches contextual NPI', () => {
333
+ const result = matchAll('npiNumber', 'NPI: 1234567893');
334
+ expect(result.length).toBeGreaterThanOrEqual(1);
335
+ });
336
+ });
337
+ describe('Prescription number', () => {
338
+ test('matches Rx format', () => {
339
+ const result = matchAll('rxNumber', 'Rx# 123456789');
340
+ expect(result.length).toBeGreaterThanOrEqual(1);
341
+ });
342
+ });
343
+ // ═══════════════════════════════════════════════════════
344
+ // VEHICLE
345
+ // ═══════════════════════════════════════════════════════
346
+ describe('VIN', () => {
347
+ test('matches valid VIN', () => {
348
+ expect(matchAll('vin', '1HGBH41JXMN109186')).toEqual(['1HGBH41JXMN109186']);
349
+ });
350
+ test('does NOT match with I, O, Q', () => {
351
+ // VIN excludes I, O, Q
352
+ expect(matchAll('vin', '1HGBHI1JXMN109186')).toEqual([]);
353
+ });
354
+ });
355
+ describe('German license plate', () => {
356
+ test('matches standard format', () => {
357
+ expect(matchAll('licensePlateDE', 'M AB 1234')).toEqual(['M AB 1234']);
358
+ });
359
+ });
360
+ describe('UK license plate', () => {
361
+ test('matches standard format', () => {
362
+ expect(matchAll('licensePlateUK', 'AB12 CDE')).toEqual(['AB12 CDE']);
363
+ });
364
+ });
365
+ describe('French license plate', () => {
366
+ test('matches standard format', () => {
367
+ expect(matchAll('licensePlateFR', 'AB-123-CD')).toEqual(['AB-123-CD']);
368
+ });
369
+ });
370
+ // ═══════════════════════════════════════════════════════
371
+ // BIOMETRIC / DIGITAL
372
+ // ═══════════════════════════════════════════════════════
373
+ describe('UUID', () => {
374
+ test('matches standard UUID', () => {
375
+ expect(matchAll('uuid', '550e8400-e29b-41d4-a716-446655440000')).toEqual([
376
+ '550e8400-e29b-41d4-a716-446655440000',
377
+ ]);
378
+ });
379
+ });
380
+ describe('IMEI', () => {
381
+ test('matches formatted IMEI', () => {
382
+ expect(matchAll('imei', '49-015420-323751-8')).toEqual(['49-015420-323751-8']);
383
+ });
384
+ });
385
+ describe('ICCID', () => {
386
+ test('matches SIM card number', () => {
387
+ expect(matchAll('iccid', '8901260222300457687')).toEqual(['8901260222300457687']);
388
+ });
389
+ });
390
+ // ═══════════════════════════════════════════════════════
391
+ // GLOBAL PHONE NUMBERS
392
+ // ═══════════════════════════════════════════════════════
393
+ describe('Spanish phone', () => {
394
+ test('matches ES mobile', () => {
395
+ expect(matchAll('phoneES', '612.345.678')).toEqual(['612.345.678']);
396
+ });
397
+ });
398
+ describe('Italian phone', () => {
399
+ test('matches IT phone', () => {
400
+ const result = matchAll('phoneIT', '+39 06 12345678');
401
+ expect(result.length).toBeGreaterThanOrEqual(1);
402
+ });
403
+ });
404
+ describe('Australian phone', () => {
405
+ test('matches AU mobile', () => {
406
+ expect(matchAll('phoneAU', '04 1234 5678')).toEqual(['04 1234 5678']);
407
+ });
408
+ });
409
+ describe('Indian phone', () => {
410
+ test('matches IN mobile', () => {
411
+ expect(matchAll('phoneIN', '98765-43210')).toEqual(['98765-43210']);
412
+ });
413
+ });
414
+ describe('Fax (contextual)', () => {
415
+ test('matches fax with label', () => {
416
+ const result = matchAll('faxCtx', 'Fax: +1-555-123-4567');
417
+ expect(result.length).toBeGreaterThanOrEqual(1);
418
+ });
419
+ });
420
+ // ═══════════════════════════════════════════════════════
421
+ // PATTERN COUNT VERIFICATION
422
+ // ═══════════════════════════════════════════════════════
423
+ describe('Total pattern count', () => {
424
+ test('has 90+ patterns for enterprise-grade coverage', () => {
425
+ expect(patterns_1.PII_PATTERNS.length).toBeGreaterThanOrEqual(90);
426
+ });
427
+ });