feat(web): support WebDAV config sync on web
This commit is contained in:
1 parent
8187ef9baf
commit
4d7d4ab74e
14 files changed
+140
-33
No files matched your search
@@ -0,0 +1,76 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
describe("WebDAV sync HTTP API", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
function stubFetch(response: unknown = undefined) {
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue(response),
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
return fetchMock;
|
||||
}
|
||||
|
||||
function lastCall(fetchMock: ReturnType<typeof stubFetch>): { url: string; body: Record<string, unknown> } {
|
||||
const [url, init] = fetchMock.mock.calls.at(-1) as [string, RequestInit];
|
||||
return { url, body: JSON.parse(String(init.body)) as Record<string, unknown> };
|
||||
}
|
||||
|
||||
const config = {
|
||||
endpoint: "https://dav.example.com/remote.php/dav/files/alice/",
|
||||
username: "alice",
|
||||
password: "app-password",
|
||||
remotePath: "DBX/sync/snapshot.json",
|
||||
};
|
||||
|
||||
it("routes WebDAV connectivity and saved-password operations through the Web backend", async () => {
|
||||
const fetchMock = stubFetch({ hasSavedPassword: true });
|
||||
const { forgetWebdavSavedPassword, saveWebdavSavedPassword, webdavPasswordStatus, webdavSyncTest } = await import("@/lib/backend/http");
|
||||
|
||||
await webdavSyncTest(config);
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/test", body: { config } });
|
||||
|
||||
await expect(webdavPasswordStatus(config)).resolves.toEqual({ hasSavedPassword: true });
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/password-status", body: { config } });
|
||||
|
||||
await saveWebdavSavedPassword(config, "saved-password");
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/save-password", body: { config, password: "saved-password" } });
|
||||
|
||||
await forgetWebdavSavedPassword(config);
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/forget-password", body: { config } });
|
||||
});
|
||||
|
||||
it("routes WebDAV secret preferences and snapshot transfers through the Web backend", async () => {
|
||||
const fetchMock = stubFetch({ bytes: 42, remotePath: config.remotePath });
|
||||
const { forgetWebdavSyncSecretsPassphrase, saveWebdavSyncSecretsPreference, webdavSyncDownload, webdavSyncSecretsStatus, webdavSyncUpload } = await import("@/lib/backend/http");
|
||||
|
||||
await webdavSyncSecretsStatus();
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/sync-secrets-status", body: {} });
|
||||
|
||||
await saveWebdavSyncSecretsPreference(true, "sync-passphrase");
|
||||
expect(lastCall(fetchMock)).toEqual({
|
||||
url: "/api/cloud-sync/webdav/save-sync-secrets-preference",
|
||||
body: { enabled: true, passphrase: "sync-passphrase" },
|
||||
});
|
||||
|
||||
await forgetWebdavSyncSecretsPassphrase();
|
||||
expect(lastCall(fetchMock)).toEqual({ url: "/api/cloud-sync/webdav/forget-sync-secrets-passphrase", body: {} });
|
||||
|
||||
const editorSettings = { theme: "dark" };
|
||||
await webdavSyncUpload(config, editorSettings, "sync-passphrase");
|
||||
expect(lastCall(fetchMock)).toEqual({
|
||||
url: "/api/cloud-sync/webdav/upload",
|
||||
body: { config, editorSettings, secretsPassphrase: "sync-passphrase" },
|
||||
});
|
||||
|
||||
await webdavSyncDownload(config, "sync-passphrase");
|
||||
expect(lastCall(fetchMock)).toEqual({
|
||||
url: "/api/cloud-sync/webdav/download",
|
||||
body: { config, secretsPassphrase: "sync-passphrase" },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -55,6 +55,22 @@ describe("settings search", () => {
|
||||
expect(searchSettings(webEntries, "desktop", "en")).toEqual([]);
|
||||
});
|
||||
|
||||
it("exposes WebDAV sync in Web settings without exposing snippet sync", () => {
|
||||
const webEntries = resolveSettingsSearchEntries(SETTINGS_SEARCH_DEFINITIONS, { isWeb: true, visibleCategories: new Set<SettingsCategory>(["sync"]) }, translate, categoryLabels);
|
||||
|
||||
expect(webEntries.map((entry) => entry.id)).toEqual(["sync-webdav", "sync-webdav-endpoint", "sync-webdav-username", "sync-webdav-password", "sync-webdav-remote-path", "sync-webdav-auto-upload", "sync-secrets", "sync-secrets-passphrase"]);
|
||||
expect(settingsDialogSource).toContain('{ value: "sync", label: t("settings.syncTab") }');
|
||||
expect(settingsDialogSource).not.toContain('...(isWeb ? [] : [{ value: "sync"');
|
||||
expect(settingsDialogSource).toContain('<TabsList v-if="!isWeb"');
|
||||
});
|
||||
|
||||
it("defines the WebDAV Web-runtime notice in every supported locale", () => {
|
||||
for (const locale of ["zh-CN", "zh-TW", "en", "es", "it", "ja", "ko", "pt-BR"]) {
|
||||
const source = readFileSync(new URL(`../../../i18n/locales/${locale}.ts`, import.meta.url), "utf8");
|
||||
expect(source, locale).toContain("syncWebDavWebDescription:");
|
||||
}
|
||||
});
|
||||
|
||||
it("matches Chinese text as a Unicode substring", () => {
|
||||
expect(searchSettings([{ id: "font", category: "editor", title: "界面字体", description: "选择应用字体", categoryLabel: "编辑器", targetId: "editor" }], "字体", "zh-CN").map((entry) => entry.id)).toEqual(["font"]);
|
||||
});
|
||||
|
||||
@@ -211,18 +211,18 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
|
||||
{ id: "tunnels", category: "tunnels", titleKey: "settings.tunnelsTab", targetId: "tunnels" },
|
||||
{ id: "shortcuts", category: "shortcuts", titleKey: "settings.shortcutsTab", targetId: "shortcuts" },
|
||||
{ id: "snippets", category: "snippets", titleKey: "settings.snippetsTab", descriptionKey: "settings.snippetsDescription", targetId: "snippets" },
|
||||
{ id: "sync-webdav", category: "sync", titleKey: "settings.syncWebDavTitle", descriptionKey: "settings.syncWebDavDescription", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-endpoint", category: "sync", titleKey: "settings.syncEndpoint", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-username", category: "sync", titleKey: "settings.syncUsername", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-password", category: "sync", titleKey: "settings.syncPassword", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-remote-path", category: "sync", titleKey: "settings.syncRemotePath", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav-auto-upload", category: "sync", titleKey: "settings.syncAutoUploadInterval", targetId: "sync-webdav", route: { syncMethodTab: "webdav" }, visible: desktopOnly },
|
||||
{ id: "sync-webdav", category: "sync", titleKey: "settings.syncWebDavTitle", descriptionKey: "settings.syncWebDavDescription", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-endpoint", category: "sync", titleKey: "settings.syncEndpoint", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-username", category: "sync", titleKey: "settings.syncUsername", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-password", category: "sync", titleKey: "settings.syncPassword", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-remote-path", category: "sync", titleKey: "settings.syncRemotePath", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-webdav-auto-upload", category: "sync", titleKey: "settings.syncAutoUploadInterval", targetId: "sync-webdav", route: { syncMethodTab: "webdav" } },
|
||||
{ id: "sync-snippet", category: "sync", titleKey: "settings.syncSnippetTitle", descriptionKey: "settings.syncSnippetDescription", targetId: "sync-snippet", route: { syncMethodTab: "snippet" }, visible: desktopOnly },
|
||||
{ id: "sync-snippet-provider", category: "sync", titleKey: "settings.syncSnippetProvider", targetId: "sync-snippet", route: { syncMethodTab: "snippet" }, visible: desktopOnly },
|
||||
{ id: "sync-snippet-id", category: "sync", titleKey: "settings.syncSnippetId", targetId: "sync-snippet", route: { syncMethodTab: "snippet" }, visible: desktopOnly },
|
||||
{ id: "sync-snippet-token", category: "sync", titleKey: "settings.syncSnippetToken", targetId: "sync-snippet", route: { syncMethodTab: "snippet" }, visible: desktopOnly },
|
||||
{ id: "sync-secrets", category: "sync", titleKey: "settings.syncSecrets", targetId: "sync", visible: desktopOnly },
|
||||
{ id: "sync-secrets-passphrase", category: "sync", titleKey: "settings.syncSecretsPassphrase", targetId: "sync", visible: desktopOnly },
|
||||
{ id: "sync-secrets", category: "sync", titleKey: "settings.syncSecrets", targetId: "sync" },
|
||||
{ id: "sync-secrets-passphrase", category: "sync", titleKey: "settings.syncSecretsPassphrase", targetId: "sync" },
|
||||
{ id: "ai-config", category: "ai", titleKey: "ai.configList", targetId: "ai" },
|
||||
{ id: "ai-prompts", category: "ai", titleKey: "ai.promptTemplates", descriptionKey: "ai.promptTemplatesDescription", targetId: "ai" },
|
||||
{ id: "ai-default-mode", category: "ai", titleKey: "ai.defaultAiMode", descriptionKey: "ai.defaultAiModeDescription", targetId: "ai" },
|
||||
|
||||
Reference in new issue
Block a user