test: better sync coverage

This commit is contained in:
zhom
2026-08-26 02:13:00 +04:00
parent e873a72387
commit abe210eda3
23 changed files with 582 additions and 84 deletions
+28 -28
View File
@@ -572,9 +572,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64"
version = "0.23.0"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9"
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "base64ct"
@@ -1046,9 +1046,9 @@ dependencies = [
[[package]]
name = "cfg-expr"
version = "0.20.8"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c"
checksum = "fe4ece8474b5f766c63426647e7b4b316b67431ade1036a8313cee24a03ae917"
dependencies = [
"smallvec",
"target-lexicon 0.13.5",
@@ -1149,9 +1149,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.6.4"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7"
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
dependencies = [
"clap_builder",
"clap_derive",
@@ -1159,9 +1159,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.6.2"
version = "4.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
dependencies = [
"anstream",
"anstyle",
@@ -1178,7 +1178,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 3.0.3",
"syn 3.0.4",
]
[[package]]
@@ -2391,9 +2391,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
dependencies = [
"futures-core",
"futures-sink",
@@ -2401,9 +2401,9 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-executor"
@@ -2418,9 +2418,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
[[package]]
name = "futures-lite"
@@ -2437,32 +2437,32 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.118",
"syn 3.0.4",
]
[[package]]
name = "futures-sink"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
[[package]]
name = "futures-task"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.33"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-channel",
"futures-core",
@@ -6443,9 +6443,9 @@ dependencies = [
[[package]]
name = "syn"
version = "3.0.3"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [
"proc-macro2",
"quote",
@@ -6536,7 +6536,7 @@ version = "7.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7"
dependencies = [
"cfg-expr 0.20.8",
"cfg-expr 0.20.9",
"heck 0.5.0",
"pkg-config",
"toml 1.1.2+spec-1.1.0",
@@ -7038,7 +7038,7 @@ checksum = "535cd782aac407a0bbf593013306e89e821017e3862ddd7b36925aa80a29e649"
dependencies = [
"async-trait",
"axum",
"base64 0.23.0",
"base64 0.23.1",
"block2",
"cairo-rs",
"clap",
+1
View File
@@ -212,6 +212,7 @@ export const commandCoverage = {
commands: [
"get_sync_settings",
"save_sync_settings",
"check_sync_server_connection",
"cloud_auth::restart_sync_service",
"set_profile_sync_mode",
"cancel_profile_sync",
+28 -15
View File
@@ -2,6 +2,7 @@
import { spawn, spawnSync } from "node:child_process";
import {
copyFileSync,
createReadStream,
createWriteStream,
existsSync,
@@ -44,6 +45,9 @@ const driverBinary = path.join(
"bin",
`tauri-wd${executableSuffix}`,
);
const appManifest = path.join(appManifestDir, "Cargo.toml");
const appLockfile = path.join(appManifestDir, "Cargo.lock");
const donutLockfile = path.join(projectRoot, "src-tauri", "Cargo.lock");
const suiteFiles = {
smoke: ["diagnostics.test.mjs", "smoke.test.mjs", "coverage.test.mjs"],
@@ -237,16 +241,13 @@ async function loadLocalValues(names) {
return values;
}
function lockedDriverVersion() {
const lockfile = readFileSync(
path.join(appManifestDir, "Cargo.lock"),
"utf8",
);
const match = lockfile.match(
/\[\[package\]\]\s*\nname = "tauri-wd"\s*\nversion = "([^"]+)"/,
);
function pinnedDriverVersion() {
const manifest = readFileSync(appManifest, "utf8");
const match = manifest.match(/^tauri-wd\s*=\s*"=([^"]+)"$/m);
if (!match) {
throw new Error("e2e/app/Cargo.lock does not resolve a tauri-wd version");
throw new Error(
'e2e/app/Cargo.toml must pin tauri-wd to an exact version, e.g. tauri-wd = "=0.1.11"',
);
}
return match[1];
}
@@ -263,7 +264,7 @@ function installedDriverVersion() {
}
function ensureDriver() {
const version = lockedDriverVersion();
const version = pinnedDriverVersion();
if (installedDriverVersion() === version) {
log(`tauri-wd ${version} already installed at ${driverBinary}`);
return;
@@ -285,15 +286,27 @@ function ensureDriver() {
);
}
// The harness links the Donut crate, so it has to resolve the same versions
// Donut itself ships. Seeding the harness lockfile from src-tauri/Cargo.lock
// keeps the two in step whenever a dependency or the app version moves; cargo
// fills in the harness-only packages on top. It is generated, never hand-edited.
function syncHarnessLockfile() {
if (
existsSync(appLockfile) &&
statSync(appLockfile).mtimeMs >= statSync(donutLockfile).mtimeMs
) {
return;
}
copyFileSync(donutLockfile, appLockfile);
log("seeded e2e/app/Cargo.lock from src-tauri/Cargo.lock");
}
function buildAll() {
run("pnpm", ["build"], projectRoot);
run("pnpm", ["copy-proxy-binary"], projectRoot);
run(process.execPath, ["src-tauri/download-xray.mjs"], projectRoot);
run(
"cargo",
["build", "--locked", "--manifest-path", "e2e/app/Cargo.toml"],
projectRoot,
);
syncHarnessLockfile();
run("cargo", ["build", "--manifest-path", "e2e/app/Cargo.toml"], projectRoot);
ensureDriver();
}
+83
View File
@@ -1,5 +1,6 @@
import assert from "node:assert/strict";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { createServer } from "node:http";
import path from "node:path";
import test from "node:test";
import { appFromEnvironment } from "../lib/app.mjs";
@@ -575,3 +576,85 @@ test("global config sealing and encrypted profile sync reject a wrong password,
]);
}
});
// A self-hosted server reaches its storage over an address only it can
// resolve — the documented compose file points S3_ENDPOINT at
// http://minio:9000, a Docker service name that exists on the compose network
// and nowhere else. Files never travel through the sync server, so every
// presigned URL then names a host the desktop cannot open: /health and /readyz
// stay green while every single transfer dies at connect. Reported as "the
// endpoint connection works every time, but no MB is ever synced".
test("the connection check fails a server whose storage host this device cannot reach", async () => {
assert.ok(syncUrl && syncToken, "Sync infrastructure was not started");
const app = appFromEnvironment("sync-preflight");
// Answers exactly like a healthy self-hosted server that signs presigned
// URLs against a container-only host.
const misconfigured = createServer((request, response) => {
if (request.url === "/readyz") {
response.writeHead(200, { "content-type": "application/json" });
response.end(
JSON.stringify({
status: "ready",
s3: true,
storageEndpoint: "http://minio.invalid:9000",
}),
);
return;
}
response.writeHead(404);
response.end();
});
await new Promise((resolve) => misconfigured.listen(0, "127.0.0.1", resolve));
const misconfiguredUrl = `http://127.0.0.1:${misconfigured.address().port}`;
try {
await app.start();
const healthy = await app.invoke("check_sync_server_connection", {
serverUrl: syncUrl,
});
assert.equal(healthy.server_reachable, true, "real sync server answers");
assert.notEqual(
healthy.storage_reachable,
false,
"the suite's own storage must be reachable from the test device",
);
// The regression itself: green server, storage nobody here can open.
const broken = await app.invoke("check_sync_server_connection", {
serverUrl: misconfiguredUrl,
});
assert.equal(broken.server_reachable, true, "server itself answered");
assert.equal(broken.storage_ready, true, "server reaches its own storage");
assert.equal(broken.storage_endpoint, "http://minio.invalid:9000");
assert.equal(
broken.storage_reachable,
false,
"an unreachable storage host must not report as a working connection",
);
assert.ok(
broken.storage_error && broken.storage_error.length > 0,
"the failure must carry a cause",
);
assert.notEqual(
broken.storage_error,
"error sending request",
"the cause must name the transport failure, not the bare reqwest text",
);
// A server that does not answer at all stays a plain connection failure,
// so the two are never confused in the UI.
const dead = await app.invoke("check_sync_server_connection", {
serverUrl: "http://127.0.0.1:1",
});
assert.equal(dead.server_reachable, false);
assert.equal(dead.storage_reachable, null);
} catch (error) {
await app.capture("failure");
throw error;
} finally {
await new Promise((resolve) => misconfigured.close(resolve));
await app.close();
}
});