exarch-rs 0.1.0 → 0.1.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.
- package/Cargo.toml +1 -0
- package/README.md +1 -1
- package/biome.json +47 -0
- package/exarch-rs.darwin-arm64.node +0 -0
- package/index.d.ts +551 -181
- package/index.js +588 -0
- package/package.json +25 -5
- package/src/config.rs +303 -47
- package/src/error.rs +42 -0
- package/src/lib.rs +553 -14
- package/src/report.rs +538 -0
- package/tests/create.test.js +124 -0
- package/tests/creation-config.test.js +97 -0
- package/tests/extract.test.js +118 -0
- package/tests/list-verify.test.js +148 -0
- package/tests/security-config.test.js +187 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for CreationConfig class
|
|
3
|
+
*/
|
|
4
|
+
const { describe, it } = require('node:test');
|
|
5
|
+
const assert = require('node:assert');
|
|
6
|
+
const { CreationConfig } = require('../index.js');
|
|
7
|
+
|
|
8
|
+
describe('CreationConfig', () => {
|
|
9
|
+
describe('constructor', () => {
|
|
10
|
+
it('should create config with defaults', () => {
|
|
11
|
+
const config = new CreationConfig();
|
|
12
|
+
|
|
13
|
+
assert.strictEqual(config.preservePermissions, true);
|
|
14
|
+
assert.strictEqual(config.followSymlinks, false);
|
|
15
|
+
assert.strictEqual(config.includeHidden, false);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('static default()', () => {
|
|
20
|
+
it('should return config equivalent to constructor', () => {
|
|
21
|
+
const config = CreationConfig.default();
|
|
22
|
+
|
|
23
|
+
assert.strictEqual(config.preservePermissions, true);
|
|
24
|
+
assert.strictEqual(config.followSymlinks, false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('builder methods', () => {
|
|
29
|
+
it('should set compression level', () => {
|
|
30
|
+
const config = new CreationConfig();
|
|
31
|
+
config.setCompressionLevel(9);
|
|
32
|
+
|
|
33
|
+
assert.strictEqual(config.compressionLevel, 9);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should set preserve permissions', () => {
|
|
37
|
+
const config = new CreationConfig();
|
|
38
|
+
config.setPreservePermissions(false);
|
|
39
|
+
|
|
40
|
+
assert.strictEqual(config.preservePermissions, false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should set follow symlinks', () => {
|
|
44
|
+
const config = new CreationConfig();
|
|
45
|
+
config.setFollowSymlinks(true);
|
|
46
|
+
|
|
47
|
+
assert.strictEqual(config.followSymlinks, true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should set include hidden', () => {
|
|
51
|
+
const config = new CreationConfig();
|
|
52
|
+
config.setIncludeHidden(true);
|
|
53
|
+
|
|
54
|
+
assert.strictEqual(config.includeHidden, true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should set max file size', () => {
|
|
58
|
+
const config = new CreationConfig();
|
|
59
|
+
config.setMaxFileSize(100 * 1024 * 1024);
|
|
60
|
+
|
|
61
|
+
assert.strictEqual(config.maxFileSize, 100 * 1024 * 1024);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('exclude patterns', () => {
|
|
66
|
+
it('should add exclude patterns', () => {
|
|
67
|
+
const config = new CreationConfig();
|
|
68
|
+
config.addExcludePattern('*.log');
|
|
69
|
+
config.addExcludePattern('node_modules');
|
|
70
|
+
|
|
71
|
+
assert.ok(config.excludePatterns.includes('*.log'));
|
|
72
|
+
assert.ok(config.excludePatterns.includes('node_modules'));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('compression levels', () => {
|
|
77
|
+
it('should accept valid compression levels 1-9', () => {
|
|
78
|
+
for (let level = 1; level <= 9; level++) {
|
|
79
|
+
const config = new CreationConfig();
|
|
80
|
+
config.setCompressionLevel(level);
|
|
81
|
+
assert.strictEqual(config.compressionLevel, level);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should throw on invalid compression level', () => {
|
|
86
|
+
const config = new CreationConfig();
|
|
87
|
+
|
|
88
|
+
assert.throws(() => {
|
|
89
|
+
config.setCompressionLevel(0);
|
|
90
|
+
}, /1.*9|invalid/i);
|
|
91
|
+
|
|
92
|
+
assert.throws(() => {
|
|
93
|
+
config.setCompressionLevel(10);
|
|
94
|
+
}, /1.*9|invalid/i);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for archive extraction functions
|
|
3
|
+
*
|
|
4
|
+
* NOTE: Extraction tests are skipped until exarch-core extract_archive API is fully implemented.
|
|
5
|
+
* The current implementation is a placeholder (see exarch-core/src/api.rs).
|
|
6
|
+
*/
|
|
7
|
+
const { describe, it, beforeEach } = require('node:test');
|
|
8
|
+
const assert = require('node:assert');
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const os = require('node:os');
|
|
12
|
+
const {
|
|
13
|
+
extractArchive,
|
|
14
|
+
extractArchiveSync,
|
|
15
|
+
createArchiveSync,
|
|
16
|
+
SecurityConfig,
|
|
17
|
+
} = require('../index.js');
|
|
18
|
+
|
|
19
|
+
function createTempDir() {
|
|
20
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'exarch-test-'));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createValidArchive(archivePath, tempDir) {
|
|
24
|
+
// Create source files
|
|
25
|
+
const sourceDir = path.join(tempDir, 'source');
|
|
26
|
+
fs.mkdirSync(sourceDir);
|
|
27
|
+
fs.writeFileSync(path.join(sourceDir, 'hello.txt'), 'Hello, World!');
|
|
28
|
+
|
|
29
|
+
// Create archive using our library
|
|
30
|
+
createArchiveSync(archivePath, [sourceDir]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('extractArchive (async)', () => {
|
|
34
|
+
let tempDir;
|
|
35
|
+
let archivePath;
|
|
36
|
+
let outputDir;
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
tempDir = createTempDir();
|
|
40
|
+
archivePath = path.join(tempDir, 'test.tar.gz');
|
|
41
|
+
outputDir = path.join(tempDir, 'output');
|
|
42
|
+
fs.mkdirSync(outputDir);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// TODO: Enable when core extract_archive is implemented
|
|
46
|
+
it.skip('should extract a valid archive', async () => {
|
|
47
|
+
createValidArchive(archivePath, tempDir);
|
|
48
|
+
|
|
49
|
+
const report = await extractArchive(archivePath, outputDir);
|
|
50
|
+
|
|
51
|
+
assert.ok(report.filesExtracted >= 1);
|
|
52
|
+
assert.ok(report.bytesWritten >= 13);
|
|
53
|
+
assert.ok(report.durationMs >= 0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// TODO: Enable when core extract_archive is implemented
|
|
57
|
+
it.skip('should accept custom SecurityConfig', async () => {
|
|
58
|
+
createValidArchive(archivePath, tempDir);
|
|
59
|
+
|
|
60
|
+
const config = new SecurityConfig();
|
|
61
|
+
config.setMaxFileSize(1024 * 1024);
|
|
62
|
+
const report = await extractArchive(archivePath, outputDir, config);
|
|
63
|
+
|
|
64
|
+
assert.ok(report.filesExtracted >= 1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should return empty report for valid archive (placeholder)', async () => {
|
|
68
|
+
createValidArchive(archivePath, tempDir);
|
|
69
|
+
|
|
70
|
+
const report = await extractArchive(archivePath, outputDir);
|
|
71
|
+
|
|
72
|
+
// Core extract_archive is currently a placeholder
|
|
73
|
+
assert.strictEqual(report.filesExtracted, 0);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('extractArchiveSync', () => {
|
|
78
|
+
let tempDir;
|
|
79
|
+
let archivePath;
|
|
80
|
+
let outputDir;
|
|
81
|
+
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
tempDir = createTempDir();
|
|
84
|
+
archivePath = path.join(tempDir, 'test.tar.gz');
|
|
85
|
+
outputDir = path.join(tempDir, 'output');
|
|
86
|
+
fs.mkdirSync(outputDir);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// TODO: Enable when core extract_archive is implemented
|
|
90
|
+
it.skip('should extract a valid archive synchronously', () => {
|
|
91
|
+
createValidArchive(archivePath, tempDir);
|
|
92
|
+
|
|
93
|
+
const report = extractArchiveSync(archivePath, outputDir);
|
|
94
|
+
|
|
95
|
+
assert.ok(report.filesExtracted >= 1);
|
|
96
|
+
assert.ok(report.bytesWritten >= 13);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// TODO: Enable when core extract_archive is implemented
|
|
100
|
+
it.skip('should accept custom SecurityConfig', () => {
|
|
101
|
+
createValidArchive(archivePath, tempDir);
|
|
102
|
+
|
|
103
|
+
const config = new SecurityConfig();
|
|
104
|
+
config.setMaxFileCount(100);
|
|
105
|
+
const report = extractArchiveSync(archivePath, outputDir, config);
|
|
106
|
+
|
|
107
|
+
assert.ok(report.filesExtracted >= 1);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should return empty report for valid archive (placeholder)', () => {
|
|
111
|
+
createValidArchive(archivePath, tempDir);
|
|
112
|
+
|
|
113
|
+
const report = extractArchiveSync(archivePath, outputDir);
|
|
114
|
+
|
|
115
|
+
// Core extract_archive is currently a placeholder
|
|
116
|
+
assert.strictEqual(report.filesExtracted, 0);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for listArchive and verifyArchive functions
|
|
3
|
+
*/
|
|
4
|
+
const { describe, it, beforeEach } = require('node:test');
|
|
5
|
+
const assert = require('node:assert');
|
|
6
|
+
const fs = require('node:fs');
|
|
7
|
+
const path = require('node:path');
|
|
8
|
+
const os = require('node:os');
|
|
9
|
+
const {
|
|
10
|
+
listArchive,
|
|
11
|
+
listArchiveSync,
|
|
12
|
+
verifyArchive,
|
|
13
|
+
verifyArchiveSync,
|
|
14
|
+
createArchiveSync,
|
|
15
|
+
SecurityConfig,
|
|
16
|
+
} = require('../index.js');
|
|
17
|
+
|
|
18
|
+
function createTempDir() {
|
|
19
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'exarch-test-'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function createValidArchive(archivePath, tempDir) {
|
|
23
|
+
// Create source files
|
|
24
|
+
const sourceDir = path.join(tempDir, 'source');
|
|
25
|
+
fs.mkdirSync(sourceDir);
|
|
26
|
+
fs.writeFileSync(path.join(sourceDir, 'hello.txt'), 'Hello, World!');
|
|
27
|
+
|
|
28
|
+
// Create archive using our library
|
|
29
|
+
createArchiveSync(archivePath, [sourceDir]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('listArchive (async)', () => {
|
|
33
|
+
let tempDir;
|
|
34
|
+
let archivePath;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
tempDir = createTempDir();
|
|
38
|
+
archivePath = path.join(tempDir, 'test.tar.gz');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should list archive contents', async () => {
|
|
42
|
+
createValidArchive(archivePath, tempDir);
|
|
43
|
+
|
|
44
|
+
const manifest = await listArchive(archivePath);
|
|
45
|
+
|
|
46
|
+
assert.ok(manifest.totalEntries >= 1);
|
|
47
|
+
assert.ok(manifest.entries.length >= 1);
|
|
48
|
+
// Find the hello.txt entry
|
|
49
|
+
const helloEntry = manifest.entries.find((e) => e.path.endsWith('hello.txt'));
|
|
50
|
+
assert.ok(helloEntry, 'should find hello.txt in archive');
|
|
51
|
+
assert.strictEqual(helloEntry.entryType, 'File');
|
|
52
|
+
assert.strictEqual(helloEntry.size, 13);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should accept custom SecurityConfig', async () => {
|
|
56
|
+
createValidArchive(archivePath, tempDir);
|
|
57
|
+
|
|
58
|
+
const config = new SecurityConfig();
|
|
59
|
+
const manifest = await listArchive(archivePath, config);
|
|
60
|
+
|
|
61
|
+
assert.ok(manifest.totalEntries >= 1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should throw on non-existent archive', async () => {
|
|
65
|
+
await assert.rejects(
|
|
66
|
+
listArchive('/nonexistent/archive.tar.gz'),
|
|
67
|
+
/IO_ERROR|No such file|not found/i
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('listArchiveSync', () => {
|
|
73
|
+
let tempDir;
|
|
74
|
+
let archivePath;
|
|
75
|
+
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
tempDir = createTempDir();
|
|
78
|
+
archivePath = path.join(tempDir, 'test.tar.gz');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should list archive contents synchronously', () => {
|
|
82
|
+
createValidArchive(archivePath, tempDir);
|
|
83
|
+
|
|
84
|
+
const manifest = listArchiveSync(archivePath);
|
|
85
|
+
|
|
86
|
+
assert.ok(manifest.totalEntries >= 1);
|
|
87
|
+
const helloEntry = manifest.entries.find((e) => e.path.endsWith('hello.txt'));
|
|
88
|
+
assert.ok(helloEntry, 'should find hello.txt in archive');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('verifyArchive (async)', () => {
|
|
93
|
+
let tempDir;
|
|
94
|
+
let archivePath;
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
tempDir = createTempDir();
|
|
98
|
+
archivePath = path.join(tempDir, 'test.tar.gz');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should verify a valid archive', async () => {
|
|
102
|
+
createValidArchive(archivePath, tempDir);
|
|
103
|
+
|
|
104
|
+
const report = await verifyArchive(archivePath);
|
|
105
|
+
|
|
106
|
+
assert.ok(['PASS', 'WARNING'].includes(report.status));
|
|
107
|
+
assert.ok(report.totalEntries >= 1);
|
|
108
|
+
assert.ok(report.integrityStatus);
|
|
109
|
+
assert.ok(report.securityStatus);
|
|
110
|
+
assert.ok(Array.isArray(report.issues));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should accept custom SecurityConfig', async () => {
|
|
114
|
+
createValidArchive(archivePath, tempDir);
|
|
115
|
+
|
|
116
|
+
const config = new SecurityConfig();
|
|
117
|
+
config.setMaxFileSize(1024);
|
|
118
|
+
const report = await verifyArchive(archivePath, config);
|
|
119
|
+
|
|
120
|
+
assert.ok(report.status);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should throw on non-existent archive', async () => {
|
|
124
|
+
await assert.rejects(
|
|
125
|
+
verifyArchive('/nonexistent/archive.tar.gz'),
|
|
126
|
+
/IO_ERROR|No such file|not found/i
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('verifyArchiveSync', () => {
|
|
132
|
+
let tempDir;
|
|
133
|
+
let archivePath;
|
|
134
|
+
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
tempDir = createTempDir();
|
|
137
|
+
archivePath = path.join(tempDir, 'test.tar.gz');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should verify archive synchronously', () => {
|
|
141
|
+
createValidArchive(archivePath, tempDir);
|
|
142
|
+
|
|
143
|
+
const report = verifyArchiveSync(archivePath);
|
|
144
|
+
|
|
145
|
+
assert.ok(['PASS', 'WARNING'].includes(report.status));
|
|
146
|
+
assert.ok(report.totalEntries >= 1);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for SecurityConfig class
|
|
3
|
+
*/
|
|
4
|
+
const { describe, it } = require('node:test');
|
|
5
|
+
const assert = require('node:assert');
|
|
6
|
+
const { SecurityConfig } = require('../index.js');
|
|
7
|
+
|
|
8
|
+
describe('SecurityConfig', () => {
|
|
9
|
+
describe('constructor', () => {
|
|
10
|
+
it('should create config with secure defaults', () => {
|
|
11
|
+
const config = new SecurityConfig();
|
|
12
|
+
|
|
13
|
+
assert.strictEqual(config.allowSymlinks, false);
|
|
14
|
+
assert.strictEqual(config.allowHardlinks, false);
|
|
15
|
+
assert.strictEqual(config.allowAbsolutePaths, false);
|
|
16
|
+
assert.strictEqual(config.allowWorldWritable, false);
|
|
17
|
+
assert.strictEqual(config.preservePermissions, false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should have default size limits', () => {
|
|
21
|
+
const config = new SecurityConfig();
|
|
22
|
+
|
|
23
|
+
assert.strictEqual(config.maxFileSize, 50 * 1024 * 1024); // 50 MB
|
|
24
|
+
assert.strictEqual(config.maxTotalSize, 500 * 1024 * 1024); // 500 MB
|
|
25
|
+
assert.strictEqual(config.maxFileCount, 10000);
|
|
26
|
+
assert.strictEqual(config.maxPathDepth, 32);
|
|
27
|
+
assert.strictEqual(config.maxCompressionRatio, 100.0);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('static default()', () => {
|
|
32
|
+
it('should return config equivalent to constructor', () => {
|
|
33
|
+
const config = SecurityConfig.default();
|
|
34
|
+
|
|
35
|
+
assert.strictEqual(config.allowSymlinks, false);
|
|
36
|
+
assert.strictEqual(config.maxFileCount, 10000);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('static permissive()', () => {
|
|
41
|
+
it('should return permissive configuration', () => {
|
|
42
|
+
const config = SecurityConfig.permissive();
|
|
43
|
+
|
|
44
|
+
assert.strictEqual(config.allowSymlinks, true);
|
|
45
|
+
assert.strictEqual(config.allowHardlinks, true);
|
|
46
|
+
assert.strictEqual(config.allowAbsolutePaths, true);
|
|
47
|
+
assert.strictEqual(config.allowWorldWritable, true);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('builder methods', () => {
|
|
52
|
+
it('should set max file size', () => {
|
|
53
|
+
const config = new SecurityConfig();
|
|
54
|
+
config.setMaxFileSize(100 * 1024 * 1024);
|
|
55
|
+
|
|
56
|
+
assert.strictEqual(config.maxFileSize, 100 * 1024 * 1024);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should set max total size', () => {
|
|
60
|
+
const config = new SecurityConfig();
|
|
61
|
+
config.setMaxTotalSize(1024 * 1024 * 1024);
|
|
62
|
+
|
|
63
|
+
assert.strictEqual(config.maxTotalSize, 1024 * 1024 * 1024);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should set max file count', () => {
|
|
67
|
+
const config = new SecurityConfig();
|
|
68
|
+
config.setMaxFileCount(50000);
|
|
69
|
+
|
|
70
|
+
assert.strictEqual(config.maxFileCount, 50000);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should set max path depth', () => {
|
|
74
|
+
const config = new SecurityConfig();
|
|
75
|
+
config.setMaxPathDepth(64);
|
|
76
|
+
|
|
77
|
+
assert.strictEqual(config.maxPathDepth, 64);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should set max compression ratio', () => {
|
|
81
|
+
const config = new SecurityConfig();
|
|
82
|
+
config.setMaxCompressionRatio(50.0);
|
|
83
|
+
|
|
84
|
+
assert.strictEqual(config.maxCompressionRatio, 50.0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should set allow symlinks', () => {
|
|
88
|
+
const config = new SecurityConfig();
|
|
89
|
+
config.setAllowSymlinks(true);
|
|
90
|
+
|
|
91
|
+
assert.strictEqual(config.allowSymlinks, true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should set allow hardlinks', () => {
|
|
95
|
+
const config = new SecurityConfig();
|
|
96
|
+
config.setAllowHardlinks(true);
|
|
97
|
+
|
|
98
|
+
assert.strictEqual(config.allowHardlinks, true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should set allow absolute paths', () => {
|
|
102
|
+
const config = new SecurityConfig();
|
|
103
|
+
config.setAllowAbsolutePaths(true);
|
|
104
|
+
|
|
105
|
+
assert.strictEqual(config.allowAbsolutePaths, true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should set allow world writable', () => {
|
|
109
|
+
const config = new SecurityConfig();
|
|
110
|
+
config.setAllowWorldWritable(true);
|
|
111
|
+
|
|
112
|
+
assert.strictEqual(config.allowWorldWritable, true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should set preserve permissions', () => {
|
|
116
|
+
const config = new SecurityConfig();
|
|
117
|
+
config.setPreservePermissions(true);
|
|
118
|
+
|
|
119
|
+
assert.strictEqual(config.preservePermissions, true);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('extension filtering', () => {
|
|
124
|
+
it('should add allowed extensions', () => {
|
|
125
|
+
const config = new SecurityConfig();
|
|
126
|
+
config.addAllowedExtension('txt');
|
|
127
|
+
config.addAllowedExtension('md');
|
|
128
|
+
|
|
129
|
+
assert.ok(config.allowedExtensions.includes('txt'));
|
|
130
|
+
assert.ok(config.allowedExtensions.includes('md'));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should check extension allowed', () => {
|
|
134
|
+
const config = new SecurityConfig();
|
|
135
|
+
config.addAllowedExtension('txt');
|
|
136
|
+
|
|
137
|
+
assert.strictEqual(config.isExtensionAllowed('txt'), true);
|
|
138
|
+
assert.strictEqual(config.isExtensionAllowed('exe'), false);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should allow all extensions when none specified', () => {
|
|
142
|
+
const config = new SecurityConfig();
|
|
143
|
+
|
|
144
|
+
assert.strictEqual(config.isExtensionAllowed('txt'), true);
|
|
145
|
+
assert.strictEqual(config.isExtensionAllowed('exe'), true);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('path component banning', () => {
|
|
150
|
+
it('should add banned components', () => {
|
|
151
|
+
const config = new SecurityConfig();
|
|
152
|
+
config.addBannedComponent('.secret');
|
|
153
|
+
|
|
154
|
+
assert.ok(config.bannedPathComponents.includes('.secret'));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should check path component allowed', () => {
|
|
158
|
+
const config = new SecurityConfig();
|
|
159
|
+
config.addBannedComponent('.secret');
|
|
160
|
+
|
|
161
|
+
assert.strictEqual(config.isPathComponentAllowed('src'), true);
|
|
162
|
+
assert.strictEqual(config.isPathComponentAllowed('.secret'), false);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('validation', () => {
|
|
167
|
+
it('should throw on invalid compression ratio', () => {
|
|
168
|
+
const config = new SecurityConfig();
|
|
169
|
+
|
|
170
|
+
assert.throws(() => {
|
|
171
|
+
config.setMaxCompressionRatio(-1);
|
|
172
|
+
}, /positive/i);
|
|
173
|
+
|
|
174
|
+
assert.throws(() => {
|
|
175
|
+
config.setMaxCompressionRatio(Infinity);
|
|
176
|
+
}, /finite/i);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should throw on negative file size', () => {
|
|
180
|
+
const config = new SecurityConfig();
|
|
181
|
+
|
|
182
|
+
assert.throws(() => {
|
|
183
|
+
config.setMaxFileSize(-1);
|
|
184
|
+
}, /negative/i);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|