Files
compute/object/store_replica_test.go
zeekay 7a3de72ea6 the driver builds the DSN, because the two backends spell a pragma differently
The durability profile was written by hand in modernc's `_pragma=NAME(VALUE)`
form. The cgo backend does not recognise that spelling and drops it silently, so
a cgo build of this store ran with journal_mode `delete`, foreign_keys off and
busy_timeout 5000 while the DSN asked for WAL, ON and 10000 — measured, all
three, at every one of the three call sites.

sqlite.PragmaDSN emits the form the linked backend actually reads, so there is
one spelling to get right and the driver owns it.

The test opens a real file and asks the database what it got, rather than
comparing DSN strings: the string is exactly what differs between the backends,
so a string assertion passes on the build that ignores it. That is also why this
went unnoticed — the hand-written form is correct under CGO_ENABLED=0.
2026-08-23 14:43:23 -07:00

98 lines
2.9 KiB
Go

// Copyright 2025 Hanzo Industries Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
package object
import (
"os"
"path/filepath"
"testing"
"github.com/hanzoai/orm/relational"
)
// openTestOrgDB opens an org SQLite the same way baseStore.EngineFor does.
func openTestOrgDB(t *testing.T, path string) *relational.Engine {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
t.Fatalf("mkdir %s: %v", path, err)
}
e, err := relational.NewEngine("sqlite", dsn(path))
if err != nil {
t.Fatalf("open %s: %v", path, err)
}
return e
}
// TestReplicatorHydrateShipRoundTrip proves the HA durability path: a "pod A"
// writes an org's rows and ships a snapshot to the object store; a fresh "pod B"
// (empty local disk) hydrates that org on open and sees the rows — SQLite survives
// a pod with no persistent volume.
func TestReplicatorHydrateShipRoundTrip(t *testing.T) {
objDir := t.TempDir() // stands in for SeaweedFS
t.Setenv("REPLICA_STORE", "file://"+objDir)
// Pod A: write org rows, push a snapshot synchronously.
rootA := t.TempDir()
repA := newReplicator(rootA)
if repA == nil {
t.Fatal("newReplicator A returned nil with REPLICA_STORE set")
}
defer repA.close()
pathA := DBPath(rootA, "acme")
engA := openTestOrgDB(t, pathA)
if _, err := engA.Exec(`CREATE TABLE t(x INT)`); err != nil {
t.Fatalf("create: %v", err)
}
if _, err := engA.Exec(`INSERT INTO t VALUES(7)`); err != nil {
t.Fatalf("insert: %v", err)
}
_ = engA.Close()
if err := repA.pushNow("acme", pathA); err != nil {
t.Fatalf("pushNow: %v", err)
}
// Pod B: fresh disk, hydrate must pull acme's DB and expose the row.
rootB := t.TempDir()
repB := newReplicator(rootB)
defer repB.close()
pathB := DBPath(rootB, "acme")
if err := os.MkdirAll(filepath.Dir(pathB), 0o700); err != nil {
t.Fatalf("mkdir B: %v", err)
}
if err := repB.hydrate("acme", pathB); err != nil {
t.Fatalf("hydrate B: %v", err)
}
engB := openTestOrgDB(t, pathB)
defer engB.Close()
var got int
if _, err := engB.SQL(`SELECT x FROM t`).Get(&got); err != nil {
t.Fatalf("read hydrated: %v", err)
}
if got != 7 {
t.Fatalf("hydrated x = %d, want 7 (cross-pod durability failed)", got)
}
}
// TestReplicatorDisabledIsLocalOnly proves the absence of REPLICA_STORE yields a
// nil replicator whose methods are safe no-ops (today's local-only behavior).
func TestReplicatorDisabledIsLocalOnly(t *testing.T) {
t.Setenv("REPLICA_STORE", "")
rep := newReplicator(t.TempDir())
if rep != nil {
t.Fatal("replicator must be nil when REPLICA_STORE is unset")
}
// Nil-safe: hydrate/ship/pushNow/close are no-ops.
if err := rep.hydrate("acme", "/tmp/x.db"); err != nil {
t.Fatalf("nil hydrate: %v", err)
}
if err := rep.pushNow("acme", "/tmp/x.db"); err != nil {
t.Fatalf("nil pushNow: %v", err)
}
rep.ship("acme", "/tmp/x.db")
rep.close()
}