@openparachute/vault 0.5.0-rc.2 → 0.5.0-rc.4
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 +1 -1
- package/src/mirror-import.test.ts +4 -0
- package/src/mirror-import.ts +27 -0
- package/src/mirror-routes.test.ts +403 -1
- package/src/mirror-routes.ts +412 -3
- package/src/oauth-discovery.test.ts +55 -0
- package/src/oauth-discovery.ts +24 -5
- package/web/ui/dist/assets/{index-DDRo6F4u.js → index-BKYNb2II.js} +10 -10
- package/web/ui/dist/index.html +1 -1
package/package.json
CHANGED
|
@@ -297,6 +297,10 @@ describe("cloneAndImport — success", () => {
|
|
|
297
297
|
expect(result.notes_imported).toBe(2); // alpha + beta
|
|
298
298
|
expect(result.notes_deleted).toBeUndefined();
|
|
299
299
|
expect(result.warnings).toEqual([]);
|
|
300
|
+
// vault#416: cloneAndImport stays focused on content — sync-enabling is
|
|
301
|
+
// the route's job. The worker always returns sync_enabled: false.
|
|
302
|
+
expect(result.sync_enabled).toBe(false);
|
|
303
|
+
expect(result.sync_warning).toBeUndefined();
|
|
300
304
|
|
|
301
305
|
const restored = await store.getNote("n-alpha");
|
|
302
306
|
expect(restored).toBeTruthy();
|
package/src/mirror-import.ts
CHANGED
|
@@ -154,6 +154,29 @@ export interface ImportResult {
|
|
|
154
154
|
* etc.). The HTTP handler returns these so the operator can audit.
|
|
155
155
|
*/
|
|
156
156
|
warnings: string[];
|
|
157
|
+
/**
|
|
158
|
+
* vault#416 — whether sync (mirror push-back to the imported repo) ended
|
|
159
|
+
* up enabled as part of this import. Default-on UX: the import request
|
|
160
|
+
* carries `enable_sync` (default true), and the route turns the imported
|
|
161
|
+
* repo into a configured, credential-backed, auto-pushing mirror after a
|
|
162
|
+
* successful import. `true` when sync is now wired (or was already wired
|
|
163
|
+
* to this same remote); `false` when sync was opted out, couldn't be
|
|
164
|
+
* enabled (no push-capable credentials), was skipped to avoid clobbering
|
|
165
|
+
* a different existing mirror, or threw during setup (import already
|
|
166
|
+
* succeeded — never lost to a sync error).
|
|
167
|
+
*
|
|
168
|
+
* `cloneAndImport` itself never sets these — the field is populated by the
|
|
169
|
+
* route (`handleMirrorImport`) after a successful import. `importResultFromStats`
|
|
170
|
+
* defaults `sync_enabled` to false; the route overwrites it.
|
|
171
|
+
*/
|
|
172
|
+
sync_enabled: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* Human-readable reason sync wasn't enabled (no creds / conflicting
|
|
175
|
+
* existing mirror / setup error). Only set when `sync_enabled` is false
|
|
176
|
+
* AND the caller asked for sync (`enable_sync !== false`). Absent when
|
|
177
|
+
* sync succeeded or the operator opted out.
|
|
178
|
+
*/
|
|
179
|
+
sync_warning?: string;
|
|
157
180
|
}
|
|
158
181
|
|
|
159
182
|
/**
|
|
@@ -500,6 +523,10 @@ function importResultFromStats(
|
|
|
500
523
|
tags_imported: stats.schemas_restored,
|
|
501
524
|
attachments_imported: stats.attachments_restored,
|
|
502
525
|
warnings,
|
|
526
|
+
// Default false; the route flips it true after wiring sync. Keeping the
|
|
527
|
+
// default here means a caller that bypasses the route (CLI, tests of
|
|
528
|
+
// cloneAndImport directly) gets a well-typed, conservative result.
|
|
529
|
+
sync_enabled: false,
|
|
503
530
|
};
|
|
504
531
|
if (mode === "replace") {
|
|
505
532
|
result.notes_deleted = stats.notes_wiped;
|
|
@@ -11,7 +11,12 @@ import fs from "node:fs";
|
|
|
11
11
|
import os from "node:os";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
defaultMirrorConfig,
|
|
16
|
+
readMirrorConfigForVault,
|
|
17
|
+
writeMirrorConfigForVault,
|
|
18
|
+
type MirrorConfig,
|
|
19
|
+
} from "./mirror-config.ts";
|
|
15
20
|
import {
|
|
16
21
|
MirrorManager,
|
|
17
22
|
type MirrorDeps,
|
|
@@ -1112,6 +1117,28 @@ const spawnCloneFail: GitSpawn = async () => ({
|
|
|
1112
1117
|
timedOut: false,
|
|
1113
1118
|
});
|
|
1114
1119
|
|
|
1120
|
+
/**
|
|
1121
|
+
* vault#416 — a MirrorManager wired to the REAL per-vault config file (so
|
|
1122
|
+
* `handleMirrorImport`'s `readMirrorConfigForVault` agrees with what
|
|
1123
|
+
* `manager.reload()` wrote) + a no-op export. Passed to `handleMirrorImport`
|
|
1124
|
+
* as the `managerOverride` so the sync-enable step has a live manager without
|
|
1125
|
+
* standing up the registry factory. Bootstrap (git init of the internal
|
|
1126
|
+
* mirror) runs for real; push to a fake remote fails non-fatally (we assert
|
|
1127
|
+
* on persisted config + credentials, not on a landed push).
|
|
1128
|
+
*/
|
|
1129
|
+
function makeSyncManager(home: string): MirrorManager {
|
|
1130
|
+
process.env.PARACHUTE_HOME = home;
|
|
1131
|
+
process.env.HOME = home;
|
|
1132
|
+
const deps: MirrorDeps = {
|
|
1133
|
+
vaultName: "default",
|
|
1134
|
+
runExport: async () => ({ notes: 0 }),
|
|
1135
|
+
firstChangedNoteTitle: async () => "",
|
|
1136
|
+
readMirrorConfig: () => readMirrorConfigForVault("default"),
|
|
1137
|
+
writeMirrorConfig: (c) => writeMirrorConfigForVault("default", c),
|
|
1138
|
+
};
|
|
1139
|
+
return new MirrorManager(deps);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1115
1142
|
describe("handleMirrorImport", () => {
|
|
1116
1143
|
let home: string;
|
|
1117
1144
|
let fixture: string;
|
|
@@ -1394,3 +1421,378 @@ describe("handleMirrorImport", () => {
|
|
|
1394
1421
|
});
|
|
1395
1422
|
});
|
|
1396
1423
|
|
|
1424
|
+
// ---------------------------------------------------------------------------
|
|
1425
|
+
// vault#416 — auto-enable sync to the imported repo (default-on, opt-out).
|
|
1426
|
+
// ---------------------------------------------------------------------------
|
|
1427
|
+
|
|
1428
|
+
describe("handleMirrorImport — auto-enable sync (vault#416)", () => {
|
|
1429
|
+
let home: string;
|
|
1430
|
+
let fixture: string;
|
|
1431
|
+
let manager: MirrorManager;
|
|
1432
|
+
|
|
1433
|
+
afterEach(async () => {
|
|
1434
|
+
if (manager) await manager.stop();
|
|
1435
|
+
if (home) fs.rmSync(home, { recursive: true, force: true });
|
|
1436
|
+
if (fixture) fs.rmSync(fixture, { recursive: true, force: true });
|
|
1437
|
+
_resetImportInFlightForTest();
|
|
1438
|
+
clearVaultStoreCache();
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
test("enable_sync true + PAT auth → mirror configured, creds persisted, auto_push on, sync_enabled true", async () => {
|
|
1442
|
+
home = tmp("import-sync-pat-");
|
|
1443
|
+
await bootstrapVault(home);
|
|
1444
|
+
fixture = await buildExportFixture();
|
|
1445
|
+
manager = makeSyncManager(home);
|
|
1446
|
+
|
|
1447
|
+
const req = new Request("http://x/import", {
|
|
1448
|
+
method: "POST",
|
|
1449
|
+
body: JSON.stringify({
|
|
1450
|
+
remote_url: "https://github.com/aaron/my-vault.git",
|
|
1451
|
+
mode: "merge",
|
|
1452
|
+
credentials: { kind: "pat", token: "ghp_import_token_abc" },
|
|
1453
|
+
enable_sync: true,
|
|
1454
|
+
}),
|
|
1455
|
+
});
|
|
1456
|
+
const res = await handleMirrorImport(
|
|
1457
|
+
req,
|
|
1458
|
+
"default",
|
|
1459
|
+
spawnCloneSuccess(fixture),
|
|
1460
|
+
undefined,
|
|
1461
|
+
manager,
|
|
1462
|
+
);
|
|
1463
|
+
expect(res.status).toBe(200);
|
|
1464
|
+
const body = (await res.json()) as {
|
|
1465
|
+
notes_imported: number;
|
|
1466
|
+
sync_enabled: boolean;
|
|
1467
|
+
sync_warning?: string;
|
|
1468
|
+
};
|
|
1469
|
+
expect(body.notes_imported).toBe(2);
|
|
1470
|
+
expect(body.sync_enabled).toBe(true);
|
|
1471
|
+
expect(body.sync_warning).toBeUndefined();
|
|
1472
|
+
|
|
1473
|
+
// Mirror config persisted with auto_push + enabled.
|
|
1474
|
+
const cfg = readMirrorConfigForVault("default");
|
|
1475
|
+
expect(cfg?.enabled).toBe(true);
|
|
1476
|
+
expect(cfg?.auto_push).toBe(true);
|
|
1477
|
+
|
|
1478
|
+
// Credentials persisted, pointing at the imported remote.
|
|
1479
|
+
const creds = readCredentials("default");
|
|
1480
|
+
expect(creds?.active_method).toBe("pat");
|
|
1481
|
+
expect(creds?.pat?.token).toBe("ghp_import_token_abc");
|
|
1482
|
+
expect(creds?.pat?.remote_url).toContain("github.com/aaron/my-vault.git");
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
test("enable_sync false → no mirror configured, sync_enabled false, no warning", async () => {
|
|
1486
|
+
home = tmp("import-sync-optout-");
|
|
1487
|
+
await bootstrapVault(home);
|
|
1488
|
+
fixture = await buildExportFixture();
|
|
1489
|
+
manager = makeSyncManager(home);
|
|
1490
|
+
|
|
1491
|
+
const req = new Request("http://x/import", {
|
|
1492
|
+
method: "POST",
|
|
1493
|
+
body: JSON.stringify({
|
|
1494
|
+
remote_url: "https://github.com/aaron/my-vault.git",
|
|
1495
|
+
mode: "merge",
|
|
1496
|
+
credentials: { kind: "pat", token: "ghp_import_token_abc" },
|
|
1497
|
+
enable_sync: false,
|
|
1498
|
+
}),
|
|
1499
|
+
});
|
|
1500
|
+
const res = await handleMirrorImport(
|
|
1501
|
+
req,
|
|
1502
|
+
"default",
|
|
1503
|
+
spawnCloneSuccess(fixture),
|
|
1504
|
+
undefined,
|
|
1505
|
+
manager,
|
|
1506
|
+
);
|
|
1507
|
+
expect(res.status).toBe(200);
|
|
1508
|
+
const body = (await res.json()) as {
|
|
1509
|
+
sync_enabled: boolean;
|
|
1510
|
+
sync_warning?: string;
|
|
1511
|
+
};
|
|
1512
|
+
expect(body.sync_enabled).toBe(false);
|
|
1513
|
+
expect(body.sync_warning).toBeUndefined();
|
|
1514
|
+
|
|
1515
|
+
// Nothing configured.
|
|
1516
|
+
expect(readMirrorConfigForVault("default")).toBeUndefined();
|
|
1517
|
+
expect(readCredentials("default")).toBeNull();
|
|
1518
|
+
});
|
|
1519
|
+
|
|
1520
|
+
test("enable_sync true + auth none → sync_enabled false + needs-write-creds warning; no broken mirror", async () => {
|
|
1521
|
+
home = tmp("import-sync-nocreds-");
|
|
1522
|
+
await bootstrapVault(home);
|
|
1523
|
+
fixture = await buildExportFixture();
|
|
1524
|
+
manager = makeSyncManager(home);
|
|
1525
|
+
|
|
1526
|
+
const req = new Request("http://x/import", {
|
|
1527
|
+
method: "POST",
|
|
1528
|
+
body: JSON.stringify({
|
|
1529
|
+
remote_url: "https://github.com/aaron/my-vault.git",
|
|
1530
|
+
mode: "merge",
|
|
1531
|
+
credentials: { kind: "none" },
|
|
1532
|
+
enable_sync: true,
|
|
1533
|
+
}),
|
|
1534
|
+
});
|
|
1535
|
+
const res = await handleMirrorImport(
|
|
1536
|
+
req,
|
|
1537
|
+
"default",
|
|
1538
|
+
spawnCloneSuccess(fixture),
|
|
1539
|
+
undefined,
|
|
1540
|
+
manager,
|
|
1541
|
+
);
|
|
1542
|
+
expect(res.status).toBe(200);
|
|
1543
|
+
const body = (await res.json()) as {
|
|
1544
|
+
notes_imported: number;
|
|
1545
|
+
sync_enabled: boolean;
|
|
1546
|
+
sync_warning?: string;
|
|
1547
|
+
};
|
|
1548
|
+
// Import still succeeded.
|
|
1549
|
+
expect(body.notes_imported).toBe(2);
|
|
1550
|
+
expect(body.sync_enabled).toBe(false);
|
|
1551
|
+
expect(body.sync_warning).toContain("write credentials");
|
|
1552
|
+
|
|
1553
|
+
// No mirror left configured, no credentials written.
|
|
1554
|
+
expect(readMirrorConfigForVault("default")).toBeUndefined();
|
|
1555
|
+
expect(readCredentials("default")).toBeNull();
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
test("enable_sync defaults to true when omitted", async () => {
|
|
1559
|
+
home = tmp("import-sync-default-");
|
|
1560
|
+
await bootstrapVault(home);
|
|
1561
|
+
fixture = await buildExportFixture();
|
|
1562
|
+
manager = makeSyncManager(home);
|
|
1563
|
+
|
|
1564
|
+
const req = new Request("http://x/import", {
|
|
1565
|
+
method: "POST",
|
|
1566
|
+
body: JSON.stringify({
|
|
1567
|
+
remote_url: "https://github.com/aaron/my-vault.git",
|
|
1568
|
+
mode: "merge",
|
|
1569
|
+
credentials: { kind: "pat", token: "ghp_default_on_token" },
|
|
1570
|
+
// enable_sync omitted — should default ON.
|
|
1571
|
+
}),
|
|
1572
|
+
});
|
|
1573
|
+
const res = await handleMirrorImport(
|
|
1574
|
+
req,
|
|
1575
|
+
"default",
|
|
1576
|
+
spawnCloneSuccess(fixture),
|
|
1577
|
+
undefined,
|
|
1578
|
+
manager,
|
|
1579
|
+
);
|
|
1580
|
+
expect(res.status).toBe(200);
|
|
1581
|
+
const body = (await res.json()) as { sync_enabled: boolean };
|
|
1582
|
+
expect(body.sync_enabled).toBe(true);
|
|
1583
|
+
expect(readMirrorConfigForVault("default")?.auto_push).toBe(true);
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
test("existing mirror to a DIFFERENT remote → not clobbered, sync_enabled false + conflict warning", async () => {
|
|
1587
|
+
home = tmp("import-sync-conflict-");
|
|
1588
|
+
await bootstrapVault(home);
|
|
1589
|
+
fixture = await buildExportFixture();
|
|
1590
|
+
manager = makeSyncManager(home);
|
|
1591
|
+
|
|
1592
|
+
// Pre-existing mirror config (enabled) + credential pointing elsewhere.
|
|
1593
|
+
writeMirrorConfigForVault("default", {
|
|
1594
|
+
...defaultMirrorConfig(),
|
|
1595
|
+
enabled: true,
|
|
1596
|
+
auto_push: true,
|
|
1597
|
+
});
|
|
1598
|
+
writeCredentials("default", {
|
|
1599
|
+
active_method: "pat",
|
|
1600
|
+
github_oauth: null,
|
|
1601
|
+
pat: {
|
|
1602
|
+
token: "ghp_existing_other",
|
|
1603
|
+
remote_url:
|
|
1604
|
+
"https://x-access-token:ghp_existing_other@github.com/aaron/OTHER-repo.git",
|
|
1605
|
+
label: "existing backup",
|
|
1606
|
+
},
|
|
1607
|
+
});
|
|
1608
|
+
|
|
1609
|
+
const req = new Request("http://x/import", {
|
|
1610
|
+
method: "POST",
|
|
1611
|
+
body: JSON.stringify({
|
|
1612
|
+
remote_url: "https://github.com/aaron/my-vault.git",
|
|
1613
|
+
mode: "merge",
|
|
1614
|
+
credentials: { kind: "pat", token: "ghp_import_token_abc" },
|
|
1615
|
+
enable_sync: true,
|
|
1616
|
+
}),
|
|
1617
|
+
});
|
|
1618
|
+
const res = await handleMirrorImport(
|
|
1619
|
+
req,
|
|
1620
|
+
"default",
|
|
1621
|
+
spawnCloneSuccess(fixture),
|
|
1622
|
+
undefined,
|
|
1623
|
+
manager,
|
|
1624
|
+
);
|
|
1625
|
+
expect(res.status).toBe(200);
|
|
1626
|
+
const body = (await res.json()) as {
|
|
1627
|
+
sync_enabled: boolean;
|
|
1628
|
+
sync_warning?: string;
|
|
1629
|
+
};
|
|
1630
|
+
expect(body.sync_enabled).toBe(false);
|
|
1631
|
+
expect(body.sync_warning).toContain("already syncs to a different repo");
|
|
1632
|
+
|
|
1633
|
+
// The existing credential was NOT clobbered.
|
|
1634
|
+
const creds = readCredentials("default");
|
|
1635
|
+
expect(creds?.pat?.token).toBe("ghp_existing_other");
|
|
1636
|
+
expect(creds?.pat?.remote_url).toContain("OTHER-repo.git");
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
test("existing mirror to the SAME remote → no-op success (sync_enabled true)", async () => {
|
|
1640
|
+
home = tmp("import-sync-same-");
|
|
1641
|
+
await bootstrapVault(home);
|
|
1642
|
+
fixture = await buildExportFixture();
|
|
1643
|
+
manager = makeSyncManager(home);
|
|
1644
|
+
|
|
1645
|
+
writeMirrorConfigForVault("default", {
|
|
1646
|
+
...defaultMirrorConfig(),
|
|
1647
|
+
enabled: true,
|
|
1648
|
+
auto_push: true,
|
|
1649
|
+
});
|
|
1650
|
+
writeCredentials("default", {
|
|
1651
|
+
active_method: "pat",
|
|
1652
|
+
github_oauth: null,
|
|
1653
|
+
pat: {
|
|
1654
|
+
token: "ghp_same_token",
|
|
1655
|
+
remote_url:
|
|
1656
|
+
"https://x-access-token:ghp_same_token@github.com/aaron/my-vault.git",
|
|
1657
|
+
label: "existing same",
|
|
1658
|
+
},
|
|
1659
|
+
});
|
|
1660
|
+
|
|
1661
|
+
const req = new Request("http://x/import", {
|
|
1662
|
+
method: "POST",
|
|
1663
|
+
body: JSON.stringify({
|
|
1664
|
+
remote_url: "https://github.com/aaron/my-vault.git",
|
|
1665
|
+
mode: "merge",
|
|
1666
|
+
credentials: { kind: "pat", token: "ghp_same_token" },
|
|
1667
|
+
enable_sync: true,
|
|
1668
|
+
}),
|
|
1669
|
+
});
|
|
1670
|
+
const res = await handleMirrorImport(
|
|
1671
|
+
req,
|
|
1672
|
+
"default",
|
|
1673
|
+
spawnCloneSuccess(fixture),
|
|
1674
|
+
undefined,
|
|
1675
|
+
manager,
|
|
1676
|
+
);
|
|
1677
|
+
expect(res.status).toBe(200);
|
|
1678
|
+
const body = (await res.json()) as {
|
|
1679
|
+
sync_enabled: boolean;
|
|
1680
|
+
sync_warning?: string;
|
|
1681
|
+
};
|
|
1682
|
+
expect(body.sync_enabled).toBe(true);
|
|
1683
|
+
expect(body.sync_warning).toBeUndefined();
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1686
|
+
test("existing GitHub-connected mirror → PAT import doesn't clobber it (sync_enabled false + warning)", async () => {
|
|
1687
|
+
home = tmp("import-sync-oauth-conflict-");
|
|
1688
|
+
await bootstrapVault(home);
|
|
1689
|
+
fixture = await buildExportFixture();
|
|
1690
|
+
manager = makeSyncManager(home);
|
|
1691
|
+
|
|
1692
|
+
writeMirrorConfigForVault("default", {
|
|
1693
|
+
...defaultMirrorConfig(),
|
|
1694
|
+
enabled: true,
|
|
1695
|
+
auto_push: true,
|
|
1696
|
+
});
|
|
1697
|
+
writeCredentials("default", {
|
|
1698
|
+
active_method: "github_oauth",
|
|
1699
|
+
github_oauth: {
|
|
1700
|
+
access_token: "gho_existing_oauth",
|
|
1701
|
+
scope: "repo",
|
|
1702
|
+
authorized_at: "2026-05-28T00:00:00.000Z",
|
|
1703
|
+
user_login: "aaron",
|
|
1704
|
+
user_id: 1,
|
|
1705
|
+
},
|
|
1706
|
+
pat: null,
|
|
1707
|
+
});
|
|
1708
|
+
|
|
1709
|
+
const req = new Request("http://x/import", {
|
|
1710
|
+
method: "POST",
|
|
1711
|
+
body: JSON.stringify({
|
|
1712
|
+
remote_url: "https://github.com/aaron/my-vault.git",
|
|
1713
|
+
mode: "merge",
|
|
1714
|
+
credentials: { kind: "pat", token: "ghp_import_token_abc" },
|
|
1715
|
+
enable_sync: true,
|
|
1716
|
+
}),
|
|
1717
|
+
});
|
|
1718
|
+
const res = await handleMirrorImport(
|
|
1719
|
+
req,
|
|
1720
|
+
"default",
|
|
1721
|
+
spawnCloneSuccess(fixture),
|
|
1722
|
+
undefined,
|
|
1723
|
+
manager,
|
|
1724
|
+
);
|
|
1725
|
+
expect(res.status).toBe(200);
|
|
1726
|
+
const body = (await res.json()) as {
|
|
1727
|
+
sync_enabled: boolean;
|
|
1728
|
+
sync_warning?: string;
|
|
1729
|
+
};
|
|
1730
|
+
expect(body.sync_enabled).toBe(false);
|
|
1731
|
+
expect(body.sync_warning).toContain("connected GitHub account");
|
|
1732
|
+
|
|
1733
|
+
// The existing OAuth credential is untouched (not switched to a PAT).
|
|
1734
|
+
const creds = readCredentials("default");
|
|
1735
|
+
expect(creds?.active_method).toBe("github_oauth");
|
|
1736
|
+
expect(creds?.pat).toBeNull();
|
|
1737
|
+
});
|
|
1738
|
+
|
|
1739
|
+
test("sync-setup failure after a successful import → import result still returned, sync_enabled false + warning", async () => {
|
|
1740
|
+
home = tmp("import-sync-setupfail-");
|
|
1741
|
+
await bootstrapVault(home);
|
|
1742
|
+
fixture = await buildExportFixture();
|
|
1743
|
+
manager = makeSyncManager(home);
|
|
1744
|
+
|
|
1745
|
+
// Force the sync-enable step to throw by stubbing the manager's reload.
|
|
1746
|
+
// The import itself has already succeeded by the time reload runs, so the
|
|
1747
|
+
// request must still return a 200 with the import counts intact.
|
|
1748
|
+
manager.reload = async () => {
|
|
1749
|
+
throw new Error("boom: simulated reload failure");
|
|
1750
|
+
};
|
|
1751
|
+
|
|
1752
|
+
const req = new Request("http://x/import", {
|
|
1753
|
+
method: "POST",
|
|
1754
|
+
body: JSON.stringify({
|
|
1755
|
+
remote_url: "https://github.com/aaron/my-vault.git",
|
|
1756
|
+
mode: "merge",
|
|
1757
|
+
credentials: { kind: "pat", token: "ghp_import_token_abc" },
|
|
1758
|
+
enable_sync: true,
|
|
1759
|
+
}),
|
|
1760
|
+
});
|
|
1761
|
+
const res = await handleMirrorImport(
|
|
1762
|
+
req,
|
|
1763
|
+
"default",
|
|
1764
|
+
spawnCloneSuccess(fixture),
|
|
1765
|
+
undefined,
|
|
1766
|
+
manager,
|
|
1767
|
+
);
|
|
1768
|
+
expect(res.status).toBe(200);
|
|
1769
|
+
const body = (await res.json()) as {
|
|
1770
|
+
notes_imported: number;
|
|
1771
|
+
sync_enabled: boolean;
|
|
1772
|
+
sync_warning?: string;
|
|
1773
|
+
};
|
|
1774
|
+
// Import NOT lost.
|
|
1775
|
+
expect(body.notes_imported).toBe(2);
|
|
1776
|
+
expect(body.sync_enabled).toBe(false);
|
|
1777
|
+
expect(body.sync_warning).toContain("enabling Sync failed");
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
test("invalid enable_sync type → 400 validation error", async () => {
|
|
1781
|
+
home = tmp("import-sync-badtype-");
|
|
1782
|
+
await bootstrapVault(home);
|
|
1783
|
+
const req = new Request("http://x/import", {
|
|
1784
|
+
method: "POST",
|
|
1785
|
+
body: JSON.stringify({
|
|
1786
|
+
remote_url: "https://github.com/aaron/my-vault.git",
|
|
1787
|
+
mode: "merge",
|
|
1788
|
+
credentials: { kind: "none" },
|
|
1789
|
+
enable_sync: "yes",
|
|
1790
|
+
}),
|
|
1791
|
+
});
|
|
1792
|
+
const res = await handleMirrorImport(req, "default");
|
|
1793
|
+
expect(res.status).toBe(400);
|
|
1794
|
+
const body = (await res.json()) as { field: string };
|
|
1795
|
+
expect(body.field).toBe("enable_sync");
|
|
1796
|
+
});
|
|
1797
|
+
});
|
|
1798
|
+
|