HH-635: migrate legacy managed configs
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -91,6 +92,61 @@ func TestPreparePreservesUserDataAndForcesServerMode(t *testing.T) {
|
||||
assertFileContent(t, filepath.Join(root, ".ssclash", "settings"), "LOG_LEVEL=debug\nOPERATING_MODE=server\nPROXY_MODE=none\n")
|
||||
}
|
||||
|
||||
func TestPrepareMigratesExactLegacyManagedConfig(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"
|
||||
writeFixture(t, root, "config.yaml", legacy)
|
||||
|
||||
result, err := Prepare(Config{
|
||||
Root: root,
|
||||
SSClashTemp: filepath.Join(tempDir, "tmp"),
|
||||
CoreSource: writeFixture(t, tempDir, "mihomo", "core"),
|
||||
ConfigSource: writeFixture(t, tempDir, "current.yaml", current),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Prepare() error = %v", err)
|
||||
}
|
||||
if result.ConfigInitialized || !result.ConfigMigrated {
|
||||
t.Fatalf("Prepare() result = %+v, want migrated existing config", result)
|
||||
}
|
||||
assertFileContent(t, filepath.Join(root, "config.yaml"), current)
|
||||
assertFileContent(t, filepath.Join(root, managedConfigVersionFile), managedConfigVersion+"\n")
|
||||
}
|
||||
|
||||
func TestPreparePreservesAndRejectsCustomLegacyGeoIPConfig(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"
|
||||
custom := "rule-providers:\nrules:\n" + legacyChinaIPRule + "\n# user managed\n"
|
||||
target := writeFixture(t, root, "config.yaml", custom)
|
||||
|
||||
_, err := Prepare(Config{
|
||||
Root: root,
|
||||
SSClashTemp: filepath.Join(tempDir, "tmp"),
|
||||
CoreSource: writeFixture(t, tempDir, "mihomo", "core"),
|
||||
ConfigSource: writeFixture(t, tempDir, "current.yaml", current),
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "custom config uses GEOIP,CN") {
|
||||
t.Fatalf("Prepare() error = %v, want explicit custom config migration error", err)
|
||||
}
|
||||
assertFileContent(t, target, custom)
|
||||
if _, statErr := os.Stat(filepath.Join(root, managedConfigVersionFile)); !errors.Is(statErr, os.ErrNotExist) {
|
||||
t.Fatalf("managed config version marker unexpectedly created: %v", statErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureAdminPasswordFailsClosedOnFreshVolume(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -529,6 +585,74 @@ func TestRunLeavesMihomoLifecycleToSSClash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunStopsSSClashWhenControllerCannotConfirmRollback(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
root := filepath.Join(tempDir, "root")
|
||||
if err := os.MkdirAll(root, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
core := writeFixture(t, tempDir, "fake-core", "#!/bin/sh\n[ \"$1\" = -t ]\n")
|
||||
ssclash := writeFixture(t, tempDir, "fake-ssclash", "#!/bin/sh\n[ \"$1\" = serve ]\nexec sleep 3600\n")
|
||||
for _, binary := range []string{core, ssclash} {
|
||||
if err := os.Chmod(binary, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
config := writeFixture(t, root, "config.yaml", "proxy-providers:\n subscription:\n type: file\n path: ./subscription.yaml\n")
|
||||
|
||||
var subscriptionRequests atomic.Int32
|
||||
subscription := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
|
||||
name := "updated"
|
||||
if subscriptionRequests.Add(1) == 1 {
|
||||
name = "initial"
|
||||
}
|
||||
_, _ = writer.Write([]byte("proxies:\n - name: " + name + "\n"))
|
||||
}))
|
||||
defer subscription.Close()
|
||||
var reloadRequests atomic.Int32
|
||||
controller := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
switch {
|
||||
case request.Method == http.MethodGet && request.URL.Path == "/version":
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
case request.Method == http.MethodPut && request.URL.Path == "/providers/proxies/subscription":
|
||||
reloadRequests.Add(1)
|
||||
http.Error(writer, "reload failed", http.StatusInternalServerError)
|
||||
default:
|
||||
http.NotFound(writer, request)
|
||||
}
|
||||
}))
|
||||
defer controller.Close()
|
||||
|
||||
runResult := make(chan error, 1)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go func() {
|
||||
runResult <- Run(ctx, RuntimeConfig{
|
||||
Root: root,
|
||||
CoreBinary: core,
|
||||
SSClashBinary: ssclash,
|
||||
ConfigSource: config,
|
||||
RuntimeDir: filepath.Join(tempDir, "runtime"),
|
||||
SubscriptionURL: subscription.URL,
|
||||
ControllerURL: controller.URL,
|
||||
UpdateInterval: 20 * time.Millisecond,
|
||||
})
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-runResult:
|
||||
if !errors.Is(err, errMihomoStateUncertain) {
|
||||
t.Fatalf("Run() error = %v, want uncertain Mihomo state", err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Run() did not stop SSClash after rollback reload failure")
|
||||
}
|
||||
if subscriptionRequests.Load() < 2 || reloadRequests.Load() != 2 {
|
||||
t.Fatalf("requests = subscription:%d reload:%d, want at least 2 and exactly 2", subscriptionRequests.Load(), reloadRequests.Load())
|
||||
}
|
||||
assertFileContent(t, filepath.Join(tempDir, "runtime", "subscription.yaml"), "proxies:\n - name: initial\n")
|
||||
}
|
||||
|
||||
func TestValidateSubscriptionURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user