busy-cli 0.1.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/README.md +129 -0
- package/dist/builders/context.d.ts +50 -0
- package/dist/builders/context.d.ts.map +1 -0
- package/dist/builders/context.js +190 -0
- package/dist/cache/index.d.ts +100 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +270 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +463 -0
- package/dist/commands/package.d.ts +96 -0
- package/dist/commands/package.d.ts.map +1 -0
- package/dist/commands/package.js +285 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/loader.d.ts +6 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +361 -0
- package/dist/merge.d.ts +16 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +102 -0
- package/dist/package/manifest.d.ts +59 -0
- package/dist/package/manifest.d.ts.map +1 -0
- package/dist/package/manifest.js +265 -0
- package/dist/parser.d.ts +28 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +220 -0
- package/dist/parsers/frontmatter.d.ts +14 -0
- package/dist/parsers/frontmatter.d.ts.map +1 -0
- package/dist/parsers/frontmatter.js +110 -0
- package/dist/parsers/imports.d.ts +48 -0
- package/dist/parsers/imports.d.ts.map +1 -0
- package/dist/parsers/imports.js +147 -0
- package/dist/parsers/links.d.ts +12 -0
- package/dist/parsers/links.d.ts.map +1 -0
- package/dist/parsers/links.js +79 -0
- package/dist/parsers/localdefs.d.ts +6 -0
- package/dist/parsers/localdefs.d.ts.map +1 -0
- package/dist/parsers/localdefs.js +132 -0
- package/dist/parsers/operations.d.ts +32 -0
- package/dist/parsers/operations.d.ts.map +1 -0
- package/dist/parsers/operations.js +313 -0
- package/dist/parsers/sections.d.ts +15 -0
- package/dist/parsers/sections.d.ts.map +1 -0
- package/dist/parsers/sections.js +173 -0
- package/dist/parsers/tools.d.ts +30 -0
- package/dist/parsers/tools.d.ts.map +1 -0
- package/dist/parsers/tools.js +178 -0
- package/dist/parsers/triggers.d.ts +35 -0
- package/dist/parsers/triggers.d.ts.map +1 -0
- package/dist/parsers/triggers.js +219 -0
- package/dist/providers/base.d.ts +60 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +34 -0
- package/dist/providers/github.d.ts +18 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +109 -0
- package/dist/providers/gitlab.d.ts +18 -0
- package/dist/providers/gitlab.d.ts.map +1 -0
- package/dist/providers/gitlab.js +101 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +17 -0
- package/dist/providers/local.d.ts +31 -0
- package/dist/providers/local.d.ts.map +1 -0
- package/dist/providers/local.js +116 -0
- package/dist/providers/url.d.ts +16 -0
- package/dist/providers/url.d.ts.map +1 -0
- package/dist/providers/url.js +45 -0
- package/dist/registry/index.d.ts +99 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +320 -0
- package/dist/types/schema.d.ts +3259 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +258 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +23 -0
- package/dist/utils/slugify.d.ts +14 -0
- package/dist/utils/slugify.d.ts.map +1 -0
- package/dist/utils/slugify.js +28 -0
- package/package.json +61 -0
- package/src/__tests__/cache.test.ts +393 -0
- package/src/__tests__/cli-package.test.ts +667 -0
- package/src/__tests__/fixtures/automated-workflow.busy.md +84 -0
- package/src/__tests__/fixtures/concept.busy.md +30 -0
- package/src/__tests__/fixtures/document.busy.md +44 -0
- package/src/__tests__/fixtures/simple-operation.busy.md +45 -0
- package/src/__tests__/fixtures/tool-document.busy.md +71 -0
- package/src/__tests__/fixtures/tool.busy.md +54 -0
- package/src/__tests__/imports.test.ts +244 -0
- package/src/__tests__/integration.test.ts +432 -0
- package/src/__tests__/operations.test.ts +408 -0
- package/src/__tests__/package-manifest.test.ts +455 -0
- package/src/__tests__/providers.test.ts +672 -0
- package/src/__tests__/registry.test.ts +402 -0
- package/src/__tests__/schema.test.ts +467 -0
- package/src/__tests__/tools.test.ts +376 -0
- package/src/__tests__/triggers.test.ts +312 -0
- package/src/builders/context.ts +294 -0
- package/src/cache/index.ts +312 -0
- package/src/cli/index.ts +514 -0
- package/src/commands/package.ts +392 -0
- package/src/index.ts +46 -0
- package/src/loader.ts +474 -0
- package/src/merge.ts +126 -0
- package/src/package/manifest.ts +349 -0
- package/src/parser.ts +278 -0
- package/src/parsers/frontmatter.ts +135 -0
- package/src/parsers/imports.ts +196 -0
- package/src/parsers/links.ts +108 -0
- package/src/parsers/localdefs.ts +166 -0
- package/src/parsers/operations.ts +404 -0
- package/src/parsers/sections.ts +230 -0
- package/src/parsers/tools.ts +215 -0
- package/src/parsers/triggers.ts +252 -0
- package/src/providers/base.ts +77 -0
- package/src/providers/github.ts +129 -0
- package/src/providers/gitlab.ts +121 -0
- package/src/providers/index.ts +25 -0
- package/src/providers/local.ts +129 -0
- package/src/providers/url.ts +56 -0
- package/src/registry/index.ts +408 -0
- package/src/types/schema.ts +369 -0
- package/src/utils/logger.ts +25 -0
- package/src/utils/slugify.ts +31 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Manager Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for .libraries/ cache management.
|
|
5
|
+
* TDD approach for package manager implementation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
import { promises as fs } from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import * as os from 'node:os';
|
|
12
|
+
import * as crypto from 'node:crypto';
|
|
13
|
+
|
|
14
|
+
// We'll implement these after tests
|
|
15
|
+
import {
|
|
16
|
+
CacheManager,
|
|
17
|
+
CachedFile,
|
|
18
|
+
deriveCachePath,
|
|
19
|
+
calculateIntegrity,
|
|
20
|
+
verifyIntegrity,
|
|
21
|
+
} from '../cache/index.js';
|
|
22
|
+
import type { ParsedURL } from '../providers/base.js';
|
|
23
|
+
|
|
24
|
+
describe('CacheManager', () => {
|
|
25
|
+
let tempDir: string;
|
|
26
|
+
let cacheManager: CacheManager;
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
// Create a temp directory for each test
|
|
30
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'busy-cache-test-'));
|
|
31
|
+
cacheManager = new CacheManager(tempDir);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(async () => {
|
|
35
|
+
// Clean up temp directory
|
|
36
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('initialization', () => {
|
|
40
|
+
it('should create .libraries directory on init', async () => {
|
|
41
|
+
await cacheManager.init();
|
|
42
|
+
|
|
43
|
+
const librariesPath = path.join(tempDir, '.libraries');
|
|
44
|
+
const exists = await fs.stat(librariesPath).then(() => true).catch(() => false);
|
|
45
|
+
expect(exists).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should not fail if .libraries already exists', async () => {
|
|
49
|
+
const librariesPath = path.join(tempDir, '.libraries');
|
|
50
|
+
await fs.mkdir(librariesPath, { recursive: true });
|
|
51
|
+
|
|
52
|
+
await expect(cacheManager.init()).resolves.not.toThrow();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should return workspace root path', () => {
|
|
56
|
+
expect(cacheManager.workspaceRoot).toBe(tempDir);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return libraries path', () => {
|
|
60
|
+
expect(cacheManager.librariesPath).toBe(path.join(tempDir, '.libraries'));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('save', () => {
|
|
65
|
+
beforeEach(async () => {
|
|
66
|
+
await cacheManager.init();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should save content to cache', async () => {
|
|
70
|
+
const content = '# Test Content\n\nThis is test content.';
|
|
71
|
+
const cachePath = 'core/test.md';
|
|
72
|
+
|
|
73
|
+
const result = await cacheManager.save(cachePath, content);
|
|
74
|
+
|
|
75
|
+
expect(result.path).toBe(cachePath);
|
|
76
|
+
expect(result.fullPath).toBe(path.join(tempDir, '.libraries', cachePath));
|
|
77
|
+
|
|
78
|
+
const savedContent = await fs.readFile(result.fullPath, 'utf-8');
|
|
79
|
+
expect(savedContent).toBe(content);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should create nested directories', async () => {
|
|
83
|
+
const content = 'content';
|
|
84
|
+
const cachePath = 'deep/nested/path/file.md';
|
|
85
|
+
|
|
86
|
+
await cacheManager.save(cachePath, content);
|
|
87
|
+
|
|
88
|
+
const fullPath = path.join(tempDir, '.libraries', cachePath);
|
|
89
|
+
const savedContent = await fs.readFile(fullPath, 'utf-8');
|
|
90
|
+
expect(savedContent).toBe(content);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should calculate and return integrity hash', async () => {
|
|
94
|
+
const content = 'test content for hash';
|
|
95
|
+
const cachePath = 'test.md';
|
|
96
|
+
|
|
97
|
+
const result = await cacheManager.save(cachePath, content);
|
|
98
|
+
|
|
99
|
+
expect(result.integrity).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should overwrite existing file', async () => {
|
|
103
|
+
const cachePath = 'test.md';
|
|
104
|
+
|
|
105
|
+
await cacheManager.save(cachePath, 'original content');
|
|
106
|
+
await cacheManager.save(cachePath, 'new content');
|
|
107
|
+
|
|
108
|
+
const fullPath = path.join(tempDir, '.libraries', cachePath);
|
|
109
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
110
|
+
expect(content).toBe('new content');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('read', () => {
|
|
115
|
+
beforeEach(async () => {
|
|
116
|
+
await cacheManager.init();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should read content from cache', async () => {
|
|
120
|
+
const content = 'cached content';
|
|
121
|
+
const cachePath = 'test.md';
|
|
122
|
+
|
|
123
|
+
await cacheManager.save(cachePath, content);
|
|
124
|
+
const result = await cacheManager.read(cachePath);
|
|
125
|
+
|
|
126
|
+
expect(result).toBe(content);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should throw if file does not exist', async () => {
|
|
130
|
+
await expect(cacheManager.read('nonexistent.md')).rejects.toThrow();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('exists', () => {
|
|
135
|
+
beforeEach(async () => {
|
|
136
|
+
await cacheManager.init();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should return true for existing file', async () => {
|
|
140
|
+
await cacheManager.save('test.md', 'content');
|
|
141
|
+
expect(await cacheManager.exists('test.md')).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should return false for non-existing file', async () => {
|
|
145
|
+
expect(await cacheManager.exists('nonexistent.md')).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('delete', () => {
|
|
150
|
+
beforeEach(async () => {
|
|
151
|
+
await cacheManager.init();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should delete cached file', async () => {
|
|
155
|
+
await cacheManager.save('test.md', 'content');
|
|
156
|
+
await cacheManager.delete('test.md');
|
|
157
|
+
|
|
158
|
+
expect(await cacheManager.exists('test.md')).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should not throw if file does not exist', async () => {
|
|
162
|
+
await expect(cacheManager.delete('nonexistent.md')).resolves.not.toThrow();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should clean up empty parent directories', async () => {
|
|
166
|
+
await cacheManager.save('deep/nested/file.md', 'content');
|
|
167
|
+
await cacheManager.delete('deep/nested/file.md');
|
|
168
|
+
|
|
169
|
+
const nestedDir = path.join(tempDir, '.libraries', 'deep', 'nested');
|
|
170
|
+
const exists = await fs.stat(nestedDir).then(() => true).catch(() => false);
|
|
171
|
+
expect(exists).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('list', () => {
|
|
176
|
+
beforeEach(async () => {
|
|
177
|
+
await cacheManager.init();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should list all cached files', async () => {
|
|
181
|
+
await cacheManager.save('file1.md', 'content1');
|
|
182
|
+
await cacheManager.save('dir/file2.md', 'content2');
|
|
183
|
+
await cacheManager.save('dir/subdir/file3.md', 'content3');
|
|
184
|
+
|
|
185
|
+
const files = await cacheManager.list();
|
|
186
|
+
|
|
187
|
+
expect(files).toHaveLength(3);
|
|
188
|
+
expect(files).toContain('file1.md');
|
|
189
|
+
expect(files).toContain('dir/file2.md');
|
|
190
|
+
expect(files).toContain('dir/subdir/file3.md');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should return empty array for empty cache', async () => {
|
|
194
|
+
const files = await cacheManager.list();
|
|
195
|
+
expect(files).toHaveLength(0);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('clean', () => {
|
|
200
|
+
beforeEach(async () => {
|
|
201
|
+
await cacheManager.init();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should remove all cached files', async () => {
|
|
205
|
+
await cacheManager.save('file1.md', 'content1');
|
|
206
|
+
await cacheManager.save('dir/file2.md', 'content2');
|
|
207
|
+
|
|
208
|
+
const removed = await cacheManager.clean();
|
|
209
|
+
|
|
210
|
+
expect(removed).toBe(2);
|
|
211
|
+
const files = await cacheManager.list();
|
|
212
|
+
expect(files).toHaveLength(0);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should return 0 for empty cache', async () => {
|
|
216
|
+
const removed = await cacheManager.clean();
|
|
217
|
+
expect(removed).toBe(0);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('verifyIntegrity', () => {
|
|
222
|
+
beforeEach(async () => {
|
|
223
|
+
await cacheManager.init();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should return true for matching integrity', async () => {
|
|
227
|
+
const content = 'test content';
|
|
228
|
+
const result = await cacheManager.save('test.md', content);
|
|
229
|
+
|
|
230
|
+
const isValid = await cacheManager.verifyIntegrity('test.md', result.integrity);
|
|
231
|
+
expect(isValid).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should return false for mismatched integrity', async () => {
|
|
235
|
+
await cacheManager.save('test.md', 'content');
|
|
236
|
+
|
|
237
|
+
const isValid = await cacheManager.verifyIntegrity('test.md', 'sha256:invalid');
|
|
238
|
+
expect(isValid).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should return false for non-existent file', async () => {
|
|
242
|
+
const isValid = await cacheManager.verifyIntegrity('nonexistent.md', 'sha256:any');
|
|
243
|
+
expect(isValid).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('getFullPath', () => {
|
|
248
|
+
it('should return full path for cache path', () => {
|
|
249
|
+
const fullPath = cacheManager.getFullPath('core/file.md');
|
|
250
|
+
expect(fullPath).toBe(path.join(tempDir, '.libraries', 'core/file.md'));
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('getCachePath', () => {
|
|
255
|
+
it('should return cache path from full path', () => {
|
|
256
|
+
const fullPath = path.join(tempDir, '.libraries', 'core/file.md');
|
|
257
|
+
const cachePath = cacheManager.getCachePath(fullPath);
|
|
258
|
+
expect(cachePath).toBe('core/file.md');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should return null for paths outside cache', () => {
|
|
262
|
+
const outsidePath = '/some/other/path/file.md';
|
|
263
|
+
const cachePath = cacheManager.getCachePath(outsidePath);
|
|
264
|
+
expect(cachePath).toBeNull();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('deriveCachePath', () => {
|
|
270
|
+
describe('GitHub URLs', () => {
|
|
271
|
+
it('should derive path from GitHub blob URL', () => {
|
|
272
|
+
const parsed: ParsedURL = {
|
|
273
|
+
provider: 'github',
|
|
274
|
+
org: 'Bravo-Tensor',
|
|
275
|
+
repo: 'busy-lang',
|
|
276
|
+
ref: 'v0.3.1',
|
|
277
|
+
path: 'busy-v2/core/prompt.md',
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const cachePath = deriveCachePath(parsed);
|
|
281
|
+
expect(cachePath).toBe('busy-lang/busy-v2/core/prompt.md');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should handle paths with directories', () => {
|
|
285
|
+
const parsed: ParsedURL = {
|
|
286
|
+
provider: 'github',
|
|
287
|
+
org: 'org',
|
|
288
|
+
repo: 'repo',
|
|
289
|
+
ref: 'main',
|
|
290
|
+
path: 'src/deep/nested/file.md',
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const cachePath = deriveCachePath(parsed);
|
|
294
|
+
expect(cachePath).toBe('repo/src/deep/nested/file.md');
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('GitLab URLs', () => {
|
|
299
|
+
it('should derive path from GitLab URL', () => {
|
|
300
|
+
const parsed: ParsedURL = {
|
|
301
|
+
provider: 'gitlab',
|
|
302
|
+
org: 'group',
|
|
303
|
+
repo: 'project',
|
|
304
|
+
ref: 'main',
|
|
305
|
+
path: 'docs/file.md',
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const cachePath = deriveCachePath(parsed);
|
|
309
|
+
expect(cachePath).toBe('project/docs/file.md');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe('Generic URLs', () => {
|
|
314
|
+
it('should derive path from generic URL', () => {
|
|
315
|
+
const parsed: ParsedURL = {
|
|
316
|
+
provider: 'url',
|
|
317
|
+
path: '/path/to/file.md',
|
|
318
|
+
rawUrl: 'https://example.com/path/to/file.md',
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const cachePath = deriveCachePath(parsed);
|
|
322
|
+
expect(cachePath).toBe('example.com/path/to/file.md');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should handle URL with query params in path', () => {
|
|
326
|
+
const parsed: ParsedURL = {
|
|
327
|
+
provider: 'url',
|
|
328
|
+
path: '/file.md',
|
|
329
|
+
rawUrl: 'https://cdn.example.org/file.md',
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const cachePath = deriveCachePath(parsed);
|
|
333
|
+
expect(cachePath).toBe('cdn.example.org/file.md');
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('calculateIntegrity', () => {
|
|
339
|
+
it('should calculate sha256 hash of content', () => {
|
|
340
|
+
const content = 'test content';
|
|
341
|
+
const hash = calculateIntegrity(content);
|
|
342
|
+
|
|
343
|
+
expect(hash).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('should return same hash for same content', () => {
|
|
347
|
+
const content = 'test content';
|
|
348
|
+
const hash1 = calculateIntegrity(content);
|
|
349
|
+
const hash2 = calculateIntegrity(content);
|
|
350
|
+
|
|
351
|
+
expect(hash1).toBe(hash2);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should return different hash for different content', () => {
|
|
355
|
+
const hash1 = calculateIntegrity('content1');
|
|
356
|
+
const hash2 = calculateIntegrity('content2');
|
|
357
|
+
|
|
358
|
+
expect(hash1).not.toBe(hash2);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should handle empty string', () => {
|
|
362
|
+
const hash = calculateIntegrity('');
|
|
363
|
+
expect(hash).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should handle unicode content', () => {
|
|
367
|
+
const hash = calculateIntegrity('こんにちは 世界');
|
|
368
|
+
expect(hash).toMatch(/^sha256:[a-f0-9]{64}$/);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('verifyIntegrity', () => {
|
|
373
|
+
it('should return true for matching hash', () => {
|
|
374
|
+
const content = 'test content';
|
|
375
|
+
const hash = calculateIntegrity(content);
|
|
376
|
+
|
|
377
|
+
expect(verifyIntegrity(content, hash)).toBe(true);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should return false for mismatched hash', () => {
|
|
381
|
+
const content = 'test content';
|
|
382
|
+
const wrongHash = 'sha256:0000000000000000000000000000000000000000000000000000000000000000';
|
|
383
|
+
|
|
384
|
+
expect(verifyIntegrity(content, wrongHash)).toBe(false);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('should return false for invalid hash format', () => {
|
|
388
|
+
const content = 'test content';
|
|
389
|
+
|
|
390
|
+
expect(verifyIntegrity(content, 'invalid')).toBe(false);
|
|
391
|
+
expect(verifyIntegrity(content, 'md5:abc123')).toBe(false);
|
|
392
|
+
});
|
|
393
|
+
});
|