@cleocode/core 2026.4.11 → 2026.4.13
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/dist/codebase-map/analyzers/architecture.d.ts.map +1 -1
- package/dist/codebase-map/analyzers/architecture.js +0 -1
- package/dist/codebase-map/analyzers/architecture.js.map +1 -1
- package/dist/conduit/local-transport.d.ts +18 -8
- package/dist/conduit/local-transport.d.ts.map +1 -1
- package/dist/conduit/local-transport.js +23 -13
- package/dist/conduit/local-transport.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -1
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +19 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.js +175 -68950
- package/dist/index.js.map +1 -7
- package/dist/init.d.ts +1 -2
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1 -2
- package/dist/init.js.map +1 -1
- package/dist/internal.d.ts +8 -3
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +13 -6
- package/dist/internal.js.map +1 -1
- package/dist/memory/learnings.d.ts +2 -2
- package/dist/memory/patterns.d.ts +6 -6
- package/dist/output.d.ts +32 -11
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +67 -67
- package/dist/output.js.map +1 -1
- package/dist/paths.js +80 -14
- package/dist/paths.js.map +1 -1
- package/dist/skills/dynamic-skill-generator.d.ts +0 -2
- package/dist/skills/dynamic-skill-generator.d.ts.map +1 -1
- package/dist/skills/dynamic-skill-generator.js.map +1 -1
- package/dist/store/agent-registry-accessor.d.ts +203 -12
- package/dist/store/agent-registry-accessor.d.ts.map +1 -1
- package/dist/store/agent-registry-accessor.js +618 -100
- package/dist/store/agent-registry-accessor.js.map +1 -1
- package/dist/store/api-key-kdf.d.ts +73 -0
- package/dist/store/api-key-kdf.d.ts.map +1 -0
- package/dist/store/api-key-kdf.js +84 -0
- package/dist/store/api-key-kdf.js.map +1 -0
- package/dist/store/cleanup-legacy.js +171 -0
- package/dist/store/cleanup-legacy.js.map +1 -0
- package/dist/store/conduit-sqlite.d.ts +184 -0
- package/dist/store/conduit-sqlite.d.ts.map +1 -0
- package/dist/store/conduit-sqlite.js +570 -0
- package/dist/store/conduit-sqlite.js.map +1 -0
- package/dist/store/global-salt.d.ts +78 -0
- package/dist/store/global-salt.d.ts.map +1 -0
- package/dist/store/global-salt.js +147 -0
- package/dist/store/global-salt.js.map +1 -0
- package/dist/store/migrate-signaldock-to-conduit.d.ts +81 -0
- package/dist/store/migrate-signaldock-to-conduit.d.ts.map +1 -0
- package/dist/store/migrate-signaldock-to-conduit.js +555 -0
- package/dist/store/migrate-signaldock-to-conduit.js.map +1 -0
- package/dist/store/nexus-sqlite.js +28 -3
- package/dist/store/nexus-sqlite.js.map +1 -1
- package/dist/store/signaldock-sqlite.d.ts +122 -19
- package/dist/store/signaldock-sqlite.d.ts.map +1 -1
- package/dist/store/signaldock-sqlite.js +401 -251
- package/dist/store/signaldock-sqlite.js.map +1 -1
- package/dist/store/sqlite-backup.js +122 -4
- package/dist/store/sqlite-backup.js.map +1 -1
- package/dist/system/backup.d.ts +0 -26
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/runtime.d.ts +0 -2
- package/dist/system/runtime.d.ts.map +1 -1
- package/dist/system/runtime.js +3 -3
- package/dist/system/runtime.js.map +1 -1
- package/dist/tasks/add.d.ts +1 -1
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +98 -23
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +4 -1
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/find.d.ts.map +1 -1
- package/dist/tasks/find.js +4 -1
- package/dist/tasks/find.js.map +1 -1
- package/dist/tasks/labels.d.ts.map +1 -1
- package/dist/tasks/labels.js +4 -1
- package/dist/tasks/labels.js.map +1 -1
- package/dist/tasks/relates.d.ts.map +1 -1
- package/dist/tasks/relates.js +16 -4
- package/dist/tasks/relates.js.map +1 -1
- package/dist/tasks/show.d.ts.map +1 -1
- package/dist/tasks/show.js +4 -1
- package/dist/tasks/show.js.map +1 -1
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +32 -6
- package/dist/tasks/update.js.map +1 -1
- package/dist/validation/engine.d.ts.map +1 -1
- package/dist/validation/engine.js +16 -4
- package/dist/validation/engine.js.map +1 -1
- package/dist/validation/param-utils.d.ts +5 -3
- package/dist/validation/param-utils.d.ts.map +1 -1
- package/dist/validation/param-utils.js +8 -6
- package/dist/validation/param-utils.js.map +1 -1
- package/dist/validation/protocols/_shared.d.ts.map +1 -1
- package/dist/validation/protocols/_shared.js +13 -6
- package/dist/validation/protocols/_shared.js.map +1 -1
- package/package.json +9 -7
- package/src/adapters/__tests__/manager.test.ts +0 -1
- package/src/codebase-map/analyzers/architecture.ts +0 -1
- package/src/conduit/__tests__/local-credential-flow.test.ts +20 -18
- package/src/conduit/__tests__/local-transport.test.ts +14 -12
- package/src/conduit/local-transport.ts +23 -13
- package/src/config.ts +0 -1
- package/src/errors.ts +24 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.ts +2 -5
- package/src/init.ts +1 -2
- package/src/internal.ts +96 -2
- package/src/lifecycle/cant/lifecycle-rcasd.cant +133 -0
- package/src/memory/__tests__/engine-compat.test.ts +2 -2
- package/src/memory/__tests__/pipeline-manifest-sqlite.test.ts +4 -4
- package/src/observability/__tests__/index.test.ts +4 -4
- package/src/observability/__tests__/log-filter.test.ts +4 -4
- package/src/output.ts +73 -75
- package/src/sessions/__tests__/session-grade.integration.test.ts +1 -1
- package/src/sessions/__tests__/session-grade.test.ts +2 -2
- package/src/skills/__tests__/dynamic-skill-generator.test.ts +0 -2
- package/src/skills/dynamic-skill-generator.ts +0 -2
- package/src/store/__tests__/agent-registry-accessor.test.ts +807 -0
- package/src/store/__tests__/api-key-kdf.test.ts +113 -0
- package/src/store/__tests__/backup-crypto.test.ts +101 -0
- package/src/store/__tests__/backup-pack.test.ts +491 -0
- package/src/store/__tests__/backup-unpack.test.ts +298 -0
- package/src/store/__tests__/conduit-sqlite.test.ts +413 -0
- package/src/store/__tests__/global-salt.test.ts +195 -0
- package/src/store/__tests__/migrate-signaldock-to-conduit.test.ts +715 -0
- package/src/store/__tests__/regenerators.test.ts +234 -0
- package/src/store/__tests__/restore-conflict-report.test.ts +274 -0
- package/src/store/__tests__/restore-json-merge.test.ts +521 -0
- package/src/store/__tests__/signaldock-sqlite.test.ts +652 -0
- package/src/store/__tests__/sqlite-backup-global.test.ts +307 -3
- package/src/store/__tests__/sqlite-backup.test.ts +5 -1
- package/src/store/__tests__/t310-integration.test.ts +1150 -0
- package/src/store/__tests__/t310-readiness.test.ts +111 -0
- package/src/store/__tests__/t311-integration.test.ts +661 -0
- package/src/store/agent-registry-accessor.ts +847 -140
- package/src/store/api-key-kdf.ts +104 -0
- package/src/store/backup-crypto.ts +209 -0
- package/src/store/backup-pack.ts +739 -0
- package/src/store/backup-unpack.ts +583 -0
- package/src/store/conduit-sqlite.ts +655 -0
- package/src/store/global-salt.ts +175 -0
- package/src/store/migrate-signaldock-to-conduit.ts +669 -0
- package/src/store/regenerators.ts +243 -0
- package/src/store/restore-conflict-report.ts +317 -0
- package/src/store/restore-json-merge.ts +653 -0
- package/src/store/signaldock-sqlite.ts +431 -254
- package/src/store/sqlite-backup.ts +185 -10
- package/src/store/t310-readiness.ts +119 -0
- package/src/system/backup.ts +2 -62
- package/src/system/runtime.ts +4 -6
- package/src/tasks/__tests__/error-hints.test.ts +256 -0
- package/src/tasks/add.ts +99 -9
- package/src/tasks/complete.ts +4 -1
- package/src/tasks/find.ts +4 -1
- package/src/tasks/labels.ts +4 -1
- package/src/tasks/relates.ts +16 -4
- package/src/tasks/show.ts +4 -1
- package/src/tasks/update.ts +32 -3
- package/src/validation/__tests__/error-hints.test.ts +97 -0
- package/src/validation/engine.ts +16 -1
- package/src/validation/param-utils.ts +10 -7
- package/src/validation/protocols/_shared.ts +14 -6
- package/src/validation/protocols/cant/architecture-decision.cant +80 -0
- package/src/validation/protocols/cant/artifact-publish.cant +95 -0
- package/src/validation/protocols/cant/consensus.cant +74 -0
- package/src/validation/protocols/cant/contribution.cant +82 -0
- package/src/validation/protocols/cant/decomposition.cant +92 -0
- package/src/validation/protocols/cant/implementation.cant +67 -0
- package/src/validation/protocols/cant/provenance.cant +88 -0
- package/src/validation/protocols/cant/release.cant +96 -0
- package/src/validation/protocols/cant/research.cant +66 -0
- package/src/validation/protocols/cant/specification.cant +67 -0
- package/src/validation/protocols/cant/testing.cant +88 -0
- package/src/validation/protocols/cant/validation.cant +65 -0
- package/src/validation/protocols/protocols-markdown/decomposition.md +0 -4
- package/templates/config.template.json +0 -1
- package/templates/global-config.template.json +0 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for backup-unpack.ts (T350).
|
|
3
|
+
*
|
|
4
|
+
* Covers: unpack of unencrypted and encrypted bundles, all verification
|
|
5
|
+
* layers, BundleError codes (70–75), staging dir management, and manifest
|
|
6
|
+
* field correctness.
|
|
7
|
+
*
|
|
8
|
+
* Uses real node:sqlite DatabaseSync to seed minimal test databases.
|
|
9
|
+
* All filesystem interactions occur in temp directories; the real user's
|
|
10
|
+
* project root is never touched.
|
|
11
|
+
*
|
|
12
|
+
* @task T350
|
|
13
|
+
* @epic T311
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import type { DatabaseSync as _DatabaseSyncType } from 'node:sqlite';
|
|
21
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
22
|
+
import { packBundle } from '../backup-pack.js';
|
|
23
|
+
import { BundleError, cleanupStaging, unpackBundle } from '../backup-unpack.js';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// node:sqlite interop
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
const _require = createRequire(import.meta.url);
|
|
30
|
+
type DatabaseSync = _DatabaseSyncType;
|
|
31
|
+
const { DatabaseSync } = _require('node:sqlite') as {
|
|
32
|
+
DatabaseSync: new (...args: ConstructorParameters<typeof _DatabaseSyncType>) => DatabaseSync;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Helpers
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Seed a project root with the minimal .cleo layout required by packBundle.
|
|
41
|
+
* Creates: tasks.db, brain.db, conduit.db, config.json, project-info.json,
|
|
42
|
+
* project-context.json.
|
|
43
|
+
*/
|
|
44
|
+
function seedProject(projectRoot: string): void {
|
|
45
|
+
const cleoDir = path.join(projectRoot, '.cleo');
|
|
46
|
+
fs.mkdirSync(cleoDir, { recursive: true });
|
|
47
|
+
for (const name of ['tasks', 'brain', 'conduit']) {
|
|
48
|
+
const db = new DatabaseSync(path.join(cleoDir, `${name}.db`));
|
|
49
|
+
db.exec('CREATE TABLE t(x INTEGER); INSERT INTO t VALUES (1);');
|
|
50
|
+
db.close();
|
|
51
|
+
}
|
|
52
|
+
fs.writeFileSync(path.join(cleoDir, 'config.json'), '{}');
|
|
53
|
+
fs.writeFileSync(path.join(cleoDir, 'project-info.json'), '{}');
|
|
54
|
+
fs.writeFileSync(path.join(cleoDir, 'project-context.json'), '{}');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Test suite
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
describe('T350 backup-unpack', () => {
|
|
62
|
+
let tmpRoot: string;
|
|
63
|
+
let bundleDir: string;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cleo-t350-root-'));
|
|
67
|
+
bundleDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cleo-t350-bundle-'));
|
|
68
|
+
seedProject(tmpRoot);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
73
|
+
fs.rmSync(bundleDir, { recursive: true, force: true });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
async function createBundle(opts?: { encrypt?: boolean; passphrase?: string }): Promise<string> {
|
|
77
|
+
const bundlePath = path.join(
|
|
78
|
+
bundleDir,
|
|
79
|
+
'test' + (opts?.encrypt === true ? '.enc' : '') + '.cleobundle.tar.gz',
|
|
80
|
+
);
|
|
81
|
+
await packBundle({
|
|
82
|
+
scope: 'project',
|
|
83
|
+
projectRoot: tmpRoot,
|
|
84
|
+
outputPath: bundlePath,
|
|
85
|
+
encrypt: opts?.encrypt,
|
|
86
|
+
passphrase: opts?.passphrase,
|
|
87
|
+
});
|
|
88
|
+
return bundlePath;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// -------------------------------------------------------------------------
|
|
92
|
+
// Unencrypted bundle — happy path
|
|
93
|
+
// -------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
it('unpacks an unencrypted bundle successfully', async () => {
|
|
96
|
+
const bundlePath = await createBundle();
|
|
97
|
+
const result = await unpackBundle({ bundlePath });
|
|
98
|
+
expect(result.verified.manifestSchema).toBe(true);
|
|
99
|
+
expect(result.verified.checksums).toBe(true);
|
|
100
|
+
expect(result.verified.sqliteIntegrity).toBe(true);
|
|
101
|
+
expect(result.manifest.backup.scope).toBe('project');
|
|
102
|
+
cleanupStaging(result.stagingDir);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('returns verified.encryptionAuth=true for unencrypted bundle (N/A = pass)', async () => {
|
|
106
|
+
const bundlePath = await createBundle();
|
|
107
|
+
const result = await unpackBundle({ bundlePath });
|
|
108
|
+
expect(result.verified.encryptionAuth).toBe(true);
|
|
109
|
+
cleanupStaging(result.stagingDir);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// -------------------------------------------------------------------------
|
|
113
|
+
// Encrypted bundle — happy path
|
|
114
|
+
// -------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
it('unpacks an encrypted bundle with correct passphrase', async () => {
|
|
117
|
+
const bundlePath = await createBundle({ encrypt: true, passphrase: 'hunter2' });
|
|
118
|
+
const result = await unpackBundle({ bundlePath, passphrase: 'hunter2' });
|
|
119
|
+
expect(result.verified.encryptionAuth).toBe(true);
|
|
120
|
+
expect(result.verified.manifestSchema).toBe(true);
|
|
121
|
+
expect(result.verified.checksums).toBe(true);
|
|
122
|
+
expect(result.verified.sqliteIntegrity).toBe(true);
|
|
123
|
+
cleanupStaging(result.stagingDir);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// -------------------------------------------------------------------------
|
|
127
|
+
// Error: wrong passphrase → E_BUNDLE_DECRYPT (70)
|
|
128
|
+
// -------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
it('throws BundleError(70) on wrong passphrase', async () => {
|
|
131
|
+
const bundlePath = await createBundle({ encrypt: true, passphrase: 'hunter2' });
|
|
132
|
+
await expect(unpackBundle({ bundlePath, passphrase: 'wrong' })).rejects.toMatchObject({
|
|
133
|
+
code: 70,
|
|
134
|
+
codeName: 'E_BUNDLE_DECRYPT',
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// -------------------------------------------------------------------------
|
|
139
|
+
// Error: encrypted bundle without passphrase → E_BUNDLE_DECRYPT (70)
|
|
140
|
+
// -------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
it('throws BundleError(70) on encrypted bundle without passphrase', async () => {
|
|
143
|
+
const bundlePath = await createBundle({ encrypt: true, passphrase: 'hunter2' });
|
|
144
|
+
await expect(unpackBundle({ bundlePath })).rejects.toMatchObject({ code: 70 });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('throws BundleError(70) on encrypted bundle with empty passphrase', async () => {
|
|
148
|
+
const bundlePath = await createBundle({ encrypt: true, passphrase: 'hunter2' });
|
|
149
|
+
await expect(unpackBundle({ bundlePath, passphrase: '' })).rejects.toMatchObject({ code: 70 });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// -------------------------------------------------------------------------
|
|
153
|
+
// Error: tampered bundle → BundleError (72 or tar error)
|
|
154
|
+
// -------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
it('throws BundleError on checksum mismatch (tampered bytes)', async () => {
|
|
157
|
+
const bundlePath = await createBundle();
|
|
158
|
+
// Tamper by flipping bits near the end of the file (in the compressed data)
|
|
159
|
+
const buf = fs.readFileSync(bundlePath);
|
|
160
|
+
// Flip bytes well inside the compressed payload (not the gzip header)
|
|
161
|
+
const tamperOffset = Math.floor(buf.length / 2);
|
|
162
|
+
buf[tamperOffset] = buf[tamperOffset]! ^ 0xff;
|
|
163
|
+
buf[tamperOffset + 1] = (buf[tamperOffset + 1] ?? 0) ^ 0xff;
|
|
164
|
+
fs.writeFileSync(bundlePath, buf);
|
|
165
|
+
// tar may catch the corruption before we read checksums — accept any BundleError
|
|
166
|
+
await expect(unpackBundle({ bundlePath })).rejects.toBeInstanceOf(BundleError);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// -------------------------------------------------------------------------
|
|
170
|
+
// Staging directory structure
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
it('returns a valid staging dir containing manifest.json, databases/, json/', async () => {
|
|
174
|
+
const bundlePath = await createBundle();
|
|
175
|
+
const result = await unpackBundle({ bundlePath });
|
|
176
|
+
expect(fs.existsSync(path.join(result.stagingDir, 'manifest.json'))).toBe(true);
|
|
177
|
+
expect(fs.existsSync(path.join(result.stagingDir, 'databases'))).toBe(true);
|
|
178
|
+
expect(fs.existsSync(path.join(result.stagingDir, 'json'))).toBe(true);
|
|
179
|
+
cleanupStaging(result.stagingDir);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('staging dir contains schemas/manifest-v1.json', async () => {
|
|
183
|
+
const bundlePath = await createBundle();
|
|
184
|
+
const result = await unpackBundle({ bundlePath });
|
|
185
|
+
expect(fs.existsSync(path.join(result.stagingDir, 'schemas', 'manifest-v1.json'))).toBe(true);
|
|
186
|
+
cleanupStaging(result.stagingDir);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// -------------------------------------------------------------------------
|
|
190
|
+
// cleanupStaging
|
|
191
|
+
// -------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
it('cleanupStaging removes the staging dir', async () => {
|
|
194
|
+
const bundlePath = await createBundle();
|
|
195
|
+
const result = await unpackBundle({ bundlePath });
|
|
196
|
+
cleanupStaging(result.stagingDir);
|
|
197
|
+
expect(fs.existsSync(result.stagingDir)).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('cleanupStaging is idempotent — does not throw if already removed', async () => {
|
|
201
|
+
const bundlePath = await createBundle();
|
|
202
|
+
const result = await unpackBundle({ bundlePath });
|
|
203
|
+
cleanupStaging(result.stagingDir);
|
|
204
|
+
// Second call must not throw
|
|
205
|
+
expect(() => cleanupStaging(result.stagingDir)).not.toThrow();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// -------------------------------------------------------------------------
|
|
209
|
+
// Manifest field correctness
|
|
210
|
+
// -------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
it('parses manifest fields correctly', async () => {
|
|
213
|
+
const bundlePath = await createBundle();
|
|
214
|
+
const result = await unpackBundle({ bundlePath });
|
|
215
|
+
expect(result.manifest.manifestVersion).toBe('1.0.0');
|
|
216
|
+
expect(result.manifest.$schema).toBe('./schemas/manifest-v1.json');
|
|
217
|
+
expect(result.manifest.backup.scope).toBe('project');
|
|
218
|
+
expect(result.manifest.integrity.algorithm).toBe('sha256');
|
|
219
|
+
expect(result.manifest.databases.length).toBeGreaterThanOrEqual(3);
|
|
220
|
+
cleanupStaging(result.stagingDir);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('manifest.backup.encrypted is false for unencrypted bundle', async () => {
|
|
224
|
+
const bundlePath = await createBundle();
|
|
225
|
+
const result = await unpackBundle({ bundlePath });
|
|
226
|
+
expect(result.manifest.backup.encrypted).toBe(false);
|
|
227
|
+
cleanupStaging(result.stagingDir);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('manifest.backup.encrypted is true for encrypted bundle', async () => {
|
|
231
|
+
const bundlePath = await createBundle({ encrypt: true, passphrase: 'p@ss' });
|
|
232
|
+
const result = await unpackBundle({ bundlePath, passphrase: 'p@ss' });
|
|
233
|
+
expect(result.manifest.backup.encrypted).toBe(true);
|
|
234
|
+
cleanupStaging(result.stagingDir);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('manifest.databases includes tasks, brain, conduit for project scope', async () => {
|
|
238
|
+
const bundlePath = await createBundle();
|
|
239
|
+
const result = await unpackBundle({ bundlePath });
|
|
240
|
+
const names = result.manifest.databases.map((d) => d.name);
|
|
241
|
+
expect(names).toContain('tasks');
|
|
242
|
+
expect(names).toContain('brain');
|
|
243
|
+
expect(names).toContain('conduit');
|
|
244
|
+
cleanupStaging(result.stagingDir);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// -------------------------------------------------------------------------
|
|
248
|
+
// BundleError class shape
|
|
249
|
+
// -------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
it('BundleError extends Error with code and codeName properties', async () => {
|
|
252
|
+
const bundlePath = await createBundle({ encrypt: true, passphrase: 'secret' });
|
|
253
|
+
let caught: unknown = null;
|
|
254
|
+
try {
|
|
255
|
+
await unpackBundle({ bundlePath });
|
|
256
|
+
} catch (err) {
|
|
257
|
+
caught = err;
|
|
258
|
+
}
|
|
259
|
+
expect(caught).toBeInstanceOf(BundleError);
|
|
260
|
+
expect(caught).toBeInstanceOf(Error);
|
|
261
|
+
const be = caught as BundleError;
|
|
262
|
+
expect(typeof be.code).toBe('number');
|
|
263
|
+
expect(typeof be.codeName).toBe('string');
|
|
264
|
+
expect(be.name).toBe('BundleError');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// -------------------------------------------------------------------------
|
|
268
|
+
// Cleanup on error — staging dir is removed when unpack fails
|
|
269
|
+
// -------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
it('does not leave a staging dir behind when decryption fails', async () => {
|
|
272
|
+
const bundlePath = await createBundle({ encrypt: true, passphrase: 'secret' });
|
|
273
|
+
const preDirs = fs.readdirSync(os.tmpdir()).filter((n) => n.startsWith('cleo-unpack-'));
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
await unpackBundle({ bundlePath, passphrase: 'wrong' });
|
|
277
|
+
} catch {
|
|
278
|
+
// expected
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const postDirs = fs.readdirSync(os.tmpdir()).filter((n) => n.startsWith('cleo-unpack-'));
|
|
282
|
+
const newDirs = postDirs.filter((d) => !preDirs.includes(d));
|
|
283
|
+
expect(newDirs).toHaveLength(0);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// -------------------------------------------------------------------------
|
|
287
|
+
// Warnings are empty for a freshly-packed bundle
|
|
288
|
+
// -------------------------------------------------------------------------
|
|
289
|
+
|
|
290
|
+
it('returns no schema version warnings for a freshly packed bundle', async () => {
|
|
291
|
+
const bundlePath = await createBundle();
|
|
292
|
+
const result = await unpackBundle({ bundlePath });
|
|
293
|
+
// No warnings for a brand-new pack; schema versions may be 'unknown'
|
|
294
|
+
// so compareSchemaVersions returns null and warnings stays empty.
|
|
295
|
+
expect(Array.isArray(result.warnings)).toBe(true);
|
|
296
|
+
cleanupStaging(result.stagingDir);
|
|
297
|
+
});
|
|
298
|
+
});
|