Files
stalwart-migrator/internal/backup/settingsdump_test.go
T
jcoffey-dev 4b0bec8956 Add SPDX headers to every Go file
GPLv3's "How to Apply These Terms" asks for a notice in each source file;
this is the modern two-line SPDX form of it rather than the full paragraph.
82 files, including tests.

The blank line after the header is load-bearing. In Go a comment block
immediately preceding `package X` becomes the package doc comment, so
without the separator the SPDX lines would be absorbed into the doc for the
eleven packages whose doc.go (or main.go) opens with one, and `go doc` would
print them. Verified it doesn't.
2026-08-23 18:03:15 -07:00

179 lines
6.0 KiB
Go

// SPDX-FileCopyrightText: 2026 LINUXexpert-org
// SPDX-License-Identifier: GPL-3.0-or-later
package backup
import (
"context"
"crypto/sha256"
"encoding/hex"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestDownloadFileAcceptsMatchingChecksum(t *testing.T) {
content := "print('fake migration script')\n"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(content))
}))
defer srv.Close()
sum := sha256.Sum256([]byte(content))
expected := hex.EncodeToString(sum[:])
dest := filepath.Join(t.TempDir(), "script.py")
got, err := DownloadFile(context.Background(), nil, srv.URL, dest, expected)
if err != nil {
t.Fatalf("DownloadFile: %v", err)
}
if got != expected {
t.Errorf("returned checksum = %s, want %s", got, expected)
}
data, _ := os.ReadFile(dest)
if string(data) != content {
t.Errorf("downloaded content = %q, want %q", data, content)
}
}
func TestDownloadFileRejectsMismatchedChecksum(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("unexpected content"))
}))
defer srv.Close()
dest := filepath.Join(t.TempDir(), "script.py")
_, err := DownloadFile(context.Background(), nil, srv.URL, dest, "0000000000000000000000000000000000000000000000000000000000000000")
if err == nil {
t.Fatal("DownloadFile should reject a checksum mismatch")
}
if _, statErr := os.Stat(dest); !os.IsNotExist(statErr) {
t.Error("DownloadFile should remove the file it wrote after a checksum mismatch")
}
}
func TestDownloadFileWithoutPinReturnsComputedHash(t *testing.T) {
content := "arbitrary content"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(content))
}))
defer srv.Close()
dest := filepath.Join(t.TempDir(), "script.py")
got, err := DownloadFile(context.Background(), nil, srv.URL, dest, "")
if err != nil {
t.Fatalf("DownloadFile: %v", err)
}
sum := sha256.Sum256([]byte(content))
want := hex.EncodeToString(sum[:])
if got != want {
t.Errorf("returned checksum = %s, want %s", got, want)
}
}
func TestRunSettingsDumpInvokesScriptWithFlags(t *testing.T) {
dir := t.TempDir()
log := argsFile(t, dir)
pythonDir := withFakeExecutable(t, "python3", fakeScriptLoggingArgs(log, "exit 0"))
err := RunSettingsDump(context.Background(), SettingsDumpOptions{
PythonPath: filepath.Join(pythonDir, "python3"),
ScriptPath: "/opt/migrate_v016.py",
URL: "https://mail.example.com",
Username: "admin",
Password: "hunter2",
SettingsPath: filepath.Join(dir, "settings.json"),
PrincipalsPath: filepath.Join(dir, "principals.json"),
})
if err != nil {
t.Fatalf("RunSettingsDump: %v", err)
}
got := readArgsFile(t, log)
for _, want := range []string{"/opt/migrate_v016.py", "dump", "--url https://mail.example.com", "--username admin"} {
if !strings.Contains(got, want) {
t.Errorf("script invoked with %q, missing %q", got, want)
}
}
}
func TestRunSettingsDumpPropagatesFailure(t *testing.T) {
pythonDir := withFakeExecutable(t, "python3", "#!/bin/sh\necho 'auth failed' >&2\nexit 1\n")
err := RunSettingsDump(context.Background(), SettingsDumpOptions{
PythonPath: filepath.Join(pythonDir, "python3"),
ScriptPath: "/opt/migrate_v016.py",
})
if err == nil {
t.Fatal("RunSettingsDump should error when the script exits non-zero")
}
if !strings.Contains(err.Error(), "auth failed") {
t.Errorf("error = %v, want it to include the script's stderr", err)
}
}
func TestRunSettingsConvertInvokesScriptWithFlags(t *testing.T) {
dir := t.TempDir()
log := argsFile(t, dir)
pythonDir := withFakeExecutable(t, "python3", fakeScriptLoggingArgs(log, "exit 0"))
err := RunSettingsConvert(context.Background(), SettingsConvertOptions{
PythonPath: filepath.Join(pythonDir, "python3"),
ScriptPath: "/opt/migrate_v016.py",
SettingsPath: filepath.Join(dir, "settings.json"),
PrincipalsPath: filepath.Join(dir, "principals.json"),
ConfigPath: filepath.Join(dir, "config.json"),
OutputPath: filepath.Join(dir, "export.json"),
})
if err != nil {
t.Fatalf("RunSettingsConvert: %v", err)
}
got := readArgsFile(t, log)
for _, want := range []string{"convert", "--settings", "--config", "--output"} {
if !strings.Contains(got, want) {
t.Errorf("script invoked with %q, missing %q", got, want)
}
}
if strings.Contains(got, "--patch-paths") {
t.Errorf("script invoked with %q, should not include --patch-paths when none given", got)
}
}
func TestRunSettingsConvertWithPatchPaths(t *testing.T) {
dir := t.TempDir()
log := argsFile(t, dir)
pythonDir := withFakeExecutable(t, "python3", fakeScriptLoggingArgs(log, "exit 0"))
err := RunSettingsConvert(context.Background(), SettingsConvertOptions{
PythonPath: filepath.Join(pythonDir, "python3"),
ScriptPath: "/opt/migrate_v016.py",
SettingsPath: filepath.Join(dir, "settings.json"),
PrincipalsPath: filepath.Join(dir, "principals.json"),
ConfigPath: filepath.Join(dir, "config.json"),
OutputPath: filepath.Join(dir, "export.json"),
PatchPaths: map[string]string{"/var/lib/stalwart": "/tmp/sandbox/stalwart"},
})
if err != nil {
t.Fatalf("RunSettingsConvert: %v", err)
}
got := readArgsFile(t, log)
if !strings.Contains(got, "--patch-paths /var/lib/stalwart=/tmp/sandbox/stalwart") {
t.Errorf("script invoked with %q, missing the expected --patch-paths flag", got)
}
}
func TestRunSettingsConvertPropagatesFailure(t *testing.T) {
pythonDir := withFakeExecutable(t, "python3", "#!/bin/sh\necho 'unsupported settings key' >&2\nexit 1\n")
err := RunSettingsConvert(context.Background(), SettingsConvertOptions{
PythonPath: filepath.Join(pythonDir, "python3"),
ScriptPath: "/opt/migrate_v016.py",
})
if err == nil {
t.Fatal("RunSettingsConvert should error when the script exits non-zero")
}
if !strings.Contains(err.Error(), "unsupported settings key") {
t.Errorf("error = %v, want it to include the script's stderr", err)
}
}