1073 lines · 35.6 KB · Go
RawDownload
package cli import (	"bytes"	"compress/gzip"	"context"	"encoding/json"	"errors"	"fmt"	"io"	"os"	"path/filepath"	"strings"	"testing"	"time" 	"github.com/openclaw/gitcrawl/internal/config") func writeTestCompressedPortableSource(t *testing.T, dbPath string) string {	t.Helper()	manifestPath := writeTestPortableManifest(t, dbPath)	manifest, ok, err := readPortableDBManifest(manifestPath)	if err != nil || !ok {		t.Fatalf("read direct portable manifest: ok=%v err=%v", ok, err)	}	archivePath := dbPath + ".gz"	source, err := os.Open(dbPath)	if err != nil {		t.Fatalf("open portable db: %v", err)	}	archive, err := os.Create(archivePath)	if err != nil {		_ = source.Close()		t.Fatalf("create portable archive: %v", err)	}	writer := gzip.NewWriter(archive)	if _, err := io.Copy(writer, source); err != nil {		_ = writer.Close()		_ = archive.Close()		_ = source.Close()		t.Fatalf("compress portable db: %v", err)	}	if err := writer.Close(); err != nil {		_ = archive.Close()		_ = source.Close()		t.Fatalf("close portable gzip writer: %v", err)	}	if err := archive.Close(); err != nil {		_ = source.Close()		t.Fatalf("close portable archive: %v", err)	}	if err := source.Close(); err != nil {		t.Fatalf("close portable db: %v", err)	}	archiveInfo, err := os.Stat(archivePath)	if err != nil {		t.Fatalf("stat portable archive: %v", err)	}	archiveSum, err := fileSHA256(archivePath)	if err != nil {		t.Fatalf("hash portable archive: %v", err)	}	manifest.Compression = "gzip"	manifest.ArchivePath = filepath.Base(archivePath)	manifest.ArchiveBytes = archiveInfo.Size()	manifest.ArchiveSHA256 = fmt.Sprintf("%x", archiveSum)	data, err := json.Marshal(manifest)	if err != nil {		t.Fatalf("marshal compressed portable manifest: %v", err)	}	if err := os.WriteFile(manifestPath, data, 0o644); err != nil {		t.Fatalf("write compressed portable manifest: %v", err)	}	if err := os.Remove(dbPath); err != nil {		t.Fatalf("remove direct portable db: %v", err)	}	return archivePath} func TestLocalRuntimeDBTarget(t *testing.T) {	direct := localRuntime{Config: config.Config{DBPath: "/data/gitcrawl.db"}}	if got, want := direct.dbTarget(), (dbTargetInfo{DBTarget: "direct", DBTargetPath: "/data/gitcrawl.db"}); got != want {		t.Fatalf("direct db target = %+v, want %+v", got, want)	} 	redirected := localRuntime{		Config:       config.Config{DBPath: "/runtime/store/data/gitcrawl.db"},		SourceDBPath: "/checkout/data/gitcrawl.db",		RemoteSource: true,	}	want := dbTargetInfo{		DBTarget:         "runtime-mirror",		DBTargetPath:     "/runtime/store/data/gitcrawl.db",		PortableSourceDB: "/checkout/data/gitcrawl.db",	}	if got := redirected.dbTarget(); got != want {		t.Fatalf("redirected db target = %+v, want %+v", got, want)	}} func TestSweepOrphanPortableRuntimeTempFiles(t *testing.T) {	dir := t.TempDir()	mirrorPath := filepath.Join(dir, "x.db")	old := time.Now().Add(-2 * portableRuntimeTempMaxAge)	oldMatching := []string{".x.db.tmp-123", ".x.db.tmp-123-wal", ".x.db.tmp-123-shm"}	oldPreserved := []string{"old.db", ".other.db.tmp-9", ".notes.tmp-backup"}	for _, name := range append(append([]string(nil), oldMatching...), oldPreserved...) {		path := filepath.Join(dir, name)		if err := os.WriteFile(path, []byte(name), 0o600); err != nil {			t.Fatalf("write %s: %v", name, err)		}		if err := os.Chtimes(path, old, old); err != nil {			t.Fatalf("backdate %s: %v", name, err)		}	}	fresh := filepath.Join(dir, ".x.db.tmp-fresh")	if err := os.WriteFile(fresh, []byte("fresh"), 0o600); err != nil {		t.Fatalf("write fresh temp: %v", err)	} 	sweepOrphanPortableRuntimeTempFiles(mirrorPath, portableRuntimeTempMaxAge) 	for _, name := range oldMatching {		if _, err := os.Stat(filepath.Join(dir, name)); !os.IsNotExist(err) {			t.Fatalf("old matching temp %s still exists: %v", name, err)		}	}	preserved := []string{fresh}	for _, name := range oldPreserved {		preserved = append(preserved, filepath.Join(dir, name))	}	for _, path := range preserved {		if _, err := os.Stat(path); err != nil {			t.Fatalf("preserved file %s: %v", path, err)		}	}} func TestCopySQLiteFileAtomicVerifiedRemovesTempFiles(t *testing.T) {	dir := t.TempDir()	source := filepath.Join(dir, "source.db")	target := filepath.Join(dir, "target.db")	seedPortableThread(t, source, 1, "copy temp cleanup")	if err := copySQLiteFileAtomicVerified(context.Background(), source, target); err != nil {		t.Fatalf("copy verified sqlite file: %v", err)	}	matches, err := filepath.Glob(filepath.Join(dir, ".target.db.tmp-*"))	if err != nil {		t.Fatalf("glob temp files: %v", err)	}	if len(matches) != 0 {		t.Fatalf("verified copy left temp files: %v", matches)	}	invalidSource := filepath.Join(dir, "invalid.db")	failedTarget := filepath.Join(dir, "failed.db")	if err := os.WriteFile(invalidSource, []byte("not sqlite"), 0o600); err != nil {		t.Fatalf("write invalid source: %v", err)	}	if err := copySQLiteFileAtomicVerified(context.Background(), invalidSource, failedTarget); err == nil {		t.Fatal("invalid sqlite source should fail validation")	}	matches, err = filepath.Glob(filepath.Join(dir, ".failed.db.tmp-*"))	if err != nil {		t.Fatalf("glob failed-copy temp files: %v", err)	}	if len(matches) != 0 {		t.Fatalf("failed verified copy left temp files: %v", matches)	} 	tempPath := filepath.Join(dir, ".failed.db.tmp-123")	for _, suffix := range []string{"-wal", "-shm"} {		if err := os.WriteFile(tempPath+suffix, []byte("sidecar"), 0o600); err != nil {			t.Fatalf("write temp sidecar: %v", err)		}	}	removeSQLiteTempSidecars(tempPath)	for _, suffix := range []string{"-wal", "-shm"} {		if _, err := os.Stat(tempPath + suffix); !os.IsNotExist(err) {			t.Fatalf("temp sidecar %s still exists: %v", suffix, err)		}	}} func TestRefreshPortableRuntimeDBInflatesCompressedSource(t *testing.T) {	ctx := context.Background()	dir := t.TempDir()	checkoutDir := filepath.Join(dir, "checkout")	sourceDB := filepath.Join(checkoutDir, "data", "openclaw__openclaw.sync.db")	mirrorDB := filepath.Join(dir, "runtime", "openclaw__openclaw.sync.db")	seedPortableThread(t, sourceDB, 7, "compressed portable source")	writeTestCompressedPortableSource(t, sourceDB) 	changed, err := refreshPortableRuntimeDB(		ctx,		sourceDB,		mirrorDB,		false,		filepath.Join(dir, "config.toml"),	)	if err != nil || !changed {		t.Fatalf("refresh compressed portable source: changed=%v err=%v", changed, err)	}	if err := sqliteStoreHealth(ctx, mirrorDB); err != nil {		t.Fatalf("inflated mirror health: %v", err)	}	manifest, ok, err := readPortableDBManifest(portableDBManifestPath(sourceDB))	if err != nil || !ok {		t.Fatalf("read compressed manifest: ok=%v err=%v", ok, err)	}	sum, err := fileSHA256(mirrorDB)	if err != nil {		t.Fatalf("hash inflated mirror: %v", err)	}	if got := fmt.Sprintf("%x", sum); got != manifest.SHA256 {		t.Fatalf("inflated mirror sha256 = %s, want %s", got, manifest.SHA256)	}} func TestCompressedPortableSourceFailureKeepsLastGoodMirror(t *testing.T) {	ctx := context.Background()	dir := t.TempDir()	checkoutDir := filepath.Join(dir, "checkout")	sourceDB := filepath.Join(checkoutDir, "data", "openclaw__openclaw.sync.db")	mirrorDB := filepath.Join(dir, "runtime", "openclaw__openclaw.sync.db")	seedPortableThread(t, sourceDB, 8, "new compressed content")	archivePath := writeTestCompressedPortableSource(t, sourceDB)	seedPortableThread(t, mirrorDB, 9, "last good mirror")	past := time.Now().Add(-time.Hour)	if err := os.Chtimes(mirrorDB, past, past); err != nil {		t.Fatalf("age mirror: %v", err)	}	before, err := fileSHA256(mirrorDB)	if err != nil {		t.Fatalf("hash mirror before: %v", err)	}	if err := os.WriteFile(archivePath, []byte("truncated"), 0o644); err != nil {		t.Fatalf("corrupt portable archive: %v", err)	} 	changed, err := refreshPortableRuntimeDB(		ctx,		sourceDB,		mirrorDB,		false,		filepath.Join(dir, "config.toml"),	)	if err == nil || changed || !strings.Contains(err.Error(), "portable manifest mismatch") {		t.Fatalf("corrupt compressed source: changed=%v err=%v", changed, err)	}	after, err := fileSHA256(mirrorDB)	if err != nil {		t.Fatalf("hash mirror after: %v", err)	}	if after != before {		t.Fatal("failed compressed refresh replaced the last good mirror")	}} func TestCompressedPortableManifestRejectsEscapingArchivePath(t *testing.T) {	dir := t.TempDir()	dbPath := filepath.Join(dir, "data", "openclaw__openclaw.sync.db")	if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil {		t.Fatalf("mkdir data: %v", err)	}	manifest := portableDBManifest{		Schema:        "gitcrawl-portable-sync-v2",		OutputBytes:   1,		SHA256:        strings.Repeat("a", 64),		Compression:   "gzip",		ArchivePath:   "../escape.db.gz",		ArchiveBytes:  1,		ArchiveSHA256: strings.Repeat("b", 64),	}	data, err := json.Marshal(manifest)	if err != nil {		t.Fatalf("marshal manifest: %v", err)	}	if err := os.WriteFile(portableDBManifestPath(dbPath), data, 0o644); err != nil {		t.Fatalf("write manifest: %v", err)	}	if _, _, _, err := portableSourceArtifact(dbPath); err == nil ||		!strings.Contains(err.Error(), "archivePath escapes") {		t.Fatalf("escaping archivePath error = %v", err)	}} func TestCompressedPortableManifestRejectsInvalidMetadata(t *testing.T) {	dir := t.TempDir()	dbPath := filepath.Join(dir, "data", "openclaw__openclaw.sync.db")	if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil {		t.Fatalf("mkdir data: %v", err)	}	manifestPath := portableDBManifestPath(dbPath)	base := portableDBManifest{		Schema:        "gitcrawl-portable-sync-v2",		OutputBytes:   1,		SHA256:        strings.Repeat("a", 64),		Compression:   "gzip",		ArchivePath:   filepath.Base(dbPath) + ".gz",		ArchiveBytes:  1,		ArchiveSHA256: strings.Repeat("b", 64),	}	cases := []struct {		name    string		mutate  func(*portableDBManifest)		message string	}{		{			name:    "unsupported compression",			mutate:  func(manifest *portableDBManifest) { manifest.Compression = "zstd" },			message: "unsupported compression",		},		{			name:    "missing archive path",			mutate:  func(manifest *portableDBManifest) { manifest.ArchivePath = "" },			message: "archivePath must be relative",		},		{			name:    "absolute archive path",			mutate:  func(manifest *portableDBManifest) { manifest.ArchivePath = "/tmp/store.db.gz" },			message: "archivePath must be relative",		},		{			name:    "drive letter archive path",			mutate:  func(manifest *portableDBManifest) { manifest.ArchivePath = `C:\store.db.gz` },			message: "archivePath must be relative",		},		{			name:    "current directory archive path",			mutate:  func(manifest *portableDBManifest) { manifest.ArchivePath = "." },			message: "archivePath escapes the store",		},		{			name:    "missing archive bytes",			mutate:  func(manifest *portableDBManifest) { manifest.ArchiveBytes = 0 },			message: "archiveBytes missing",		},		{			name:    "missing archive sha",			mutate:  func(manifest *portableDBManifest) { manifest.ArchiveSHA256 = "" },			message: "archiveSha256 missing",		},	}	for _, tc := range cases {		t.Run(tc.name, func(t *testing.T) {			manifest := base			tc.mutate(&manifest)			data, err := json.Marshal(manifest)			if err != nil {				t.Fatalf("marshal manifest: %v", err)			}			if err := os.WriteFile(manifestPath, data, 0o644); err != nil {				t.Fatalf("write manifest: %v", err)			}			if _, _, _, err := portableSourceArtifact(dbPath); err == nil ||				!strings.Contains(err.Error(), tc.message) {				t.Fatalf("portable source error = %v, want %q", err, tc.message)			}		})	}} func TestCompressedPortableArchiveRejectsTampering(t *testing.T) {	dir := t.TempDir()	dbPath := filepath.Join(dir, "data", "openclaw__openclaw.sync.db")	seedPortableThread(t, dbPath, 10, "compressed portable source")	archivePath := writeTestCompressedPortableSource(t, dbPath)	manifestPath := portableDBManifestPath(dbPath)	manifest, ok, err := readPortableDBManifest(manifestPath)	if err != nil || !ok {		t.Fatalf("read compressed manifest: ok=%v err=%v", ok, err)	} 	manifest.ArchiveBytes++	if err := validatePortableArchive(archivePath, manifest); err == nil ||		!strings.Contains(err.Error(), "archive size") {		t.Fatalf("archive size error = %v", err)	} 	manifest.ArchiveBytes--	manifest.ArchiveSHA256 = strings.Repeat("0", 64)	if err := validatePortableArchive(archivePath, manifest); err == nil ||		!strings.Contains(err.Error(), "archive sha256") {		t.Fatalf("archive hash error = %v", err)	}} func TestCompressedPortableArchiveRejectsInvalidPayload(t *testing.T) {	dir := t.TempDir()	dbPath := filepath.Join(dir, "data", "openclaw__openclaw.sync.db")	seedPortableThread(t, dbPath, 11, "compressed portable source")	archivePath := writeTestCompressedPortableSource(t, dbPath)	manifestPath := portableDBManifestPath(dbPath)	targetPath := filepath.Join(dir, "runtime", filepath.Base(dbPath)) 	writeArchive := func(data []byte, outputBytes int64) {		t.Helper()		if err := os.WriteFile(archivePath, data, 0o644); err != nil {			t.Fatalf("write archive: %v", err)		}		manifest, ok, err := readPortableDBManifest(manifestPath)		if err != nil || !ok {			t.Fatalf("read compressed manifest: ok=%v err=%v", ok, err)		}		info, err := os.Stat(archivePath)		if err != nil {			t.Fatalf("stat archive: %v", err)		}		sum, err := fileSHA256(archivePath)		if err != nil {			t.Fatalf("hash archive: %v", err)		}		manifest.OutputBytes = outputBytes		manifest.ArchiveBytes = info.Size()		manifest.ArchiveSHA256 = fmt.Sprintf("%x", sum)		encoded, err := json.Marshal(manifest)		if err != nil {			t.Fatalf("marshal compressed manifest: %v", err)		}		if err := os.WriteFile(manifestPath, encoded, 0o644); err != nil {			t.Fatalf("write compressed manifest: %v", err)		}	} 	writeArchive([]byte("not gzip"), 1)	if _, err := stagePortableSQLiteSourceTemp(dbPath, targetPath, 0o600); err == nil ||		!strings.Contains(err.Error(), "open portable gzip archive") {		t.Fatalf("invalid gzip error = %v", err)	} 	var compressed bytes.Buffer	writer := gzip.NewWriter(&compressed)	if _, err := writer.Write([]byte("short")); err != nil {		t.Fatalf("write gzip payload: %v", err)	}	if err := writer.Close(); err != nil {		t.Fatalf("close gzip payload: %v", err)	}	writeArchive(compressed.Bytes(), 10)	if _, err := stagePortableSQLiteSourceTemp(dbPath, targetPath, 0o600); err == nil ||		!strings.Contains(err.Error(), "inflated size") {		t.Fatalf("inflated size error = %v", err)	}} func TestValidateCompressedPortableSQLiteSource(t *testing.T) {	ctx := context.Background()	dir := t.TempDir()	dbPath := filepath.Join(dir, "data", "openclaw__openclaw.sync.db")	seedPortableThread(t, dbPath, 12, "compressed validation source")	writeTestCompressedPortableSource(t, dbPath) 	if err := validatePortableSQLiteSourceFile(ctx, dbPath, dbPath); err != nil {		t.Fatalf("validate compressed portable source: %v", err)	}} func TestPreserveMalformedCompressedPortableSource(t *testing.T) {	dir := t.TempDir()	root := filepath.Join(dir, "checkout")	dbPath := filepath.Join(root, "data", "openclaw__openclaw.sync.db")	seedPortableThread(t, dbPath, 13, "compressed evidence source")	archivePath := writeTestCompressedPortableSource(t, dbPath) 	backupDir, err := preserveMalformedPortableDB(root, dbPath)	if err != nil {		t.Fatalf("preserve compressed portable source: %v", err)	}	for _, name := range []string{		filepath.Base(archivePath) + ".malformed",		filepath.Base(portableDBManifestPath(dbPath)),	} {		if _, err := os.Stat(filepath.Join(backupDir, name)); err != nil {			t.Fatalf("stat preserved evidence %s: %v", name, err)		}	}} func writeTestPortableManifest(t *testing.T, dbPath string) string {	t.Helper()	info, err := os.Stat(dbPath)	if err != nil {		t.Fatalf("stat db for manifest: %v", err)	}	sum, err := fileSHA256(dbPath)	if err != nil {		t.Fatalf("hash db for manifest: %v", err)	}	manifest := portableDBManifest{		Schema:      "gitcrawl-portable-sync-v2",		ExportedAt:  time.Now().UTC().Format(time.RFC3339Nano),		OutputPath:  filepath.Base(dbPath),		OutputBytes: info.Size(),		SHA256:      fmt.Sprintf("%x", sum),		QuickCheck:  "ok",	}	data, err := json.Marshal(manifest)	if err != nil {		t.Fatalf("marshal manifest: %v", err)

Lines 1–500 of 1073.