From 5b14158870e4db1a10adc298e1986a25d18807be Mon Sep 17 00:00:00 2001 From: Rogee Date: Tue, 25 Aug 2026 11:22:21 +0800 Subject: [PATCH] HH-649: strengthen legacy migration tests (#7) --- internal/bootstrap/bootstrap_test.go | 39 +++++++++++++++ tests/container-smoke.sh | 74 ++++++++++++++-------------- 2 files changed, 75 insertions(+), 38 deletions(-) diff --git a/internal/bootstrap/bootstrap_test.go b/internal/bootstrap/bootstrap_test.go index de6e9f6..59ee4c0 100644 --- a/internal/bootstrap/bootstrap_test.go +++ b/internal/bootstrap/bootstrap_test.go @@ -120,6 +120,45 @@ func TestPrepareMigratesExactLegacyManagedConfig(t *testing.T) { assertFileContent(t, filepath.Join(root, managedConfigVersionFile), managedConfigVersion+"\n") } +func TestPrepareBackfillsVersionAfterMigrationMarkerFailure(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + root := filepath.Join(tempDir, "data") + if err := os.MkdirAll(root, 0o755); err != nil { + t.Fatal(err) + } + current := "rule-providers:\n" + managedChinaIPProvider + "rules:\n" + managedChinaIPRule + "\n" + legacy := "rule-providers:\nrules:\n" + legacyChinaIPRule + "\n" + target := writeFixture(t, root, "config.yaml", legacy) + configSource := writeFixture(t, tempDir, "current.yaml", current) + + _, migrated, err := prepareManagedConfig( + configSource, + target, + filepath.Join(tempDir, "missing", managedConfigVersionFile), + ) + if err == nil || !migrated { + t.Fatalf("prepareManagedConfig() = migrated %t, error %v; want migrated config and marker write error", migrated, err) + } + assertFileContent(t, target, current) + + result, err := Prepare(Config{ + Root: root, + SSClashTemp: filepath.Join(tempDir, "tmp"), + CoreSource: writeFixture(t, tempDir, "mihomo", "core"), + ConfigSource: configSource, + }) + if err != nil { + t.Fatalf("Prepare() retry error = %v", err) + } + if result.ConfigInitialized || result.ConfigMigrated { + t.Fatalf("Prepare() retry result = %+v, want marker-only recovery", result) + } + assertFileContent(t, target, current) + assertFileContent(t, filepath.Join(root, managedConfigVersionFile), managedConfigVersion+"\n") +} + func TestPreparePreservesAndRejectsCustomLegacyGeoIPConfig(t *testing.T) { t.Parallel() diff --git a/tests/container-smoke.sh b/tests/container-smoke.sh index 9619808..d1e8302 100755 --- a/tests/container-smoke.sh +++ b/tests/container-smoke.sh @@ -8,29 +8,31 @@ unconfigured="mohomo-docker-unconfigured-${suffix}" legacy_container="mohomo-docker-legacy-${suffix}" provider="mohomo-provider-smoke-${suffix}" network="mohomo-network-smoke-${suffix}" +legacy_network="mohomo-legacy-network-smoke-${suffix}" volume="mohomo-volume-smoke-${suffix}" legacy_volume="mohomo-legacy-volume-smoke-${suffix}" secret="container-smoke-secret" admin_password="container-smoke-admin-password" -case "$container:$unconfigured:$legacy_container:$provider:$network:$volume:$legacy_volume" in -mohomo-docker-smoke-*':mohomo-docker-unconfigured-'*':mohomo-docker-legacy-'*':mohomo-provider-smoke-'*':mohomo-network-smoke-'*':mohomo-volume-smoke-'*':mohomo-legacy-volume-smoke-'*) ;; +case "$container:$unconfigured:$legacy_container:$provider:$network:$legacy_network:$volume:$legacy_volume" in +mohomo-docker-smoke-*':mohomo-docker-unconfigured-'*':mohomo-docker-legacy-'*':mohomo-provider-smoke-'*':mohomo-network-smoke-'*':mohomo-legacy-network-smoke-'*':mohomo-volume-smoke-'*':mohomo-legacy-volume-smoke-'*) ;; *) echo "refusing unsafe cleanup targets" >&2; exit 1 ;; esac cleanup() { docker container rm --force "$container" "$unconfigured" "$legacy_container" "$provider" >/dev/null 2>&1 || true docker volume rm "$volume" "$legacy_volume" >/dev/null 2>&1 || true - docker network rm "$network" >/dev/null 2>&1 || true + docker network rm "$network" "$legacy_network" >/dev/null 2>&1 || true } trap cleanup EXIT INT TERM wait_for_health() { + health_container=${1:-$container} attempt=0 - until [ "$(docker inspect --format '{{.State.Health.Status}}' "$container")" = healthy ]; do + until [ "$(docker inspect --format '{{.State.Health.Status}}' "$health_container")" = healthy ]; do attempt=$((attempt + 1)) if [ "$attempt" -ge 30 ]; then - docker logs "$container" >&2 + docker logs "$health_container" >&2 echo "container did not become healthy" >&2 exit 1 fi @@ -116,6 +118,24 @@ docker run --rm --network none --entrypoint /bin/sh "$image" -c ' /usr/local/lib/ssclash/clash -t -d "$runtime" -f "$runtime/config.yaml" ' >/dev/null +docker network create "$network" >/dev/null +docker network create --internal "$legacy_network" >/dev/null +docker run --detach --rm \ + --name "$provider" \ + --network "$network" \ + --entrypoint /bin/sh \ + "$image" -c 'while :; do printf "HTTP/1.1 200 OK\r\nContent-Type: text/yaml\r\nConnection: close\r\n\r\nproxies:\n - name: smoke-node\n type: socks5\n server: 127.0.0.1\n port: 9\n" | nc -l -p 8080; done' >/dev/null +docker network connect "$legacy_network" "$provider" +attempt=0 +until docker exec "$provider" wget -qO- http://127.0.0.1:8080/provider.yaml | grep -F 'name: smoke-node' >/dev/null; do + attempt=$((attempt + 1)) + if [ "$attempt" -ge 10 ]; then + echo "subscription fixture did not become ready" >&2 + exit 1 + fi + sleep 1 +done + docker volume create "$legacy_volume" >/dev/null docker run --rm \ --volume "$legacy_volume:/opt/clash" \ @@ -128,50 +148,28 @@ docker run --rm \ > /opt/clash/config.yaml grep -F " - GEOIP,CN,🎯 ε…¨ηƒη›΄θΏž" /opt/clash/config.yaml >/dev/null ' -if docker run \ +docker run --detach \ --name "$legacy_container" \ - --network none \ - --env "SUBSCRIPTION_URL=http://127.0.0.1:9/provider.yaml" \ + --network "$legacy_network" \ + --env "SUBSCRIPTION_URL=http://${provider}:8080/provider.yaml" \ --env "SSCLASH_PASSWORD=${admin_password}" \ --volume "$legacy_volume:/opt/clash" \ - "$image" >/dev/null 2>&1; then - echo "legacy volume unexpectedly started without a subscription network" >&2 - exit 1 -fi -docker logs "$legacy_container" 2>&1 | grep -F 'initial subscription update failed: subscription request failed' >/dev/null + "$image" >/dev/null +wait_for_health "$legacy_container" +docker logs "$legacy_container" 2>&1 | grep -F 'bootstrap: SSClash started' >/dev/null if docker logs "$legacy_container" 2>&1 | grep -F 'geoip.metadb' >/dev/null; then echo "legacy managed config attempted a GeoIP download" >&2 exit 1 fi -docker run --rm --network none \ - --volume "$legacy_volume:/opt/clash" \ - --entrypoint /bin/sh \ - "$image" -c ' +docker exec "$legacy_container" /bin/sh -c ' set -eu cmp /opt/clash/config.yaml /usr/local/share/ssclash/config.yaml test "$(cat /opt/clash/.mohomo-docker-config-version)" = 1 - runtime=$(mktemp -d) - cp /opt/clash/config.yaml "$runtime/config.yaml" - printf "proxies:\n - name: smoke-node\n type: socks5\n server: 127.0.0.1\n port: 9\n" > "$runtime/subscription.yaml" - /usr/local/lib/ssclash/clash -t -d "$runtime" -f "$runtime/config.yaml" + grep -F "name: smoke-node" /dev/shm/mohomo/subscription.yaml >/dev/null + /usr/local/lib/ssclash/clash -t -d /dev/shm/mohomo -f /dev/shm/mohomo/config.yaml ' >/dev/null - -docker network create "$network" >/dev/null +docker container rm --force "$legacy_container" >/dev/null docker volume create "$volume" >/dev/null -docker run --detach --rm \ - --name "$provider" \ - --network "$network" \ - --entrypoint /bin/sh \ - "$image" -c 'while :; do printf "HTTP/1.1 200 OK\r\nContent-Type: text/yaml\r\nConnection: close\r\n\r\nproxies:\n - name: smoke-node\n type: socks5\n server: 127.0.0.1\n port: 9\n" | nc -l -p 8080; done' >/dev/null -attempt=0 -until docker exec "$provider" wget -qO- http://127.0.0.1:8080/provider.yaml | grep -F 'name: smoke-node' >/dev/null; do - attempt=$((attempt + 1)) - if [ "$attempt" -ge 10 ]; then - echo "subscription fixture did not become ready" >&2 - exit 1 - fi - sleep 1 -done docker run --detach \ --name "$unconfigured" \ @@ -289,4 +287,4 @@ assert_published_ports web_port=$(docker port "$container" 9091/tcp | awk -F: 'NR == 1 { print $NF }') assert_web_login_and_start "$web_port" -echo "container smoke test passed: legacy config migrates offline; fresh volume fails closed; authenticated 9091 survives same-volume rebuild; only 7890/9091 are published" +echo "container smoke test passed: legacy config migrates and validates a synthetic subscription offline; fresh volume fails closed; authenticated 9091 survives same-volume rebuild; only 7890/9091 are published"