@forwardimpact/libutil 0.1.72 → 0.1.73
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/package.json +12 -2
- package/{finder.js → src/finder.js} +4 -1
- package/test/downloader.test.js +0 -223
- package/test/extractor.test.js +0 -338
- package/test/finder.test.js +0 -358
- package/test/fixtures/sample.tar.gz +0 -0
- package/test/fixtures/sample.zip +0 -0
- package/test/http.test.js +0 -93
- package/test/libutil.test.js +0 -35
- package/test/logger.test.js +0 -219
- package/test/processor.test.js +0 -140
- package/test/retry.test.js +0 -194
- package/test/tokenizer.test.js +0 -123
- package/test/wait.test.js +0 -109
- /package/{downloader.js → src/downloader.js} +0 -0
- /package/{extractor.js → src/extractor.js} +0 -0
- /package/{http.js → src/http.js} +0 -0
- /package/{index.js → src/index.js} +0 -0
- /package/{processor.js → src/processor.js} +0 -0
- /package/{retry.js → src/retry.js} +0 -0
- /package/{tokenizer.js → src/tokenizer.js} +0 -0
- /package/{wait.js → src/wait.js} +0 -0
package/package.json
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/libutil",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.73",
|
|
4
4
|
"description": "Utility functions and utilities for Guide",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "D. Olsson <hi@senzilla.io>",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"main": "index.js",
|
|
8
|
+
"main": "./src/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.js",
|
|
11
|
+
"./bin/fit-download-bundle.js": "./bin/fit-download-bundle.js",
|
|
12
|
+
"./bin/fit-tiktoken.js": "./bin/fit-tiktoken.js"
|
|
13
|
+
},
|
|
9
14
|
"bin": {
|
|
10
15
|
"fit-download-bundle": "./bin/fit-download-bundle.js",
|
|
11
16
|
"fit-tiktoken": "./bin/fit-tiktoken.js"
|
|
12
17
|
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src/**/*.js",
|
|
20
|
+
"bin/**/*.js",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
13
23
|
"engines": {
|
|
14
24
|
"bun": ">=1.2.0",
|
|
15
25
|
"node": ">=18.0.0"
|
|
@@ -114,7 +114,7 @@ export class Finder {
|
|
|
114
114
|
*/
|
|
115
115
|
findGeneratedPath(projectRoot, packageName) {
|
|
116
116
|
const packagePath = this.findPackagePath(projectRoot, packageName);
|
|
117
|
-
return path.join(packagePath, "generated");
|
|
117
|
+
return path.join(packagePath, "src", "generated");
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
@@ -139,6 +139,9 @@ export class Finder {
|
|
|
139
139
|
// Target doesn't exist, which is fine
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
// Ensure the target's parent directory exists before symlinking
|
|
143
|
+
await fsAsync.mkdir(path.dirname(targetPath), { recursive: true });
|
|
144
|
+
|
|
142
145
|
// Create the symlink
|
|
143
146
|
await fsAsync.symlink(sourcePath, targetPath, "dir");
|
|
144
147
|
this.#logger.debug("Finder", "Created symlink", {
|
package/test/downloader.test.js
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { strict as assert } from "node:assert";
|
|
2
|
-
import { test, describe, beforeEach, mock } from "node:test";
|
|
3
|
-
|
|
4
|
-
import { createSilentLogger } from "@forwardimpact/libharness";
|
|
5
|
-
|
|
6
|
-
import { BundleDownloader } from "../downloader.js";
|
|
7
|
-
|
|
8
|
-
describe("BundleDownloader", () => {
|
|
9
|
-
let mockStorageFactory;
|
|
10
|
-
let mockExtractor;
|
|
11
|
-
let mockLogger;
|
|
12
|
-
let mockFinder;
|
|
13
|
-
let mockProcess;
|
|
14
|
-
let mockLocalStorage;
|
|
15
|
-
let mockRemoteStorage;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
mockLocalStorage = {
|
|
19
|
-
ensureBucket: async () => {},
|
|
20
|
-
put: async () => {},
|
|
21
|
-
delete: async () => {},
|
|
22
|
-
path: (key = ".") => `/local/path/${key}`,
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
mockRemoteStorage = {
|
|
26
|
-
exists: async () => true,
|
|
27
|
-
get: async () => Buffer.from("bundle data"),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
mockStorageFactory = (prefix, type) => {
|
|
31
|
-
return type === "local" ? mockLocalStorage : mockRemoteStorage;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
mockExtractor = {
|
|
35
|
-
extract: mock.fn(async () => {}),
|
|
36
|
-
};
|
|
37
|
-
mockLogger = createSilentLogger();
|
|
38
|
-
mockFinder = {
|
|
39
|
-
createPackageSymlinks: mock.fn(async () => {}),
|
|
40
|
-
};
|
|
41
|
-
mockProcess = {
|
|
42
|
-
env: { STORAGE_TYPE: "s3" },
|
|
43
|
-
};
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("constructor validates required dependencies", () => {
|
|
47
|
-
assert.throws(
|
|
48
|
-
() =>
|
|
49
|
-
new BundleDownloader(
|
|
50
|
-
null,
|
|
51
|
-
mockFinder,
|
|
52
|
-
mockLogger,
|
|
53
|
-
mockExtractor,
|
|
54
|
-
mockProcess,
|
|
55
|
-
),
|
|
56
|
-
/createStorageFn is required/,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
assert.throws(
|
|
60
|
-
() =>
|
|
61
|
-
new BundleDownloader(
|
|
62
|
-
mockStorageFactory,
|
|
63
|
-
null,
|
|
64
|
-
mockLogger,
|
|
65
|
-
mockExtractor,
|
|
66
|
-
mockProcess,
|
|
67
|
-
),
|
|
68
|
-
/finder is required/,
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
assert.throws(
|
|
72
|
-
() =>
|
|
73
|
-
new BundleDownloader(
|
|
74
|
-
mockStorageFactory,
|
|
75
|
-
mockFinder,
|
|
76
|
-
null,
|
|
77
|
-
mockExtractor,
|
|
78
|
-
mockProcess,
|
|
79
|
-
),
|
|
80
|
-
/logger is required/,
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
assert.throws(
|
|
84
|
-
() =>
|
|
85
|
-
new BundleDownloader(
|
|
86
|
-
mockStorageFactory,
|
|
87
|
-
mockFinder,
|
|
88
|
-
mockLogger,
|
|
89
|
-
null,
|
|
90
|
-
mockProcess,
|
|
91
|
-
),
|
|
92
|
-
/extractor is required/,
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
assert.throws(
|
|
96
|
-
() =>
|
|
97
|
-
new BundleDownloader(
|
|
98
|
-
mockStorageFactory,
|
|
99
|
-
mockFinder,
|
|
100
|
-
mockLogger,
|
|
101
|
-
mockExtractor,
|
|
102
|
-
null,
|
|
103
|
-
),
|
|
104
|
-
/process is required/,
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("downloads and extracts bundle when it exists", async () => {
|
|
109
|
-
const operations = [];
|
|
110
|
-
mockLocalStorage.put = async (key, data) => {
|
|
111
|
-
operations.push({ op: "put", key, hasData: !!data });
|
|
112
|
-
};
|
|
113
|
-
mockLocalStorage.delete = async (key) => {
|
|
114
|
-
operations.push({ op: "delete", key });
|
|
115
|
-
};
|
|
116
|
-
mockExtractor.extract = async (sourcePath, targetPath) => {
|
|
117
|
-
operations.push({ op: "extract", sourcePath, targetPath });
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const download = new BundleDownloader(
|
|
121
|
-
mockStorageFactory,
|
|
122
|
-
mockFinder,
|
|
123
|
-
mockLogger,
|
|
124
|
-
mockExtractor,
|
|
125
|
-
mockProcess,
|
|
126
|
-
);
|
|
127
|
-
await download.initialize();
|
|
128
|
-
await download.download();
|
|
129
|
-
|
|
130
|
-
assert.strictEqual(operations.length, 3);
|
|
131
|
-
assert.deepStrictEqual(operations[0], {
|
|
132
|
-
op: "put",
|
|
133
|
-
key: "bundle.tar.gz",
|
|
134
|
-
hasData: true,
|
|
135
|
-
});
|
|
136
|
-
assert.strictEqual(operations[1].op, "extract");
|
|
137
|
-
assert.strictEqual(operations[1].sourcePath, "/local/path/bundle.tar.gz");
|
|
138
|
-
assert.strictEqual(operations[1].targetPath, "/local/path/.");
|
|
139
|
-
assert.deepStrictEqual(operations[2], {
|
|
140
|
-
op: "delete",
|
|
141
|
-
key: "bundle.tar.gz",
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("throws error when bundle does not exist", async () => {
|
|
146
|
-
mockRemoteStorage.exists = async () => false;
|
|
147
|
-
|
|
148
|
-
const download = new BundleDownloader(
|
|
149
|
-
mockStorageFactory,
|
|
150
|
-
mockFinder,
|
|
151
|
-
mockLogger,
|
|
152
|
-
mockExtractor,
|
|
153
|
-
mockProcess,
|
|
154
|
-
);
|
|
155
|
-
await download.initialize();
|
|
156
|
-
|
|
157
|
-
await assert.rejects(() => download.download(), {
|
|
158
|
-
message: /Bundle not found/,
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test("initializes storage instances correctly", async () => {
|
|
163
|
-
const ensureBucketCalls = [];
|
|
164
|
-
mockLocalStorage.ensureBucket = async () => {
|
|
165
|
-
ensureBucketCalls.push("generated");
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const download = new BundleDownloader(
|
|
169
|
-
mockStorageFactory,
|
|
170
|
-
mockFinder,
|
|
171
|
-
mockLogger,
|
|
172
|
-
mockExtractor,
|
|
173
|
-
mockProcess,
|
|
174
|
-
);
|
|
175
|
-
await download.initialize();
|
|
176
|
-
|
|
177
|
-
assert.strictEqual(ensureBucketCalls.length, 1);
|
|
178
|
-
assert.strictEqual(ensureBucketCalls[0], "generated");
|
|
179
|
-
|
|
180
|
-
// Should have called createPackageSymlinks
|
|
181
|
-
assert.strictEqual(mockFinder.createPackageSymlinks.mock.calls.length, 1);
|
|
182
|
-
assert.strictEqual(
|
|
183
|
-
mockFinder.createPackageSymlinks.mock.calls[0].arguments[0],
|
|
184
|
-
"/local/path/.",
|
|
185
|
-
);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("skips download when STORAGE_TYPE is local", async () => {
|
|
189
|
-
mockProcess.env.STORAGE_TYPE = "local";
|
|
190
|
-
mockRemoteStorage.exists = mock.fn(async () => true);
|
|
191
|
-
|
|
192
|
-
const download = new BundleDownloader(
|
|
193
|
-
mockStorageFactory,
|
|
194
|
-
mockFinder,
|
|
195
|
-
mockLogger,
|
|
196
|
-
mockExtractor,
|
|
197
|
-
mockProcess,
|
|
198
|
-
);
|
|
199
|
-
await download.initialize();
|
|
200
|
-
await download.download();
|
|
201
|
-
|
|
202
|
-
// Should not have called remote storage
|
|
203
|
-
assert.strictEqual(mockRemoteStorage.exists.mock.calls.length, 0);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("downloads when STORAGE_TYPE is s3", async () => {
|
|
207
|
-
mockProcess.env.STORAGE_TYPE = "s3";
|
|
208
|
-
mockRemoteStorage.exists = mock.fn(async () => true);
|
|
209
|
-
|
|
210
|
-
const download = new BundleDownloader(
|
|
211
|
-
mockStorageFactory,
|
|
212
|
-
mockFinder,
|
|
213
|
-
mockLogger,
|
|
214
|
-
mockExtractor,
|
|
215
|
-
mockProcess,
|
|
216
|
-
);
|
|
217
|
-
await download.initialize();
|
|
218
|
-
await download.download();
|
|
219
|
-
|
|
220
|
-
// Should have called remote storage
|
|
221
|
-
assert.strictEqual(mockRemoteStorage.exists.mock.calls.length, 1);
|
|
222
|
-
});
|
|
223
|
-
});
|
package/test/extractor.test.js
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import { test, describe, beforeEach, afterEach } from "node:test";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import fsPromises from "fs/promises";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
|
-
|
|
8
|
-
// Module under test
|
|
9
|
-
import { TarExtractor, ZipExtractor } from "../extractor.js";
|
|
10
|
-
|
|
11
|
-
describe("TarExtractor", () => {
|
|
12
|
-
let extractor;
|
|
13
|
-
let tempDir;
|
|
14
|
-
let fixturesDir;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
extractor = new TarExtractor(fsPromises, path);
|
|
18
|
-
|
|
19
|
-
// Setup directories
|
|
20
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
-
const __dirname = path.dirname(__filename);
|
|
22
|
-
fixturesDir = path.join(__dirname, "fixtures");
|
|
23
|
-
tempDir = path.join(__dirname, ".tmp-extractor-test");
|
|
24
|
-
|
|
25
|
-
// Clean up any existing temp directory
|
|
26
|
-
if (fs.existsSync(tempDir)) {
|
|
27
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
28
|
-
}
|
|
29
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
// Clean up temp directory
|
|
34
|
-
if (fs.existsSync(tempDir)) {
|
|
35
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe("constructor", () => {
|
|
40
|
-
test("creates TarExtractor with fs and path", () => {
|
|
41
|
-
const extractor = new TarExtractor(fsPromises, path);
|
|
42
|
-
|
|
43
|
-
assert.ok(extractor instanceof TarExtractor);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("validates fs parameter", () => {
|
|
47
|
-
assert.throws(() => new TarExtractor(), {
|
|
48
|
-
message: /fs dependency is required/,
|
|
49
|
-
});
|
|
50
|
-
assert.throws(() => new TarExtractor(null, path), {
|
|
51
|
-
message: /fs dependency is required/,
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("validates path parameter", () => {
|
|
56
|
-
assert.throws(() => new TarExtractor(fsPromises), {
|
|
57
|
-
message: /path dependency is required/,
|
|
58
|
-
});
|
|
59
|
-
assert.throws(() => new TarExtractor(fsPromises, null), {
|
|
60
|
-
message: /path dependency is required/,
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe("extract", () => {
|
|
66
|
-
test("extracts tar.gz archive to specified directory", async () => {
|
|
67
|
-
const tarPath = path.join(fixturesDir, "sample.tar.gz");
|
|
68
|
-
const outputDir = path.join(tempDir, "tar-output");
|
|
69
|
-
|
|
70
|
-
await extractor.extract(tarPath, outputDir);
|
|
71
|
-
|
|
72
|
-
// Verify extracted files exist
|
|
73
|
-
assert.ok(fs.existsSync(path.join(outputDir, "test.txt")));
|
|
74
|
-
assert.ok(fs.existsSync(path.join(outputDir, "nested.txt")));
|
|
75
|
-
assert.ok(fs.existsSync(path.join(outputDir, "subdir", "deep.txt")));
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("extracts files with correct content", async () => {
|
|
79
|
-
const tarPath = path.join(fixturesDir, "sample.tar.gz");
|
|
80
|
-
const outputDir = path.join(tempDir, "tar-content");
|
|
81
|
-
|
|
82
|
-
await extractor.extract(tarPath, outputDir);
|
|
83
|
-
|
|
84
|
-
// Verify file contents
|
|
85
|
-
const testContent = await fsPromises.readFile(
|
|
86
|
-
path.join(outputDir, "test.txt"),
|
|
87
|
-
"utf8",
|
|
88
|
-
);
|
|
89
|
-
assert.strictEqual(testContent.trim(), "Hello from root file");
|
|
90
|
-
|
|
91
|
-
const nestedContent = await fsPromises.readFile(
|
|
92
|
-
path.join(outputDir, "nested.txt"),
|
|
93
|
-
"utf8",
|
|
94
|
-
);
|
|
95
|
-
assert.strictEqual(nestedContent.trim(), "Nested content");
|
|
96
|
-
|
|
97
|
-
const deepContent = await fsPromises.readFile(
|
|
98
|
-
path.join(outputDir, "subdir", "deep.txt"),
|
|
99
|
-
"utf8",
|
|
100
|
-
);
|
|
101
|
-
assert.strictEqual(deepContent.trim(), "Deep file content");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("creates nested directory structure", async () => {
|
|
105
|
-
const tarPath = path.join(fixturesDir, "sample.tar.gz");
|
|
106
|
-
const outputDir = path.join(tempDir, "tar-structure");
|
|
107
|
-
|
|
108
|
-
await extractor.extract(tarPath, outputDir);
|
|
109
|
-
|
|
110
|
-
// Verify directory structure
|
|
111
|
-
const subdirPath = path.join(outputDir, "subdir");
|
|
112
|
-
assert.ok(fs.existsSync(subdirPath));
|
|
113
|
-
|
|
114
|
-
const stats = await fsPromises.stat(subdirPath);
|
|
115
|
-
assert.ok(stats.isDirectory());
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("handles non-existent output directory", async () => {
|
|
119
|
-
const tarPath = path.join(fixturesDir, "sample.tar.gz");
|
|
120
|
-
const outputDir = path.join(tempDir, "deep", "nested", "tar-output");
|
|
121
|
-
|
|
122
|
-
// Should not throw - should create directory structure
|
|
123
|
-
await extractor.extract(tarPath, outputDir);
|
|
124
|
-
|
|
125
|
-
assert.ok(fs.existsSync(path.join(outputDir, "test.txt")));
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe("ZipExtractor", () => {
|
|
131
|
-
let extractor;
|
|
132
|
-
let tempDir;
|
|
133
|
-
let fixturesDir;
|
|
134
|
-
|
|
135
|
-
beforeEach(() => {
|
|
136
|
-
extractor = new ZipExtractor(fsPromises, path);
|
|
137
|
-
|
|
138
|
-
// Setup directories
|
|
139
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
140
|
-
const __dirname = path.dirname(__filename);
|
|
141
|
-
fixturesDir = path.join(__dirname, "fixtures");
|
|
142
|
-
tempDir = path.join(__dirname, ".tmp-extractor-test");
|
|
143
|
-
|
|
144
|
-
// Clean up any existing temp directory
|
|
145
|
-
if (fs.existsSync(tempDir)) {
|
|
146
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
147
|
-
}
|
|
148
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
afterEach(() => {
|
|
152
|
-
// Clean up temp directory
|
|
153
|
-
if (fs.existsSync(tempDir)) {
|
|
154
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
describe("constructor", () => {
|
|
159
|
-
test("creates ZipExtractor with fs and path", () => {
|
|
160
|
-
const extractor = new ZipExtractor(fsPromises, path);
|
|
161
|
-
|
|
162
|
-
assert.ok(extractor instanceof ZipExtractor);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("validates fs parameter", () => {
|
|
166
|
-
assert.throws(() => new ZipExtractor(), {
|
|
167
|
-
message: /fs dependency is required/,
|
|
168
|
-
});
|
|
169
|
-
assert.throws(() => new ZipExtractor(null, path), {
|
|
170
|
-
message: /fs dependency is required/,
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("validates path parameter", () => {
|
|
175
|
-
assert.throws(() => new ZipExtractor(fsPromises), {
|
|
176
|
-
message: /path dependency is required/,
|
|
177
|
-
});
|
|
178
|
-
assert.throws(() => new ZipExtractor(fsPromises, null), {
|
|
179
|
-
message: /path dependency is required/,
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
describe("extract", () => {
|
|
185
|
-
test("extracts zip archive to specified directory", async () => {
|
|
186
|
-
const zipPath = path.join(fixturesDir, "sample.zip");
|
|
187
|
-
const outputDir = path.join(tempDir, "zip-output");
|
|
188
|
-
|
|
189
|
-
await extractor.extract(zipPath, outputDir);
|
|
190
|
-
|
|
191
|
-
// Verify extracted files exist
|
|
192
|
-
assert.ok(fs.existsSync(path.join(outputDir, "test.txt")));
|
|
193
|
-
assert.ok(fs.existsSync(path.join(outputDir, "nested.txt")));
|
|
194
|
-
assert.ok(fs.existsSync(path.join(outputDir, "subdir", "deep.txt")));
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("extracts files with correct content", async () => {
|
|
198
|
-
const zipPath = path.join(fixturesDir, "sample.zip");
|
|
199
|
-
const outputDir = path.join(tempDir, "zip-content");
|
|
200
|
-
|
|
201
|
-
await extractor.extract(zipPath, outputDir);
|
|
202
|
-
|
|
203
|
-
// Verify file contents
|
|
204
|
-
const testContent = await fsPromises.readFile(
|
|
205
|
-
path.join(outputDir, "test.txt"),
|
|
206
|
-
"utf8",
|
|
207
|
-
);
|
|
208
|
-
assert.strictEqual(testContent.trim(), "Hello from root file");
|
|
209
|
-
|
|
210
|
-
const nestedContent = await fsPromises.readFile(
|
|
211
|
-
path.join(outputDir, "nested.txt"),
|
|
212
|
-
"utf8",
|
|
213
|
-
);
|
|
214
|
-
assert.strictEqual(nestedContent.trim(), "Nested content");
|
|
215
|
-
|
|
216
|
-
const deepContent = await fsPromises.readFile(
|
|
217
|
-
path.join(outputDir, "subdir", "deep.txt"),
|
|
218
|
-
"utf8",
|
|
219
|
-
);
|
|
220
|
-
assert.strictEqual(deepContent.trim(), "Deep file content");
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test("creates nested directory structure", async () => {
|
|
224
|
-
const zipPath = path.join(fixturesDir, "sample.zip");
|
|
225
|
-
const outputDir = path.join(tempDir, "zip-structure");
|
|
226
|
-
|
|
227
|
-
await extractor.extract(zipPath, outputDir);
|
|
228
|
-
|
|
229
|
-
// Verify directory structure
|
|
230
|
-
const subdirPath = path.join(outputDir, "subdir");
|
|
231
|
-
assert.ok(fs.existsSync(subdirPath));
|
|
232
|
-
|
|
233
|
-
const stats = await fsPromises.stat(subdirPath);
|
|
234
|
-
assert.ok(stats.isDirectory());
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test("handles non-existent output directory", async () => {
|
|
238
|
-
const zipPath = path.join(fixturesDir, "sample.zip");
|
|
239
|
-
const outputDir = path.join(tempDir, "deep", "nested", "zip-output");
|
|
240
|
-
|
|
241
|
-
// Should not throw - should create directory structure
|
|
242
|
-
await extractor.extract(zipPath, outputDir);
|
|
243
|
-
|
|
244
|
-
assert.ok(fs.existsSync(path.join(outputDir, "test.txt")));
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("throws error for invalid zip file", async () => {
|
|
248
|
-
const invalidZipPath = path.join(tempDir, "invalid.zip");
|
|
249
|
-
|
|
250
|
-
// Create an invalid zip file (just some random bytes)
|
|
251
|
-
await fsPromises.writeFile(invalidZipPath, Buffer.from([0, 1, 2, 3, 4]));
|
|
252
|
-
|
|
253
|
-
const outputDir = path.join(tempDir, "invalid-output");
|
|
254
|
-
|
|
255
|
-
await assert.rejects(
|
|
256
|
-
async () => {
|
|
257
|
-
await extractor.extract(invalidZipPath, outputDir);
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
message: /Invalid ZIP file/,
|
|
261
|
-
},
|
|
262
|
-
);
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
describe("format comparison", () => {
|
|
267
|
-
test("both extractors produce identical file content", async () => {
|
|
268
|
-
const tarPath = path.join(fixturesDir, "sample.tar.gz");
|
|
269
|
-
const zipPath = path.join(fixturesDir, "sample.zip");
|
|
270
|
-
const tarOutput = path.join(tempDir, "tar-compare");
|
|
271
|
-
const zipOutput = path.join(tempDir, "zip-compare");
|
|
272
|
-
|
|
273
|
-
const tarExtractor = new TarExtractor(fsPromises, path);
|
|
274
|
-
const zipExtractor = new ZipExtractor(fsPromises, path);
|
|
275
|
-
|
|
276
|
-
await tarExtractor.extract(tarPath, tarOutput);
|
|
277
|
-
await zipExtractor.extract(zipPath, zipOutput);
|
|
278
|
-
|
|
279
|
-
// Compare file contents
|
|
280
|
-
const tarTest = await fsPromises.readFile(
|
|
281
|
-
path.join(tarOutput, "test.txt"),
|
|
282
|
-
"utf8",
|
|
283
|
-
);
|
|
284
|
-
const zipTest = await fsPromises.readFile(
|
|
285
|
-
path.join(zipOutput, "test.txt"),
|
|
286
|
-
"utf8",
|
|
287
|
-
);
|
|
288
|
-
assert.strictEqual(tarTest, zipTest);
|
|
289
|
-
|
|
290
|
-
const tarNested = await fsPromises.readFile(
|
|
291
|
-
path.join(tarOutput, "nested.txt"),
|
|
292
|
-
"utf8",
|
|
293
|
-
);
|
|
294
|
-
const zipNested = await fsPromises.readFile(
|
|
295
|
-
path.join(zipOutput, "nested.txt"),
|
|
296
|
-
"utf8",
|
|
297
|
-
);
|
|
298
|
-
assert.strictEqual(tarNested, zipNested);
|
|
299
|
-
|
|
300
|
-
const tarDeep = await fsPromises.readFile(
|
|
301
|
-
path.join(tarOutput, "subdir", "deep.txt"),
|
|
302
|
-
"utf8",
|
|
303
|
-
);
|
|
304
|
-
const zipDeep = await fsPromises.readFile(
|
|
305
|
-
path.join(zipOutput, "subdir", "deep.txt"),
|
|
306
|
-
"utf8",
|
|
307
|
-
);
|
|
308
|
-
assert.strictEqual(tarDeep, zipDeep);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test("both extractors create identical directory structure", async () => {
|
|
312
|
-
const tarPath = path.join(fixturesDir, "sample.tar.gz");
|
|
313
|
-
const zipPath = path.join(fixturesDir, "sample.zip");
|
|
314
|
-
const tarOutput = path.join(tempDir, "tar-struct");
|
|
315
|
-
const zipOutput = path.join(tempDir, "zip-struct");
|
|
316
|
-
|
|
317
|
-
const tarExtractor = new TarExtractor(fsPromises, path);
|
|
318
|
-
const zipExtractor = new ZipExtractor(fsPromises, path);
|
|
319
|
-
|
|
320
|
-
await tarExtractor.extract(tarPath, tarOutput);
|
|
321
|
-
await zipExtractor.extract(zipPath, zipOutput);
|
|
322
|
-
|
|
323
|
-
// Check that both have the subdir
|
|
324
|
-
assert.ok(fs.existsSync(path.join(tarOutput, "subdir")));
|
|
325
|
-
assert.ok(fs.existsSync(path.join(zipOutput, "subdir")));
|
|
326
|
-
|
|
327
|
-
// Check that both subdirs are directories
|
|
328
|
-
const tarSubdirStats = await fsPromises.stat(
|
|
329
|
-
path.join(tarOutput, "subdir"),
|
|
330
|
-
);
|
|
331
|
-
const zipSubdirStats = await fsPromises.stat(
|
|
332
|
-
path.join(zipOutput, "subdir"),
|
|
333
|
-
);
|
|
334
|
-
assert.ok(tarSubdirStats.isDirectory());
|
|
335
|
-
assert.ok(zipSubdirStats.isDirectory());
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
});
|