repo-cloak-cli 1.2.4 ā 1.3.2
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.
- package/.github/workflows/release.yml +92 -92
- package/LICENSE +21 -21
- package/README.md +138 -118
- package/bin/repo-cloak.js +9 -9
- package/package.json +50 -50
- package/src/cli.js +86 -84
- package/src/commands/pull.js +582 -352
- package/src/commands/push.js +250 -235
- package/src/core/anonymizer.js +128 -128
- package/src/core/copier.js +139 -139
- package/src/core/crypto.js +128 -128
- package/src/core/git.js +61 -0
- package/src/core/mapper.js +235 -235
- package/src/core/scanner.js +137 -137
- package/src/index.js +8 -8
- package/src/ui/banner.js +70 -70
- package/src/ui/fileSelector.js +256 -256
- package/src/ui/prompts.js +165 -165
- package/tests/anonymizer.test.js +127 -127
- package/tests/copier.test.js +94 -94
- package/tests/crypto.test.js +106 -106
- package/tests/git.test.js +103 -0
- package/tests/mapper.test.js +166 -166
- package/tests/scanner.test.js +100 -100
- package/medium.md +0 -319
package/src/ui/prompts.js
CHANGED
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Prompts
|
|
3
|
-
* Handles all user input prompts
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import inquirer from 'inquirer';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import { existsSync } from 'fs';
|
|
9
|
-
import { resolve, isAbsolute } from 'path';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Prompt for source directory
|
|
13
|
-
*/
|
|
14
|
-
export async function promptSourceDirectory(defaultPath = process.cwd()) {
|
|
15
|
-
const { sourcePath } = await inquirer.prompt([
|
|
16
|
-
{
|
|
17
|
-
type: 'input',
|
|
18
|
-
name: 'sourcePath',
|
|
19
|
-
message: 'Source directory:',
|
|
20
|
-
default: defaultPath,
|
|
21
|
-
validate: (input) => {
|
|
22
|
-
const path = resolve(input);
|
|
23
|
-
if (!existsSync(path)) {
|
|
24
|
-
return 'Directory does not exist. Please enter a valid path.';
|
|
25
|
-
}
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
return resolve(sourcePath);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Prompt for destination directory
|
|
36
|
-
*/
|
|
37
|
-
export async function promptDestinationDirectory() {
|
|
38
|
-
const { destPath } = await inquirer.prompt([
|
|
39
|
-
{
|
|
40
|
-
type: 'input',
|
|
41
|
-
name: 'destPath',
|
|
42
|
-
message: 'Destination directory (will be created if not exists):',
|
|
43
|
-
validate: (input) => {
|
|
44
|
-
if (!input.trim()) {
|
|
45
|
-
return 'Please enter a destination path.';
|
|
46
|
-
}
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
return resolve(destPath);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Prompt for keyword replacements
|
|
57
|
-
*/
|
|
58
|
-
export async function promptKeywordReplacements() {
|
|
59
|
-
const replacements = [];
|
|
60
|
-
|
|
61
|
-
console.log(chalk.cyan('\nš Keyword Replacements'));
|
|
62
|
-
console.log(chalk.dim(' Replace sensitive information with anonymous values.\n'));
|
|
63
|
-
|
|
64
|
-
let addMore = true;
|
|
65
|
-
|
|
66
|
-
while (addMore) {
|
|
67
|
-
const { original, replacement } = await inquirer.prompt([
|
|
68
|
-
{
|
|
69
|
-
type: 'input',
|
|
70
|
-
name: 'original',
|
|
71
|
-
message: 'Text to find (leave empty to finish):',
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
type: 'input',
|
|
75
|
-
name: 'replacement',
|
|
76
|
-
message: 'Replace with:',
|
|
77
|
-
when: (answers) => answers.original.trim() !== '',
|
|
78
|
-
validate: (input) => {
|
|
79
|
-
if (!input.trim()) {
|
|
80
|
-
return 'Please enter a replacement value.';
|
|
81
|
-
}
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
]);
|
|
86
|
-
|
|
87
|
-
if (original.trim() === '') {
|
|
88
|
-
addMore = false;
|
|
89
|
-
} else {
|
|
90
|
-
replacements.push({
|
|
91
|
-
original: original.trim(),
|
|
92
|
-
replacement: replacement.trim()
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
console.log(chalk.green(` ā "${original}" ā "${replacement}"`));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (replacements.length === 0) {
|
|
100
|
-
console.log(chalk.yellow(' No replacements configured. Files will be copied as-is.'));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return replacements;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Confirm before proceeding
|
|
108
|
-
*/
|
|
109
|
-
export async function confirmAction(message) {
|
|
110
|
-
const { confirmed } = await inquirer.prompt([
|
|
111
|
-
{
|
|
112
|
-
type: 'confirm',
|
|
113
|
-
name: 'confirmed',
|
|
114
|
-
message,
|
|
115
|
-
default: true
|
|
116
|
-
}
|
|
117
|
-
]);
|
|
118
|
-
|
|
119
|
-
return confirmed;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Show summary of selections
|
|
124
|
-
*/
|
|
125
|
-
export async function showSummaryAndConfirm(fileCount, destination, replacements) {
|
|
126
|
-
console.log(chalk.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
127
|
-
console.log(chalk.white.bold('š Summary\n'));
|
|
128
|
-
console.log(chalk.white(` š Files to copy: ${chalk.yellow.bold(fileCount)}`));
|
|
129
|
-
console.log(chalk.white(` š Destination: ${chalk.yellow.bold(destination)}`));
|
|
130
|
-
|
|
131
|
-
if (replacements.length > 0) {
|
|
132
|
-
console.log(chalk.white(` š Replacements: ${chalk.yellow.bold(replacements.length)}`));
|
|
133
|
-
replacements.forEach(r => {
|
|
134
|
-
console.log(chalk.dim(` "${r.original}" ā "${r.replacement}"`));
|
|
135
|
-
});
|
|
136
|
-
} else {
|
|
137
|
-
console.log(chalk.white(` š Replacements: ${chalk.dim('None')}`));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
console.log(chalk.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
141
|
-
|
|
142
|
-
return confirmAction('Proceed with extraction?');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Prompt for selecting a backup folder (for push command)
|
|
147
|
-
*/
|
|
148
|
-
export async function promptBackupFolder() {
|
|
149
|
-
const { folderPath } = await inquirer.prompt([
|
|
150
|
-
{
|
|
151
|
-
type: 'input',
|
|
152
|
-
name: 'folderPath',
|
|
153
|
-
message: 'Path to cloaked backup folder:',
|
|
154
|
-
validate: (input) => {
|
|
155
|
-
const path = resolve(input);
|
|
156
|
-
if (!existsSync(path)) {
|
|
157
|
-
return 'Folder does not exist. Please enter a valid path.';
|
|
158
|
-
}
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
]);
|
|
163
|
-
|
|
164
|
-
return resolve(folderPath);
|
|
165
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* User Prompts
|
|
3
|
+
* Handles all user input prompts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { resolve, isAbsolute } from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Prompt for source directory
|
|
13
|
+
*/
|
|
14
|
+
export async function promptSourceDirectory(defaultPath = process.cwd()) {
|
|
15
|
+
const { sourcePath } = await inquirer.prompt([
|
|
16
|
+
{
|
|
17
|
+
type: 'input',
|
|
18
|
+
name: 'sourcePath',
|
|
19
|
+
message: 'Source directory:',
|
|
20
|
+
default: defaultPath,
|
|
21
|
+
validate: (input) => {
|
|
22
|
+
const path = resolve(input);
|
|
23
|
+
if (!existsSync(path)) {
|
|
24
|
+
return 'Directory does not exist. Please enter a valid path.';
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
return resolve(sourcePath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Prompt for destination directory
|
|
36
|
+
*/
|
|
37
|
+
export async function promptDestinationDirectory() {
|
|
38
|
+
const { destPath } = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: 'input',
|
|
41
|
+
name: 'destPath',
|
|
42
|
+
message: 'Destination directory (will be created if not exists):',
|
|
43
|
+
validate: (input) => {
|
|
44
|
+
if (!input.trim()) {
|
|
45
|
+
return 'Please enter a destination path.';
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
return resolve(destPath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Prompt for keyword replacements
|
|
57
|
+
*/
|
|
58
|
+
export async function promptKeywordReplacements() {
|
|
59
|
+
const replacements = [];
|
|
60
|
+
|
|
61
|
+
console.log(chalk.cyan('\nš Keyword Replacements'));
|
|
62
|
+
console.log(chalk.dim(' Replace sensitive information with anonymous values.\n'));
|
|
63
|
+
|
|
64
|
+
let addMore = true;
|
|
65
|
+
|
|
66
|
+
while (addMore) {
|
|
67
|
+
const { original, replacement } = await inquirer.prompt([
|
|
68
|
+
{
|
|
69
|
+
type: 'input',
|
|
70
|
+
name: 'original',
|
|
71
|
+
message: 'Text to find (leave empty to finish):',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'input',
|
|
75
|
+
name: 'replacement',
|
|
76
|
+
message: 'Replace with:',
|
|
77
|
+
when: (answers) => answers.original.trim() !== '',
|
|
78
|
+
validate: (input) => {
|
|
79
|
+
if (!input.trim()) {
|
|
80
|
+
return 'Please enter a replacement value.';
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
if (original.trim() === '') {
|
|
88
|
+
addMore = false;
|
|
89
|
+
} else {
|
|
90
|
+
replacements.push({
|
|
91
|
+
original: original.trim(),
|
|
92
|
+
replacement: replacement.trim()
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
console.log(chalk.green(` ā "${original}" ā "${replacement}"`));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (replacements.length === 0) {
|
|
100
|
+
console.log(chalk.yellow(' No replacements configured. Files will be copied as-is.'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return replacements;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Confirm before proceeding
|
|
108
|
+
*/
|
|
109
|
+
export async function confirmAction(message) {
|
|
110
|
+
const { confirmed } = await inquirer.prompt([
|
|
111
|
+
{
|
|
112
|
+
type: 'confirm',
|
|
113
|
+
name: 'confirmed',
|
|
114
|
+
message,
|
|
115
|
+
default: true
|
|
116
|
+
}
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
return confirmed;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Show summary of selections
|
|
124
|
+
*/
|
|
125
|
+
export async function showSummaryAndConfirm(fileCount, destination, replacements) {
|
|
126
|
+
console.log(chalk.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
127
|
+
console.log(chalk.white.bold('š Summary\n'));
|
|
128
|
+
console.log(chalk.white(` š Files to copy: ${chalk.yellow.bold(fileCount)}`));
|
|
129
|
+
console.log(chalk.white(` š Destination: ${chalk.yellow.bold(destination)}`));
|
|
130
|
+
|
|
131
|
+
if (replacements.length > 0) {
|
|
132
|
+
console.log(chalk.white(` š Replacements: ${chalk.yellow.bold(replacements.length)}`));
|
|
133
|
+
replacements.forEach(r => {
|
|
134
|
+
console.log(chalk.dim(` "${r.original}" ā "${r.replacement}"`));
|
|
135
|
+
});
|
|
136
|
+
} else {
|
|
137
|
+
console.log(chalk.white(` š Replacements: ${chalk.dim('None')}`));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log(chalk.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
141
|
+
|
|
142
|
+
return confirmAction('Proceed with extraction?');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Prompt for selecting a backup folder (for push command)
|
|
147
|
+
*/
|
|
148
|
+
export async function promptBackupFolder() {
|
|
149
|
+
const { folderPath } = await inquirer.prompt([
|
|
150
|
+
{
|
|
151
|
+
type: 'input',
|
|
152
|
+
name: 'folderPath',
|
|
153
|
+
message: 'Path to cloaked backup folder:',
|
|
154
|
+
validate: (input) => {
|
|
155
|
+
const path = resolve(input);
|
|
156
|
+
if (!existsSync(path)) {
|
|
157
|
+
return 'Folder does not exist. Please enter a valid path.';
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
return resolve(folderPath);
|
|
165
|
+
}
|
package/tests/anonymizer.test.js
CHANGED
|
@@ -1,127 +1,127 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Anonymizer Tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from 'vitest';
|
|
6
|
-
import { createAnonymizer, createDeanonymizer } from '../src/core/anonymizer.js';
|
|
7
|
-
|
|
8
|
-
describe('Anonymizer', () => {
|
|
9
|
-
describe('createAnonymizer', () => {
|
|
10
|
-
it('should replace exact matches', () => {
|
|
11
|
-
const anonymizer = createAnonymizer([
|
|
12
|
-
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
// Title case input -> Title case output (first letter upper, rest lower)
|
|
16
|
-
expect(anonymizer('Hello Cuviva world')).toBe('Hello Abccompany world');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should handle all uppercase', () => {
|
|
20
|
-
const anonymizer = createAnonymizer([
|
|
21
|
-
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
22
|
-
]);
|
|
23
|
-
|
|
24
|
-
expect(anonymizer('CUVIVA is great')).toBe('ABCCOMPANY is great');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should handle all lowercase', () => {
|
|
28
|
-
const anonymizer = createAnonymizer([
|
|
29
|
-
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
30
|
-
]);
|
|
31
|
-
|
|
32
|
-
expect(anonymizer('cuviva is lower')).toBe('abccompany is lower');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should handle multiple replacements', () => {
|
|
36
|
-
const anonymizer = createAnonymizer([
|
|
37
|
-
{ original: 'Cuviva', replacement: 'ABCCompany' },
|
|
38
|
-
{ original: 'Frontend', replacement: 'Client' }
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
// Both are Title case -> first upper + rest lower
|
|
42
|
-
expect(anonymizer('Cuviva Frontend API')).toBe('Abccompany Client API');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should handle empty replacements', () => {
|
|
46
|
-
const anonymizer = createAnonymizer([]);
|
|
47
|
-
expect(anonymizer('Hello world')).toBe('Hello world');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('should handle special regex characters in original', () => {
|
|
51
|
-
const anonymizer = createAnonymizer([
|
|
52
|
-
{ original: 'test.value', replacement: 'replaced' }
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
// Title case preservation
|
|
56
|
-
expect(anonymizer('This is Test.value here')).toBe('This is Replaced here');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should handle null or undefined replacements', () => {
|
|
60
|
-
const anonymizer = createAnonymizer(null);
|
|
61
|
-
expect(anonymizer('Hello world')).toBe('Hello world');
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('createDeanonymizer', () => {
|
|
66
|
-
it('should reverse the anonymization', () => {
|
|
67
|
-
const replacements = [
|
|
68
|
-
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
const deanonymizer = createDeanonymizer(replacements);
|
|
72
|
-
|
|
73
|
-
// ABCCompany (Title case) -> Cuviva (Title case: first upper + rest lower)
|
|
74
|
-
expect(deanonymizer('Hello ABCCompany world')).toBe('Hello Cuviva world');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should handle multiple replacements in reverse', () => {
|
|
78
|
-
const replacements = [
|
|
79
|
-
{ original: 'Cuviva', replacement: 'ABCCompany' },
|
|
80
|
-
{ original: 'API', replacement: 'Service' }
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
const deanonymizer = createDeanonymizer(replacements);
|
|
84
|
-
|
|
85
|
-
// Title case -> Title case for both
|
|
86
|
-
expect(deanonymizer('ABCCompany Service')).toBe('Cuviva Api');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should handle uppercase in reverse', () => {
|
|
90
|
-
const replacements = [
|
|
91
|
-
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
const deanonymizer = createDeanonymizer(replacements);
|
|
95
|
-
|
|
96
|
-
expect(deanonymizer('ABCCOMPANY')).toBe('CUVIVA');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('should handle lowercase in reverse', () => {
|
|
100
|
-
const replacements = [
|
|
101
|
-
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
102
|
-
];
|
|
103
|
-
|
|
104
|
-
const deanonymizer = createDeanonymizer(replacements);
|
|
105
|
-
|
|
106
|
-
expect(deanonymizer('abccompany')).toBe('cuviva');
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('Case transformation', () => {
|
|
112
|
-
it('should preserve all uppercase', () => {
|
|
113
|
-
const anonymizer = createAnonymizer([
|
|
114
|
-
{ original: 'SECRET', replacement: 'PUBLIC' }
|
|
115
|
-
]);
|
|
116
|
-
|
|
117
|
-
expect(anonymizer('This is SECRET data')).toBe('This is PUBLIC data');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should preserve all lowercase', () => {
|
|
121
|
-
const anonymizer = createAnonymizer([
|
|
122
|
-
{ original: 'secret', replacement: 'public' }
|
|
123
|
-
]);
|
|
124
|
-
|
|
125
|
-
expect(anonymizer('this is secret data')).toBe('this is public data');
|
|
126
|
-
});
|
|
127
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Anonymizer Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { createAnonymizer, createDeanonymizer } from '../src/core/anonymizer.js';
|
|
7
|
+
|
|
8
|
+
describe('Anonymizer', () => {
|
|
9
|
+
describe('createAnonymizer', () => {
|
|
10
|
+
it('should replace exact matches', () => {
|
|
11
|
+
const anonymizer = createAnonymizer([
|
|
12
|
+
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
// Title case input -> Title case output (first letter upper, rest lower)
|
|
16
|
+
expect(anonymizer('Hello Cuviva world')).toBe('Hello Abccompany world');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should handle all uppercase', () => {
|
|
20
|
+
const anonymizer = createAnonymizer([
|
|
21
|
+
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
expect(anonymizer('CUVIVA is great')).toBe('ABCCOMPANY is great');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle all lowercase', () => {
|
|
28
|
+
const anonymizer = createAnonymizer([
|
|
29
|
+
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
expect(anonymizer('cuviva is lower')).toBe('abccompany is lower');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should handle multiple replacements', () => {
|
|
36
|
+
const anonymizer = createAnonymizer([
|
|
37
|
+
{ original: 'Cuviva', replacement: 'ABCCompany' },
|
|
38
|
+
{ original: 'Frontend', replacement: 'Client' }
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
// Both are Title case -> first upper + rest lower
|
|
42
|
+
expect(anonymizer('Cuviva Frontend API')).toBe('Abccompany Client API');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should handle empty replacements', () => {
|
|
46
|
+
const anonymizer = createAnonymizer([]);
|
|
47
|
+
expect(anonymizer('Hello world')).toBe('Hello world');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle special regex characters in original', () => {
|
|
51
|
+
const anonymizer = createAnonymizer([
|
|
52
|
+
{ original: 'test.value', replacement: 'replaced' }
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
// Title case preservation
|
|
56
|
+
expect(anonymizer('This is Test.value here')).toBe('This is Replaced here');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle null or undefined replacements', () => {
|
|
60
|
+
const anonymizer = createAnonymizer(null);
|
|
61
|
+
expect(anonymizer('Hello world')).toBe('Hello world');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('createDeanonymizer', () => {
|
|
66
|
+
it('should reverse the anonymization', () => {
|
|
67
|
+
const replacements = [
|
|
68
|
+
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const deanonymizer = createDeanonymizer(replacements);
|
|
72
|
+
|
|
73
|
+
// ABCCompany (Title case) -> Cuviva (Title case: first upper + rest lower)
|
|
74
|
+
expect(deanonymizer('Hello ABCCompany world')).toBe('Hello Cuviva world');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should handle multiple replacements in reverse', () => {
|
|
78
|
+
const replacements = [
|
|
79
|
+
{ original: 'Cuviva', replacement: 'ABCCompany' },
|
|
80
|
+
{ original: 'API', replacement: 'Service' }
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const deanonymizer = createDeanonymizer(replacements);
|
|
84
|
+
|
|
85
|
+
// Title case -> Title case for both
|
|
86
|
+
expect(deanonymizer('ABCCompany Service')).toBe('Cuviva Api');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle uppercase in reverse', () => {
|
|
90
|
+
const replacements = [
|
|
91
|
+
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const deanonymizer = createDeanonymizer(replacements);
|
|
95
|
+
|
|
96
|
+
expect(deanonymizer('ABCCOMPANY')).toBe('CUVIVA');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle lowercase in reverse', () => {
|
|
100
|
+
const replacements = [
|
|
101
|
+
{ original: 'Cuviva', replacement: 'ABCCompany' }
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const deanonymizer = createDeanonymizer(replacements);
|
|
105
|
+
|
|
106
|
+
expect(deanonymizer('abccompany')).toBe('cuviva');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('Case transformation', () => {
|
|
112
|
+
it('should preserve all uppercase', () => {
|
|
113
|
+
const anonymizer = createAnonymizer([
|
|
114
|
+
{ original: 'SECRET', replacement: 'PUBLIC' }
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
expect(anonymizer('This is SECRET data')).toBe('This is PUBLIC data');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should preserve all lowercase', () => {
|
|
121
|
+
const anonymizer = createAnonymizer([
|
|
122
|
+
{ original: 'secret', replacement: 'public' }
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
expect(anonymizer('this is secret data')).toBe('this is public data');
|
|
126
|
+
});
|
|
127
|
+
});
|