Files
nginx-ui/model/namespace_test.go
0xJacky 9f70d47b35 feat(cluster): unify multi-node configuration synchronization
Deployment to other nodes was file by file, manual, and always validated
against the local Nginx. One synchronization engine now backs every path.

- Directory deployment: a directory carries its own deployment targets and
  every file below it inherits them, so a whole tree replicates in one
  request per node (#1559).
- Node sync: one action pushes all configurations, sites and streams to the
  selected nodes, which is what a freshly added node needs (#1484).
- Namespace replication: the namespace record is mirrored to its member nodes
  and synced sites and streams carry the namespace name, so every node groups
  them identically (#1744).
- Automatic sync: a namespace can switch to the auto strategy with its own
  interval and a cron job re-pushes its content (#1582).
- deploy_mode=remote no longer touches the local Nginx: no sites-enabled
  symlink, no local nginx -t and no local reload. The deployment intent moves
  to the database, and moving a site into a remote namespace detaches its
  leftover local symlink (#1505).

The receiving side gains a batch config endpoint that writes every file and
reloads once, with a per-file fallback for nodes that predate it. Disabling a
site or stream became idempotent so converging a node reports no spurious
failures, renaming only tests and reloads when the enabled tree actually
changed, and stream delete now removes the stream record instead of a site one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 18:07:34 +08:00

53 lines
1.5 KiB
Go

package model
import "testing"
func TestNamespaceIsRemoteDeploy(t *testing.T) {
var missing *Namespace
if missing.IsRemoteDeploy() {
t.Fatal("a site without a namespace deploys locally")
}
if (&Namespace{DeployMode: DeployModeLocal}).IsRemoteDeploy() {
t.Fatal("local deploy mode must not be treated as remote")
}
if !(&Namespace{DeployMode: DeployModeRemote}).IsRemoteDeploy() {
t.Fatal("remote deploy mode must be detected")
}
}
func TestNamespaceIsAutoSync(t *testing.T) {
var missing *Namespace
if missing.IsAutoSync() {
t.Fatal("a missing namespace never syncs automatically")
}
if (&Namespace{SyncStrategy: SyncStrategyManual}).IsAutoSync() {
t.Fatal("manual strategy must not sync automatically")
}
if !(&Namespace{SyncStrategy: SyncStrategyAuto}).IsAutoSync() {
t.Fatal("auto strategy must sync automatically")
}
}
func TestNamespaceEffectiveSyncInterval(t *testing.T) {
var missing *Namespace
if got := missing.EffectiveSyncInterval(); got != DefaultSyncIntervalMinutes {
t.Fatalf("got %d, want %d", got, DefaultSyncIntervalMinutes)
}
if got := (&Namespace{SyncIntervalMinutes: 0}).EffectiveSyncInterval(); got != DefaultSyncIntervalMinutes {
t.Fatalf("got %d, want %d", got, DefaultSyncIntervalMinutes)
}
if got := (&Namespace{SyncIntervalMinutes: -5}).EffectiveSyncInterval(); got != DefaultSyncIntervalMinutes {
t.Fatalf("got %d, want %d", got, DefaultSyncIntervalMinutes)
}
if got := (&Namespace{SyncIntervalMinutes: 5}).EffectiveSyncInterval(); got != 5 {
t.Fatalf("got %d, want 5", got)
}
}