@logictan/dsh-config-manager 0.1.60
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/LICENSE +21 -0
- package/README.md +105 -0
- package/cordis.patch.yml +21 -0
- package/lib/adapters/agent-instructions.d.ts +13 -0
- package/lib/adapters/agent-instructions.js +46 -0
- package/lib/adapters/agent-presets.d.ts +13 -0
- package/lib/adapters/agent-presets.js +14 -0
- package/lib/adapters/credentials.d.ts +37 -0
- package/lib/adapters/credentials.js +122 -0
- package/lib/adapters/file-collection.d.ts +15 -0
- package/lib/adapters/file-collection.js +127 -0
- package/lib/adapters/index.d.ts +57 -0
- package/lib/adapters/index.js +55 -0
- package/lib/adapters/link-report.d.ts +16 -0
- package/lib/adapters/link-report.js +47 -0
- package/lib/adapters/mcp.d.ts +28 -0
- package/lib/adapters/mcp.js +178 -0
- package/lib/adapters/plugin-files.d.ts +18 -0
- package/lib/adapters/plugin-files.js +142 -0
- package/lib/adapters/plugins.d.ts +41 -0
- package/lib/adapters/plugins.js +603 -0
- package/lib/adapters/pnpm-workspace.d.ts +26 -0
- package/lib/adapters/pnpm-workspace.js +147 -0
- package/lib/adapters/prompts.d.ts +30 -0
- package/lib/adapters/prompts.js +231 -0
- package/lib/adapters/providers.d.ts +34 -0
- package/lib/adapters/providers.js +155 -0
- package/lib/adapters/self.d.ts +17 -0
- package/lib/adapters/self.js +71 -0
- package/lib/adapters/sessions.d.ts +14 -0
- package/lib/adapters/sessions.js +15 -0
- package/lib/adapters/settings.d.ts +28 -0
- package/lib/adapters/settings.js +165 -0
- package/lib/adapters/skills.d.ts +13 -0
- package/lib/adapters/skills.js +14 -0
- package/lib/adapters/test-helpers.d.ts +125 -0
- package/lib/adapters/test-helpers.js +236 -0
- package/lib/adapters/ui.d.ts +27 -0
- package/lib/adapters/ui.js +89 -0
- package/lib/adapters/workspaces.d.ts +13 -0
- package/lib/adapters/workspaces.js +106 -0
- package/lib/client.d.ts +1026 -0
- package/lib/client.js +10047 -0
- package/lib/core/analyzer.d.ts +99 -0
- package/lib/core/analyzer.js +934 -0
- package/lib/core/backup.d.ts +138 -0
- package/lib/core/backup.js +513 -0
- package/lib/core/boot-rescue.d.ts +146 -0
- package/lib/core/boot-rescue.js +618 -0
- package/lib/core/cache-cleaner.d.ts +31 -0
- package/lib/core/cache-cleaner.js +95 -0
- package/lib/core/config-lifecycle.d.ts +180 -0
- package/lib/core/config-lifecycle.js +481 -0
- package/lib/core/config-snapshot.d.ts +135 -0
- package/lib/core/config-snapshot.js +424 -0
- package/lib/core/config-state.d.ts +70 -0
- package/lib/core/config-state.js +177 -0
- package/lib/core/consult-source.d.ts +58 -0
- package/lib/core/consult-source.js +260 -0
- package/lib/core/crash-report.d.ts +84 -0
- package/lib/core/crash-report.js +317 -0
- package/lib/core/exporter.d.ts +68 -0
- package/lib/core/exporter.js +375 -0
- package/lib/core/ghost-sweep.d.ts +19 -0
- package/lib/core/ghost-sweep.js +52 -0
- package/lib/core/importer.d.ts +60 -0
- package/lib/core/importer.js +29 -0
- package/lib/core/index.d.ts +14 -0
- package/lib/core/index.js +14 -0
- package/lib/core/journal.d.ts +197 -0
- package/lib/core/journal.js +418 -0
- package/lib/core/local-plugin-host.d.ts +35 -0
- package/lib/core/local-plugin-host.js +120 -0
- package/lib/core/local-plugin-pack.d.ts +135 -0
- package/lib/core/local-plugin-pack.js +278 -0
- package/lib/core/messages.d.ts +310 -0
- package/lib/core/messages.js +621 -0
- package/lib/core/migration-consult.d.ts +117 -0
- package/lib/core/migration-consult.js +363 -0
- package/lib/core/migration-history.d.ts +153 -0
- package/lib/core/migration-history.js +411 -0
- package/lib/core/model-tools.d.ts +50 -0
- package/lib/core/model-tools.js +148 -0
- package/lib/core/msg-types.d.ts +8 -0
- package/lib/core/msg-types.js +6 -0
- package/lib/core/phase3-child-crash.d.ts +1 -0
- package/lib/core/phase3-child-crash.js +87 -0
- package/lib/core/phase3-host.d.ts +138 -0
- package/lib/core/phase3-host.js +359 -0
- package/lib/core/phase3-prod-child.d.ts +1 -0
- package/lib/core/phase3-prod-child.js +35 -0
- package/lib/core/phase4-crash-child.d.ts +1 -0
- package/lib/core/phase4-crash-child.js +103 -0
- package/lib/core/plugin-cli.d.ts +150 -0
- package/lib/core/plugin-cli.js +497 -0
- package/lib/core/reconcile.d.ts +120 -0
- package/lib/core/reconcile.js +389 -0
- package/lib/core/recovery-orchestrator.d.ts +94 -0
- package/lib/core/recovery-orchestrator.js +327 -0
- package/lib/core/restore.d.ts +162 -0
- package/lib/core/restore.js +703 -0
- package/lib/core/rollback.d.ts +12 -0
- package/lib/core/rollback.js +145 -0
- package/lib/core/run-registry.d.ts +85 -0
- package/lib/core/run-registry.js +157 -0
- package/lib/core/startup-barrier.d.ts +53 -0
- package/lib/core/startup-barrier.js +77 -0
- package/lib/core/types.d.ts +515 -0
- package/lib/core/types.js +27 -0
- package/lib/core/undo.d.ts +73 -0
- package/lib/core/undo.js +87 -0
- package/lib/core/validator.d.ts +18 -0
- package/lib/core/validator.js +77 -0
- package/lib/core/verify-recovery.d.ts +21 -0
- package/lib/core/verify-recovery.js +256 -0
- package/lib/core/watcher.d.ts +101 -0
- package/lib/core/watcher.js +219 -0
- package/lib/index.d.ts +238 -0
- package/lib/index.js +2764 -0
- package/lib/migrations/index.d.ts +29 -0
- package/lib/migrations/index.js +56 -0
- package/lib/migrations/v1-to-v2.d.ts +9 -0
- package/lib/migrations/v1-to-v2.js +10 -0
- package/lib/profiles/index.d.ts +4 -0
- package/lib/profiles/index.js +5 -0
- package/lib/profiles/profile-manager.d.ts +106 -0
- package/lib/profiles/profile-manager.js +543 -0
- package/lib/schema/config.d.ts +28 -0
- package/lib/schema/config.js +165 -0
- package/lib/schema/index.d.ts +24 -0
- package/lib/schema/index.js +26 -0
- package/lib/schema/manifest.d.ts +28 -0
- package/lib/schema/manifest.js +109 -0
- package/lib/schema/tombstones.d.ts +46 -0
- package/lib/schema/tombstones.js +64 -0
- package/lib/schema/types.d.ts +232 -0
- package/lib/schema/types.js +8 -0
- package/lib/schema/versions.d.ts +27 -0
- package/lib/schema/versions.js +50 -0
- package/lib/security/encryption.d.ts +85 -0
- package/lib/security/encryption.js +279 -0
- package/lib/security/index.d.ts +16 -0
- package/lib/security/index.js +17 -0
- package/lib/security/integrity.d.ts +30 -0
- package/lib/security/integrity.js +78 -0
- package/lib/security/redaction.d.ts +26 -0
- package/lib/security/redaction.js +95 -0
- package/lib/security/secret-scanner.d.ts +86 -0
- package/lib/security/secret-scanner.js +404 -0
- package/lib/security/vault.d.ts +59 -0
- package/lib/security/vault.js +151 -0
- package/lib/security/zip-security.d.ts +27 -0
- package/lib/security/zip-security.js +154 -0
- package/lib/sync/autosync-config.d.ts +39 -0
- package/lib/sync/autosync-config.js +151 -0
- package/lib/sync/autosync-scheduler.d.ts +139 -0
- package/lib/sync/autosync-scheduler.js +564 -0
- package/lib/sync/fs.d.ts +18 -0
- package/lib/sync/fs.js +49 -0
- package/lib/sync/git/git-transport.d.ts +100 -0
- package/lib/sync/git/git-transport.js +437 -0
- package/lib/sync/github-auth.d.ts +131 -0
- package/lib/sync/github-auth.js +264 -0
- package/lib/sync/layout.d.ts +43 -0
- package/lib/sync/layout.js +203 -0
- package/lib/sync/snapshot-json.d.ts +9 -0
- package/lib/sync/snapshot-json.js +107 -0
- package/lib/sync/sync-config.d.ts +81 -0
- package/lib/sync/sync-config.js +281 -0
- package/lib/sync/sync-engine.d.ts +269 -0
- package/lib/sync/sync-engine.js +591 -0
- package/lib/sync/sync-history.d.ts +42 -0
- package/lib/sync/sync-history.js +78 -0
- package/lib/sync/sync-selection.d.ts +29 -0
- package/lib/sync/sync-selection.js +127 -0
- package/lib/sync/sync-session.d.ts +40 -0
- package/lib/sync/sync-session.js +54 -0
- package/lib/sync/sync-state.d.ts +37 -0
- package/lib/sync/sync-state.js +108 -0
- package/lib/sync/transport.d.ts +75 -0
- package/lib/sync/transport.js +55 -0
- package/lib/sync/ui-prefs.d.ts +34 -0
- package/lib/sync/ui-prefs.js +109 -0
- package/lib/sync/webdav/webdav-transport.d.ts +77 -0
- package/lib/sync/webdav/webdav-transport.js +451 -0
- package/lib/ui/errors.d.ts +19 -0
- package/lib/ui/errors.js +81 -0
- package/lib/ui/export-flow.d.ts +82 -0
- package/lib/ui/export-flow.js +98 -0
- package/lib/ui/i18n.d.ts +289 -0
- package/lib/ui/i18n.js +581 -0
- package/lib/ui/migration-consult-view.d.ts +91 -0
- package/lib/ui/migration-consult-view.js +115 -0
- package/lib/ui/progress.d.ts +38 -0
- package/lib/ui/progress.js +87 -0
- package/lib/ui/report.d.ts +23 -0
- package/lib/ui/report.js +153 -0
- package/lib/ui/test-helpers.d.ts +69 -0
- package/lib/ui/test-helpers.js +154 -0
- package/lib/ui/types.d.ts +257 -0
- package/lib/ui/types.js +21 -0
- package/lib/utils/atomic-write.d.ts +88 -0
- package/lib/utils/atomic-write.js +399 -0
- package/lib/utils/env-lock.d.ts +393 -0
- package/lib/utils/env-lock.js +930 -0
- package/lib/utils/hashing.d.ts +17 -0
- package/lib/utils/hashing.js +41 -0
- package/lib/utils/json.d.ts +24 -0
- package/lib/utils/json.js +76 -0
- package/lib/utils/logger.d.ts +39 -0
- package/lib/utils/logger.js +82 -0
- package/lib/utils/paths.d.ts +42 -0
- package/lib/utils/paths.js +189 -0
- package/lib/utils/proxy.d.ts +69 -0
- package/lib/utils/proxy.js +455 -0
- package/lib/utils/recursive-walk.d.ts +24 -0
- package/lib/utils/recursive-walk.js +134 -0
- package/lib/utils/zip.d.ts +54 -0
- package/lib/utils/zip.js +285 -0
- package/package.json +188 -0
- package/src/adapters/agent-instructions.ts +48 -0
- package/src/adapters/agent-presets.ts +14 -0
- package/src/adapters/credentials.test.ts +90 -0
- package/src/adapters/credentials.ts +143 -0
- package/src/adapters/file-collection.ts +137 -0
- package/src/adapters/files.test.ts +353 -0
- package/src/adapters/index.ts +94 -0
- package/src/adapters/link-report.test.ts +65 -0
- package/src/adapters/link-report.ts +61 -0
- package/src/adapters/mcp.test.ts +96 -0
- package/src/adapters/mcp.ts +185 -0
- package/src/adapters/plugin-files.ts +146 -0
- package/src/adapters/plugins.test.ts +326 -0
- package/src/adapters/plugins.ts +615 -0
- package/src/adapters/pnpm-workspace.test.ts +87 -0
- package/src/adapters/pnpm-workspace.ts +150 -0
- package/src/adapters/prompts.test.ts +110 -0
- package/src/adapters/prompts.ts +230 -0
- package/src/adapters/providers.test.ts +126 -0
- package/src/adapters/providers.ts +173 -0
- package/src/adapters/roundtrip.test.ts +190 -0
- package/src/adapters/self.test.ts +92 -0
- package/src/adapters/self.ts +75 -0
- package/src/adapters/sessions.ts +15 -0
- package/src/adapters/settings.test.ts +100 -0
- package/src/adapters/settings.ts +187 -0
- package/src/adapters/skills.ts +14 -0
- package/src/adapters/test-helpers.ts +248 -0
- package/src/adapters/ui.test.ts +57 -0
- package/src/adapters/ui.ts +108 -0
- package/src/adapters/workspaces.test.ts +106 -0
- package/src/adapters/workspaces.ts +109 -0
- package/src/client/ConfigManagerSection.tsx +90 -0
- package/src/client/api.ts +66 -0
- package/src/client/client-types.ts +18 -0
- package/src/client/common/ErrorBanner.tsx +73 -0
- package/src/client/common/Icon.tsx +129 -0
- package/src/client/common/Modal.tsx +173 -0
- package/src/client/common/ToastViewport.tsx +73 -0
- package/src/client/common/toast-store.test.ts +192 -0
- package/src/client/common/toast-store.ts +181 -0
- package/src/client/common/toast-types.ts +7 -0
- package/src/client/common/ui.tsx +342 -0
- package/src/client/config-manager.module.css +2971 -0
- package/src/client/css-modules.d.ts +8 -0
- package/src/client/index.ts +65 -0
- package/src/client/locales.ts +56 -0
- package/src/client/lucide-icons.d.ts +38 -0
- package/src/client/run-store.test.ts +223 -0
- package/src/client/run-store.ts +372 -0
- package/src/client/sync/SyncConfirmView.tsx +379 -0
- package/src/client/sync/SyncHistoryView.test.ts +40 -0
- package/src/client/sync/SyncHistoryView.tsx +220 -0
- package/src/client/sync/SyncSettingsView.tsx +1535 -0
- package/src/client/sync/history-model.test.ts +194 -0
- package/src/client/sync/history-model.ts +195 -0
- package/src/client/sync/sync-api.test.ts +585 -0
- package/src/client/sync/sync-api.ts +543 -0
- package/src/client/sync/sync-locales.ts +416 -0
- package/src/client/sync/sync-push-preview.test.ts +52 -0
- package/src/client/sync/sync-view-v2.test.ts +213 -0
- package/src/client/sync/sync-view.test.ts +487 -0
- package/src/client/sync/sync-view.ts +759 -0
- package/src/core/analyzer.ts +1068 -0
- package/src/core/backup.ts +605 -0
- package/src/core/boot-rescue.test.ts +847 -0
- package/src/core/boot-rescue.ts +737 -0
- package/src/core/cache-cleaner.test.ts +169 -0
- package/src/core/cache-cleaner.ts +121 -0
- package/src/core/config-lifecycle.test.ts +727 -0
- package/src/core/config-lifecycle.ts +573 -0
- package/src/core/config-snapshot.test.ts +369 -0
- package/src/core/config-snapshot.ts +522 -0
- package/src/core/config-state.test.ts +381 -0
- package/src/core/config-state.ts +207 -0
- package/src/core/consult-source.test.ts +199 -0
- package/src/core/consult-source.ts +293 -0
- package/src/core/crash-report.test.ts +380 -0
- package/src/core/crash-report.ts +369 -0
- package/src/core/exporter.ts +417 -0
- package/src/core/ghost-sweep.test.ts +94 -0
- package/src/core/ghost-sweep.ts +67 -0
- package/src/core/importer.ts +83 -0
- package/src/core/index.ts +56 -0
- package/src/core/journal.test.ts +257 -0
- package/src/core/journal.ts +518 -0
- package/src/core/local-plugin-host.ts +152 -0
- package/src/core/local-plugin-pack.test.ts +359 -0
- package/src/core/local-plugin-pack.ts +363 -0
- package/src/core/messages.test.ts +54 -0
- package/src/core/messages.ts +664 -0
- package/src/core/migration-consult.test.ts +315 -0
- package/src/core/migration-consult.ts +494 -0
- package/src/core/migration-history.test.ts +319 -0
- package/src/core/migration-history.ts +484 -0
- package/src/core/model-tools.test.ts +190 -0
- package/src/core/model-tools.ts +208 -0
- package/src/core/msg-types.ts +10 -0
- package/src/core/phase3-child-crash.ts +95 -0
- package/src/core/phase3-host.ts +411 -0
- package/src/core/phase3-p1.test.ts +212 -0
- package/src/core/phase3-p2b-fingerprint.test.ts +189 -0
- package/src/core/phase3-prod-child.ts +36 -0
- package/src/core/phase3-production-integration.test.ts +286 -0
- package/src/core/phase4-crash-child.ts +101 -0
- package/src/core/phase4-crash-injection.test.ts +166 -0
- package/src/core/plugin-cli.fs.test.ts +184 -0
- package/src/core/plugin-cli.test.ts +137 -0
- package/src/core/plugin-cli.ts +563 -0
- package/src/core/reconcile.test.ts +392 -0
- package/src/core/reconcile.ts +498 -0
- package/src/core/recovery-orchestrator.test.ts +145 -0
- package/src/core/recovery-orchestrator.ts +366 -0
- package/src/core/restore-manage.test.ts +94 -0
- package/src/core/restore.ts +849 -0
- package/src/core/rollback.ts +151 -0
- package/src/core/run-progress.test.ts +237 -0
- package/src/core/run-registry.test.ts +232 -0
- package/src/core/run-registry.ts +209 -0
- package/src/core/smoke.test.ts +842 -0
- package/src/core/startup-barrier.ts +99 -0
- package/src/core/types.ts +542 -0
- package/src/core/undo.ts +116 -0
- package/src/core/validator.ts +84 -0
- package/src/core/verify-recovery.ts +265 -0
- package/src/core/watcher.test.ts +326 -0
- package/src/core/watcher.ts +263 -0
- package/src/index.facade.test.ts +265 -0
- package/src/index.sync.test.ts +217 -0
- package/src/index.ts +2975 -0
- package/src/migrations/index.ts +71 -0
- package/src/migrations/v1-to-v2.ts +17 -0
- package/src/profiles/index.ts +8 -0
- package/src/profiles/profile-manager.test.ts +282 -0
- package/src/profiles/profile-manager.ts +630 -0
- package/src/schema/config.ts +165 -0
- package/src/schema/index.ts +71 -0
- package/src/schema/manifest-schema.test.ts +519 -0
- package/src/schema/manifest.ts +124 -0
- package/src/schema/tombstones.test.ts +131 -0
- package/src/schema/tombstones.ts +89 -0
- package/src/schema/types.ts +245 -0
- package/src/schema/versions.ts +55 -0
- package/src/security/encryption.ts +335 -0
- package/src/security/index.ts +16 -0
- package/src/security/integrity.ts +102 -0
- package/src/security/redaction.ts +101 -0
- package/src/security/secret-scanner.ts +445 -0
- package/src/security/security.test.ts +1215 -0
- package/src/security/vault.test.ts +164 -0
- package/src/security/vault.ts +198 -0
- package/src/security/zip-security.ts +180 -0
- package/src/sync/autosync-config.test.ts +147 -0
- package/src/sync/autosync-config.ts +179 -0
- package/src/sync/autosync-scheduler.test.ts +490 -0
- package/src/sync/autosync-scheduler.ts +646 -0
- package/src/sync/fs.ts +48 -0
- package/src/sync/git/git-transport.test.ts +673 -0
- package/src/sync/git/git-transport.ts +491 -0
- package/src/sync/github-auth.test.ts +280 -0
- package/src/sync/github-auth.ts +356 -0
- package/src/sync/layout.test.ts +270 -0
- package/src/sync/layout.ts +222 -0
- package/src/sync/snapshot-json.test.ts +119 -0
- package/src/sync/snapshot-json.ts +128 -0
- package/src/sync/sync-config.test.ts +310 -0
- package/src/sync/sync-config.ts +299 -0
- package/src/sync/sync-engine.test.ts +1074 -0
- package/src/sync/sync-engine.ts +754 -0
- package/src/sync/sync-history.test.ts +116 -0
- package/src/sync/sync-history.ts +120 -0
- package/src/sync/sync-selection.test.ts +186 -0
- package/src/sync/sync-selection.ts +143 -0
- package/src/sync/sync-session.test.ts +143 -0
- package/src/sync/sync-session.ts +77 -0
- package/src/sync/sync-state.test.ts +164 -0
- package/src/sync/sync-state.ts +131 -0
- package/src/sync/transport.test.ts +111 -0
- package/src/sync/transport.ts +118 -0
- package/src/sync/ui-prefs.test.ts +152 -0
- package/src/sync/ui-prefs.ts +135 -0
- package/src/sync/webdav/webdav-transport.test.ts +871 -0
- package/src/sync/webdav/webdav-transport.ts +535 -0
- package/src/ui/errors.test.ts +47 -0
- package/src/ui/errors.ts +100 -0
- package/src/ui/export-flow.test.ts +87 -0
- package/src/ui/export-flow.ts +156 -0
- package/src/ui/i18n.ts +590 -0
- package/src/ui/migration-consult-view.test.ts +151 -0
- package/src/ui/migration-consult-view.ts +178 -0
- package/src/ui/progress.test.ts +47 -0
- package/src/ui/progress.ts +102 -0
- package/src/ui/report.test.ts +101 -0
- package/src/ui/report.ts +146 -0
- package/src/ui/test-helpers.ts +184 -0
- package/src/ui/types.ts +325 -0
- package/src/utils/atomic-write.test.ts +312 -0
- package/src/utils/atomic-write.ts +422 -0
- package/src/utils/env-lock.test.ts +999 -0
- package/src/utils/env-lock.ts +1086 -0
- package/src/utils/hashing.ts +53 -0
- package/src/utils/json.ts +77 -0
- package/src/utils/logger.ts +114 -0
- package/src/utils/paths.ts +176 -0
- package/src/utils/proxy.test.ts +465 -0
- package/src/utils/proxy.ts +493 -0
- package/src/utils/recursive-walk.test.ts +148 -0
- package/src/utils/recursive-walk.ts +145 -0
- package/src/utils/zip.ts +332 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DshPluginsFacade / ensureActivationRow 测试(M5 补写,failing-first 目标):
|
|
3
|
+
* - install 无 marketplace 时走官方 dsh plugin CLI 通道,且绝不抛「插件市场服务不可用」
|
|
4
|
+
* - listInstalled 委托 profile 文件实时读取(真实版本)
|
|
5
|
+
* - ensureActivationRow 幂等(重复安装不重复行;bundle 包跳过)
|
|
6
|
+
*
|
|
7
|
+
* 通过注入 mock runner(DshPluginsFacade 构造器第 4 参)拦截子进程,不触发真实
|
|
8
|
+
* dsh/pnpm;profile 目录用真实临时目录(node:fs),node_modules 落盘 package.json
|
|
9
|
+
* 驱动 hasDshBundlePatch 判定。
|
|
10
|
+
*/
|
|
11
|
+
import test from 'node:test';
|
|
12
|
+
import assert from 'node:assert/strict';
|
|
13
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
|
|
17
|
+
import { DshPluginsFacade, ensureActivationRow } from './index.ts';
|
|
18
|
+
import { resolveProfileDir } from './core/plugin-cli.ts';
|
|
19
|
+
import type { DshPluginResult } from './core/plugin-cli.ts';
|
|
20
|
+
import type { PatchChange, PatchFileFacade } from './core/types.ts';
|
|
21
|
+
|
|
22
|
+
/* ------------------------------------------------ mock 基础设施 */
|
|
23
|
+
|
|
24
|
+
class MemPatchFile implements PatchFileFacade {
|
|
25
|
+
lines = new Map<string, { lineId: string; raw: unknown }>();
|
|
26
|
+
async readPatchLines(_file: string): Promise<{ lineId: string; raw: unknown }[]> {
|
|
27
|
+
return [...this.lines.values()];
|
|
28
|
+
}
|
|
29
|
+
async applyPatchChanges(_file: string, changes: PatchChange[]): Promise<void> {
|
|
30
|
+
for (const c of changes) {
|
|
31
|
+
if (c.action === 'remove') this.lines.delete(c.lineId);
|
|
32
|
+
else this.lines.set(c.lineId, { lineId: c.lineId, raw: c.raw });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface TempProfile {
|
|
38
|
+
homeDir: string;
|
|
39
|
+
profileDir: string;
|
|
40
|
+
cleanup: () => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function makeTempProfile(): TempProfile {
|
|
44
|
+
const homeDir = mkdtempSync(join(tmpdir(), 'dsh-cm-facade-'));
|
|
45
|
+
const profileDir = resolveProfileDir(homeDir, 'web');
|
|
46
|
+
mkdirSync(profileDir, { recursive: true });
|
|
47
|
+
return {
|
|
48
|
+
homeDir,
|
|
49
|
+
profileDir,
|
|
50
|
+
cleanup: () => rmSync(homeDir, { recursive: true, force: true }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 写 node_modules/<name>/package.json;bundlePatch 缺省 = 非 bundle。 */
|
|
55
|
+
function writeInstalledPkg(profileDir: string, name: string, version: string, bundlePatch?: string): void {
|
|
56
|
+
const pkgDir = join(profileDir, 'node_modules', name);
|
|
57
|
+
mkdirSync(pkgDir, { recursive: true });
|
|
58
|
+
const manifest: Record<string, unknown> = { name, version };
|
|
59
|
+
if (bundlePatch !== undefined) manifest['dsh'] = { bundle: { patch: bundlePatch } };
|
|
60
|
+
writeFileSync(join(pkgDir, 'package.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function okResult(): DshPluginResult {
|
|
64
|
+
return { exitCode: 0, timedOut: false, stdout: '', stderr: '' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ------------------------------------------------------- 测试主体 */
|
|
68
|
+
|
|
69
|
+
test('install: 无 marketplace 时走 CLI 通道(mock runner 记录 argv),成功 → needsRestart + 非 bundle 补激活行', async () => {
|
|
70
|
+
const { homeDir, profileDir, cleanup } = makeTempProfile();
|
|
71
|
+
try {
|
|
72
|
+
writeInstalledPkg(profileDir, 'pkg-a', '1.0.0'); // 非 bundle
|
|
73
|
+
const calls: { profileDir: string; profile: string; args: string[] }[] = [];
|
|
74
|
+
const runner = async (p: string, profile: string, args: readonly string[]): Promise<DshPluginResult> => {
|
|
75
|
+
calls.push({ profileDir: p, profile, args: [...args] });
|
|
76
|
+
return okResult();
|
|
77
|
+
};
|
|
78
|
+
const patchFile = new MemPatchFile();
|
|
79
|
+
const facade = new DshPluginsFacade(homeDir, 'web', patchFile, runner);
|
|
80
|
+
|
|
81
|
+
const r = await facade.install('pkg-a');
|
|
82
|
+
assert.equal(r.needsRestart, true);
|
|
83
|
+
assert.equal(calls.length, 1, 'install 恰好一次 CLI 调用');
|
|
84
|
+
assert.equal(calls[0]?.profileDir, profileDir);
|
|
85
|
+
assert.equal(calls[0]?.profile, 'web');
|
|
86
|
+
assert.deepEqual(calls[0]?.args, ['add', 'pkg-a'], '必须构造 dsh plugin --profile web add pkg-a 的 argv');
|
|
87
|
+
// 非 bundle:成功路径幂等补激活行
|
|
88
|
+
assert.deepEqual(
|
|
89
|
+
[...patchFile.lines.keys()],
|
|
90
|
+
['pm-pkg-a'],
|
|
91
|
+
'非 bundle 插件安装后写入 pm-<slug> 激活行',
|
|
92
|
+
);
|
|
93
|
+
assert.deepEqual(patchFile.lines.get('pm-pkg-a')?.raw, { id: 'pm-pkg-a', name: 'pkg-a' });
|
|
94
|
+
} finally {
|
|
95
|
+
cleanup();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('install: bundle 包成功 → 不补 patch 行(reconcile 维护 bundles)', async () => {
|
|
100
|
+
const { homeDir, profileDir, cleanup } = makeTempProfile();
|
|
101
|
+
try {
|
|
102
|
+
writeInstalledPkg(profileDir, 'pkg-bundle', '1.0.0', 'patch.yml'); // bundle
|
|
103
|
+
const runner = async (): Promise<DshPluginResult> => okResult();
|
|
104
|
+
const patchFile = new MemPatchFile();
|
|
105
|
+
const facade = new DshPluginsFacade(homeDir, 'web', patchFile, runner);
|
|
106
|
+
|
|
107
|
+
await facade.install('pkg-bundle');
|
|
108
|
+
assert.equal(patchFile.lines.size, 0, 'bundle 包不写 patch 行');
|
|
109
|
+
} finally {
|
|
110
|
+
cleanup();
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('install: 非 registry spec(github:)→ add 按 spec 安装;registry 版本区间 → 裸包名(npm 最新)', async () => {
|
|
115
|
+
const { homeDir, profileDir, cleanup } = makeTempProfile();
|
|
116
|
+
try {
|
|
117
|
+
writeInstalledPkg(profileDir, 'dsh-memory-evolve', '1.0.0');
|
|
118
|
+
writeInstalledPkg(profileDir, 'dshmarket', '1.0.3');
|
|
119
|
+
const calls: { args: string[] }[] = [];
|
|
120
|
+
const runner = async (_p: string, _profile: string, args: readonly string[]): Promise<DshPluginResult> => {
|
|
121
|
+
calls.push({ args: [...args] });
|
|
122
|
+
return okResult();
|
|
123
|
+
};
|
|
124
|
+
const facade = new DshPluginsFacade(homeDir, 'web', new MemPatchFile(), runner);
|
|
125
|
+
|
|
126
|
+
await facade.install('dsh-memory-evolve', 'github:csyangwen/dsh-memory-evolve');
|
|
127
|
+
await facade.install('dshmarket', '^1.0.3');
|
|
128
|
+
await facade.install('pkg-plain');
|
|
129
|
+
assert.deepEqual(calls.map((c) => c.args), [
|
|
130
|
+
['add', 'github:csyangwen/dsh-memory-evolve'],
|
|
131
|
+
['add', 'dshmarket'],
|
|
132
|
+
['add', 'pkg-plain'],
|
|
133
|
+
], 'github: 来源按 spec 安装;registry 版本区间与裸包名都走 npm 最新版');
|
|
134
|
+
} finally {
|
|
135
|
+
cleanup();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('install: CLI 失败 → 分类后的可读错误,且绝不出现「插件市场服务不可用」', async () => {
|
|
140
|
+
const { homeDir, profileDir, cleanup } = makeTempProfile();
|
|
141
|
+
try {
|
|
142
|
+
writeInstalledPkg(profileDir, 'pkg-a', '1.0.0');
|
|
143
|
+
const runner = async (): Promise<DshPluginResult> => ({
|
|
144
|
+
exitCode: 1, timedOut: false, stdout: '',
|
|
145
|
+
stderr: 'ERR_PNPM_FETCH_404 GET https://registry.npmjs.org/ghost-pkg: Not Found - 404',
|
|
146
|
+
});
|
|
147
|
+
const facade = new DshPluginsFacade(homeDir, 'web', new MemPatchFile(), runner);
|
|
148
|
+
|
|
149
|
+
await assert.rejects(
|
|
150
|
+
() => facade.install('pkg-a'),
|
|
151
|
+
(err: unknown) => {
|
|
152
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
153
|
+
assert.match(msg, /fetch-404/, '错误必须带分类 code');
|
|
154
|
+
assert.doesNotMatch(msg, /插件市场服务不可用/, 'CLI 通道失败绝不能回退到 marketplace 报错');
|
|
155
|
+
return true;
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
} finally {
|
|
159
|
+
cleanup();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('install: 激活行补写失败 → 明确报错(不吞),允许重试幂等补行', async () => {
|
|
164
|
+
const { homeDir, profileDir, cleanup } = makeTempProfile();
|
|
165
|
+
try {
|
|
166
|
+
writeInstalledPkg(profileDir, 'pkg-a', '1.0.0');
|
|
167
|
+
const runner = async (): Promise<DshPluginResult> => okResult();
|
|
168
|
+
const brokenPatch = new MemPatchFile();
|
|
169
|
+
brokenPatch.applyPatchChanges = async () => {
|
|
170
|
+
throw new Error('disk full');
|
|
171
|
+
};
|
|
172
|
+
const facade = new DshPluginsFacade(homeDir, 'web', brokenPatch, runner);
|
|
173
|
+
|
|
174
|
+
await assert.rejects(
|
|
175
|
+
() => facade.install('pkg-a'),
|
|
176
|
+
/激活行写入 profile 补丁失败/,
|
|
177
|
+
);
|
|
178
|
+
} finally {
|
|
179
|
+
cleanup();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('listInstalled: 委托 profile 文件实时读取,返回真实落盘版本', async () => {
|
|
184
|
+
const { homeDir, profileDir, cleanup } = makeTempProfile();
|
|
185
|
+
try {
|
|
186
|
+
writeFileSync(
|
|
187
|
+
join(profileDir, 'package.json'),
|
|
188
|
+
JSON.stringify({
|
|
189
|
+
name: 'dsh-profile-web',
|
|
190
|
+
dependencies: { '@linxin666/dsh-ssh': '^0.1.0', 'pkg-a': '1.0.0', '@deepseek-ai/dsh-base': '0.1.0-rc.6' },
|
|
191
|
+
dsh: { profile: { bundles: ['@linxin666/dsh-ssh'] } },
|
|
192
|
+
}, null, 2) + '\n',
|
|
193
|
+
'utf8',
|
|
194
|
+
);
|
|
195
|
+
writeInstalledPkg(profileDir, '@linxin666/dsh-ssh', '0.1.12', 'patch.yml');
|
|
196
|
+
writeInstalledPkg(profileDir, 'pkg-a', '1.0.0');
|
|
197
|
+
writeInstalledPkg(profileDir, '@deepseek-ai/dsh-base', '0.1.0-rc.6');
|
|
198
|
+
|
|
199
|
+
const facade = new DshPluginsFacade(homeDir, 'web', new MemPatchFile(), async () => okResult());
|
|
200
|
+
const list = await facade.listInstalled();
|
|
201
|
+
const ssh = list.find((p) => p.name === '@linxin666/dsh-ssh');
|
|
202
|
+
assert.equal(ssh?.version, '0.1.12', '真实落盘版本(声明是 ^0.1.0)');
|
|
203
|
+
assert.equal(ssh?.isBundle, true);
|
|
204
|
+
assert.equal(list.some((p) => p.name === '@deepseek-ai/dsh-base'), false);
|
|
205
|
+
} finally {
|
|
206
|
+
cleanup();
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('ensureActivationRow: 幂等——重复安装不重复行', async () => {
|
|
211
|
+
const { profileDir, cleanup } = makeTempProfile();
|
|
212
|
+
try {
|
|
213
|
+
writeInstalledPkg(profileDir, 'pkg-a', '1.0.0');
|
|
214
|
+
const patchFile = new MemPatchFile();
|
|
215
|
+
|
|
216
|
+
await ensureActivationRow(patchFile, join(profileDir, 'node_modules', 'pkg-a'), 'pkg-a');
|
|
217
|
+
await ensureActivationRow(patchFile, join(profileDir, 'node_modules', 'pkg-a'), 'pkg-a');
|
|
218
|
+
await ensureActivationRow(patchFile, join(profileDir, 'node_modules', 'pkg-a'), 'pkg-a');
|
|
219
|
+
assert.equal(patchFile.lines.size, 1, '三次调用只产生一行');
|
|
220
|
+
} finally {
|
|
221
|
+
cleanup();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('ensureActivationRow: 已有同 name 行(任意 id)→ 不重复插入', async () => {
|
|
226
|
+
const { profileDir, cleanup } = makeTempProfile();
|
|
227
|
+
try {
|
|
228
|
+
writeInstalledPkg(profileDir, 'pkg-a', '1.0.0');
|
|
229
|
+
const patchFile = new MemPatchFile();
|
|
230
|
+
patchFile.lines.set('user-line', { lineId: 'user-line', raw: { id: 'user-line', name: 'pkg-a' } });
|
|
231
|
+
|
|
232
|
+
await ensureActivationRow(patchFile, join(profileDir, 'node_modules', 'pkg-a'), 'pkg-a');
|
|
233
|
+
assert.equal(patchFile.lines.size, 1, '按 name 去重,不新增行');
|
|
234
|
+
assert.equal(patchFile.lines.has('user-line'), true);
|
|
235
|
+
} finally {
|
|
236
|
+
cleanup();
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('ensureActivationRow: bundle 包跳过(reconcile 已维护 bundles)', async () => {
|
|
241
|
+
const { profileDir, cleanup } = makeTempProfile();
|
|
242
|
+
try {
|
|
243
|
+
writeInstalledPkg(profileDir, 'pkg-bundle', '1.0.0', 'patch.yml');
|
|
244
|
+
const patchFile = new MemPatchFile();
|
|
245
|
+
|
|
246
|
+
await ensureActivationRow(patchFile, join(profileDir, 'node_modules', 'pkg-bundle'), 'pkg-bundle');
|
|
247
|
+
assert.equal(patchFile.lines.size, 0, 'bundle 包不写 patch 行');
|
|
248
|
+
} finally {
|
|
249
|
+
cleanup();
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('ensureActivationRow: scope 包名 slug 形态正确(@scope/name → pm-scope-name)', async () => {
|
|
254
|
+
const { profileDir, cleanup } = makeTempProfile();
|
|
255
|
+
try {
|
|
256
|
+
writeInstalledPkg(profileDir, '@org/pkg-a', '1.0.0');
|
|
257
|
+
const patchFile = new MemPatchFile();
|
|
258
|
+
|
|
259
|
+
await ensureActivationRow(patchFile, join(profileDir, 'node_modules', '@org', 'pkg-a'), '@org/pkg-a');
|
|
260
|
+
assert.equal(patchFile.lines.size, 1);
|
|
261
|
+
assert.equal(patchFile.lines.has('pm-org-pkg-a'), true, '去 @ 后连字符 slug');
|
|
262
|
+
} finally {
|
|
263
|
+
cleanup();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3:Host 同步路由的 transport 分支解析与校验测试(failing-first 目标)。
|
|
3
|
+
*
|
|
4
|
+
* 直接测导出的纯函数(仿 buildRestoreBody 模式),不依赖真实 Cordis Context:
|
|
5
|
+
* - parseSyncBody:按 body.transport 分支解析 git/webdav(可辨识联合 SyncConfig);
|
|
6
|
+
* 必填校验;validateWebDavUrl/validateRepoUrl;password/token 非空写入对应
|
|
7
|
+
* 独立 credentials ref(SYNC_WEBDAV_CREDENTIAL_REF / SYNC_CREDENTIAL_REF)。
|
|
8
|
+
* - webdavBaseUrl:由 webdav.url 合成通道 baseUrl(尾部 '/');git 通道返回 ''。
|
|
9
|
+
* - SyncRouteError:非 2xx 请求级错误。
|
|
10
|
+
*
|
|
11
|
+
* 凭据用内存 mock(记录 set 的 (ref, value)),不触碰真实 DSH credentials。
|
|
12
|
+
*/
|
|
13
|
+
import test from 'node:test';
|
|
14
|
+
import assert from 'node:assert/strict';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
parseSyncBody, webdavBaseUrl, extractSyncSections, mergePersistedWebDavUsername, SyncRouteError,
|
|
18
|
+
SYNC_CREDENTIAL_REF, SYNC_WEBDAV_CREDENTIAL_REF,
|
|
19
|
+
type ParseSyncBodyDeps,
|
|
20
|
+
} from './index.ts';
|
|
21
|
+
import { isWebDavConfig, type SyncConfig } from './sync/sync-config.ts';
|
|
22
|
+
|
|
23
|
+
/** 内存凭据 mock:记录写入的 (ref, value) 列表 */
|
|
24
|
+
function memCreds(): ParseSyncBodyDeps & { writes: Array<[string, string]>; } {
|
|
25
|
+
const writes: Array<[string, string]> = [];
|
|
26
|
+
return {
|
|
27
|
+
writes,
|
|
28
|
+
credentials: {
|
|
29
|
+
async set(ref: unknown, value: string) {
|
|
30
|
+
writes.push([String(ref), value]);
|
|
31
|
+
},
|
|
32
|
+
} as unknown as ParseSyncBodyDeps['credentials'],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ---------------- git 分支 ---------------- */
|
|
37
|
+
|
|
38
|
+
test('parseSyncBody: git 分支(缺省 transport)解析 repoUrl(gitBin 已废弃),token 写入 SYNC_CREDENTIAL_REF', async () => {
|
|
39
|
+
const deps = memCreds();
|
|
40
|
+
const cfg = await parseSyncBody(
|
|
41
|
+
{ repoUrl: 'https://github.com/foo/bar.git', gitBin: '/usr/bin/git', token: 'tok-123' },
|
|
42
|
+
deps,
|
|
43
|
+
);
|
|
44
|
+
assert.deepEqual(cfg, {
|
|
45
|
+
schemaVersion: 2,
|
|
46
|
+
transport: 'git',
|
|
47
|
+
git: { repoUrl: 'https://github.com/foo/bar.git' },
|
|
48
|
+
});
|
|
49
|
+
assert.deepEqual(deps.writes, [[SYNC_CREDENTIAL_REF, 'tok-123']], 'token 写 git credentials ref,不写 webdav ref');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('parseSyncBody: git 分支无 token → 不写凭据', async () => {
|
|
53
|
+
const deps = memCreds();
|
|
54
|
+
const cfg = await parseSyncBody({ repoUrl: 'git@github.com:foo/bar.git' }, deps);
|
|
55
|
+
assert.deepEqual(cfg, { schemaVersion: 2, transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } });
|
|
56
|
+
assert.equal(deps.writes.length, 0, '无 token 不产生凭据写入');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('parseSyncBody: git 分支缺 repoUrl → SyncRouteError', async () => {
|
|
60
|
+
const deps = memCreds();
|
|
61
|
+
await assert.rejects(
|
|
62
|
+
() => parseSyncBody({ transport: 'git' }, deps),
|
|
63
|
+
(err) => err instanceof SyncRouteError && /repoUrl is required/.test(err.message),
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('parseSyncBody: git 分支 repoUrl 含 userinfo(token 拼 URL)→ 拒绝', async () => {
|
|
68
|
+
const deps = memCreds();
|
|
69
|
+
await assert.rejects(
|
|
70
|
+
() => parseSyncBody({ repoUrl: 'https://user:pass@github.com/foo/bar.git' }, deps),
|
|
71
|
+
(err) => err instanceof SyncRouteError,
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/* ---------------- webdav 分支 ---------------- */
|
|
76
|
+
|
|
77
|
+
test('parseSyncBody: webdav 分支解析 url/username,password 写入 SYNC_WEBDAV_CREDENTIAL_REF', async () => {
|
|
78
|
+
const deps = memCreds();
|
|
79
|
+
const cfg = await parseSyncBody(
|
|
80
|
+
{
|
|
81
|
+
transport: 'webdav',
|
|
82
|
+
url: 'https://dav.example.com/remote.php/dav/files/user',
|
|
83
|
+
username: 'alice',
|
|
84
|
+
password: 'pw-secret',
|
|
85
|
+
},
|
|
86
|
+
deps,
|
|
87
|
+
);
|
|
88
|
+
assert.deepEqual(cfg, {
|
|
89
|
+
schemaVersion: 2,
|
|
90
|
+
transport: 'webdav',
|
|
91
|
+
webdav: { url: 'https://dav.example.com/remote.php/dav/files/user', username: 'alice' },
|
|
92
|
+
});
|
|
93
|
+
assert.deepEqual(deps.writes, [[SYNC_WEBDAV_CREDENTIAL_REF, 'pw-secret']], 'password 写独立 webdav credentials ref');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('parseSyncBody: webdav 分支缺 url → SyncRouteError', async () => {
|
|
97
|
+
const deps = memCreds();
|
|
98
|
+
await assert.rejects(
|
|
99
|
+
() => parseSyncBody({ transport: 'webdav', username: 'alice' }, deps),
|
|
100
|
+
(err) => err instanceof SyncRouteError && /url is required/.test(err.message),
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('parseSyncBody: webdav 分支 url 非 http(s)/含 userinfo → 拒绝', async () => {
|
|
105
|
+
const deps = memCreds();
|
|
106
|
+
await assert.rejects(
|
|
107
|
+
() => parseSyncBody({ transport: 'webdav', url: 'ftp://dav.example.com' }, deps),
|
|
108
|
+
(err) => err instanceof SyncRouteError,
|
|
109
|
+
);
|
|
110
|
+
await assert.rejects(
|
|
111
|
+
() => parseSyncBody({ transport: 'webdav', url: 'https://u:p@dav.example.com' }, deps),
|
|
112
|
+
(err) => err instanceof SyncRouteError,
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('parseSyncBody: webdav 分支无 password → 不写凭据', async () => {
|
|
117
|
+
const deps = memCreds();
|
|
118
|
+
const cfg = await parseSyncBody({ transport: 'webdav', url: 'https://dav.example.com' }, deps);
|
|
119
|
+
assert.equal(cfg.transport, 'webdav');
|
|
120
|
+
assert.equal(deps.writes.length, 0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('parseSyncBody: HTTP 凭据写入失败 → SyncRouteError(含 ref 名,无 secret)', async () => {
|
|
124
|
+
const deps: ParseSyncBodyDeps = {
|
|
125
|
+
credentials: {
|
|
126
|
+
async set() { throw new Error('denied'); },
|
|
127
|
+
} as unknown as ParseSyncBodyDeps['credentials'],
|
|
128
|
+
};
|
|
129
|
+
await assert.rejects(
|
|
130
|
+
() => parseSyncBody({ transport: 'webdav', url: 'https://dav.example.com', password: 'pw' }, deps),
|
|
131
|
+
(err) => err instanceof SyncRouteError && err.message.includes(SYNC_WEBDAV_CREDENTIAL_REF) && !err.message.includes('pw'),
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/* ---------------- webdavBaseUrl ---------------- */
|
|
136
|
+
|
|
137
|
+
test('webdavBaseUrl: webdav.url 作为 base,尾部规范化带 /;git 通道返回空', () => {
|
|
138
|
+
const webdav: SyncConfig = { schemaVersion: 2, transport: 'webdav', webdav: { url: 'https://dav.example.com/remote.php/dav/files/user' } };
|
|
139
|
+
assert.equal(webdavBaseUrl(webdav), 'https://dav.example.com/remote.php/dav/files/user/');
|
|
140
|
+
assert.equal(
|
|
141
|
+
webdavBaseUrl({ ...webdav, webdav: { url: 'https://dav.example.com/' } }),
|
|
142
|
+
'https://dav.example.com/',
|
|
143
|
+
);
|
|
144
|
+
const git: SyncConfig = { schemaVersion: 2, transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } };
|
|
145
|
+
assert.equal(webdavBaseUrl(git), '', 'git 通道无 webdav baseUrl');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/* ---------------- extractSyncSections(push 分区选择) ---------------- */
|
|
149
|
+
|
|
150
|
+
const KNOWN = new Set(['settings', 'providers', 'plugins', 'skills']);
|
|
151
|
+
|
|
152
|
+
test('extractSyncSections: 缺省 / 非数组 / 空数组 → undefined(默认全量推荐分区)', () => {
|
|
153
|
+
assert.equal(extractSyncSections({ repoUrl: 'x' }, KNOWN), undefined);
|
|
154
|
+
assert.equal(extractSyncSections({ sections: 'settings' }, KNOWN), undefined, '非数组视为未指定');
|
|
155
|
+
assert.equal(extractSyncSections({ sections: [] }, KNOWN), undefined, '空数组视为未指定');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('extractSyncSections: 合法分区 → 去重保序返回 SectionId[]', () => {
|
|
159
|
+
assert.deepEqual(
|
|
160
|
+
extractSyncSections({ sections: ['plugins', 'settings', 'settings'] }, KNOWN),
|
|
161
|
+
['plugins', 'settings'],
|
|
162
|
+
'重复分区去重,保持首次出现顺序',
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('extractSyncSections: 元素非字符串 / 空串 → SyncRouteError', () => {
|
|
167
|
+
assert.throws(
|
|
168
|
+
() => extractSyncSections({ sections: ['settings', 42] }, KNOWN),
|
|
169
|
+
(err) => err instanceof SyncRouteError && /sections/.test(err.message),
|
|
170
|
+
);
|
|
171
|
+
assert.throws(
|
|
172
|
+
() => extractSyncSections({ sections: [''] }, KNOWN),
|
|
173
|
+
(err) => err instanceof SyncRouteError,
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('extractSyncSections: 元素为未知分区 id → SyncRouteError(不静默吞错)', () => {
|
|
178
|
+
assert.throws(
|
|
179
|
+
() => extractSyncSections({ sections: ['settings', 'nope'] }, KNOWN),
|
|
180
|
+
(err) => err instanceof SyncRouteError && /nope/.test(err.message),
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/* ---------------- mergePersistedWebDavUsername(prepareSync username 回退) ---------------- */
|
|
185
|
+
|
|
186
|
+
const WEBDAV_CFG: SyncConfig = { schemaVersion: 2, transport: 'webdav', webdav: { url: 'https://dav.example.com/dav' } };
|
|
187
|
+
const WEBDAV_WITH_USER: SyncConfig = {
|
|
188
|
+
schemaVersion: 2, transport: 'webdav', webdav: { url: 'https://dav.example.com/dav', username: 'alice' },
|
|
189
|
+
};
|
|
190
|
+
const GIT_CFG: SyncConfig = { schemaVersion: 2, transport: 'git', git: { repoUrl: 'git@github.com:foo/bar.git' } };
|
|
191
|
+
|
|
192
|
+
test('mergePersistedWebDavUsername: webdav 缺 username + 持久化有 username → 回填(snapshotsList 挂载自动加载场景)', () => {
|
|
193
|
+
const cfg = mergePersistedWebDavUsername({ ...WEBDAV_CFG }, { ...WEBDAV_WITH_USER });
|
|
194
|
+
assert.ok(isWebDavConfig(cfg) && cfg.webdav.username === 'alice', '回填持久化 username');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('mergePersistedWebDavUsername: 请求已带 username → 保留新值不回填(用户输入优先)', () => {
|
|
198
|
+
const cfg = mergePersistedWebDavUsername(
|
|
199
|
+
{ ...WEBDAV_CFG, webdav: { ...WEBDAV_CFG.webdav, username: 'bob' } },
|
|
200
|
+
{ ...WEBDAV_WITH_USER },
|
|
201
|
+
);
|
|
202
|
+
assert.ok(isWebDavConfig(cfg) && cfg.webdav.username === 'bob');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('mergePersistedWebDavUsername: 持久化无 username / 非 webdav / 为 null → 原样返回', () => {
|
|
206
|
+
const noUser = mergePersistedWebDavUsername({ ...WEBDAV_CFG }, { ...WEBDAV_CFG });
|
|
207
|
+
assert.ok(isWebDavConfig(noUser) && noUser.webdav.username === undefined, '持久化无 username 不回填');
|
|
208
|
+
|
|
209
|
+
const gitPersisted = mergePersistedWebDavUsername({ ...WEBDAV_CFG }, { ...GIT_CFG });
|
|
210
|
+
assert.ok(isWebDavConfig(gitPersisted) && gitPersisted.webdav.username === undefined, '持久化是 git 通道不回填');
|
|
211
|
+
|
|
212
|
+
const nullPersisted = mergePersistedWebDavUsername({ ...WEBDAV_CFG }, null);
|
|
213
|
+
assert.ok(isWebDavConfig(nullPersisted) && nullPersisted.webdav.username === undefined, '无持久化配置原样返回');
|
|
214
|
+
|
|
215
|
+
const gitCfg = mergePersistedWebDavUsername({ ...GIT_CFG }, { ...WEBDAV_WITH_USER });
|
|
216
|
+
assert.deepEqual(gitCfg, GIT_CFG, 'git 请求体不受影响');
|
|
217
|
+
});
|