package release import ( "context" "errors" "sync" "sync/atomic" "testing" "github.com/carlosframework/platform/internal/hostwrite" ) type pausedReleaseWork struct { pauseAt int calls atomic.Int64 valid atomic.Bool once sync.Once entered chan struct{} resume chan struct{} } func newPausedReleaseWork(pauseAt int) *pausedReleaseWork { w := &pausedReleaseWork{pauseAt: pauseAt, entered: make(chan struct{}), resume: make(chan struct{})} w.valid.Store(true) return w } func (w *pausedReleaseWork) With(_ context.Context, id hostwrite.Identity, fn func() error) error { if id.Release == "" { return errors.New("missing release identity") } call := int(w.calls.Add(1)) if call == w.pauseAt { w.once.Do(func() { close(w.entered) }) <-w.resume } if !w.valid.Load() { return errors.New("release selection changed") } return fn() } type oneReleaseSelector struct{ work hostwrite.Work } func (s oneReleaseSelector) Select(context.Context, string, string) (hostwrite.Work, error) { return s.work, nil } func TestPassStaleSelectionBeforePointLiveMakesNoHostEffect(t *testing.T) { s := NewDirStore(t.TempDir()) priv, pub := testKeys(t) opt := t.TempDir() reg := testRegistry(t) t.Cleanup(func() { reg.Close() }) shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("build")) if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v1"); err != nil { t.Fatal(err) } addRoute(t, reg, "app.example", "myapp", "canary/x", false) w := newPausedReleaseWork(1) restarts := atomic.Int64{} done := make(chan error, 1) go func() { done <- Pass(t.Context(), reg, s, pub, opt, func(string) error { restarts.Add(1); return nil }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{work: w}})) }() <-w.entered w.valid.Store(false) close(w.resume) if err := <-done; err != nil { t.Fatal(err) } if live, err := LiveVersion(opt, "app.example"); err != nil || live != "" { t.Fatalf("live = %q, %v", live, err) } rt, err := reg.Get("app.example") if err != nil || rt.Version != "" || restarts.Load() != 0 { t.Fatalf("route=%+v err=%v restarts=%d", rt, err, restarts.Load()) } } func TestPassRollbackReusesOriginalSelection(t *testing.T) { s := NewDirStore(t.TempDir()) priv, pub := testKeys(t) opt := t.TempDir() reg := testRegistry(t) t.Cleanup(func() { reg.Close() }) for _, version := range []string{"v1", "v2"} { shipRelease(t, s, "bab", "myapp", version, adoptPlatform, []byte(version)) } if err := StageArtifact(opt, "bab", "v1", "myapp", []byte("v1")); err != nil { t.Fatal(err) } if err := PointLive(opt, "app.example", "bab", "v1", "myapp"); err != nil { t.Fatal(err) } if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v2"); err != nil { t.Fatal(err) } addRoute(t, reg, "app.example", "myapp", "canary/x", false) w := newPausedReleaseWork(3) done := make(chan error, 1) go func() { done <- Pass(t.Context(), reg, s, pub, opt, func(string) error { return errors.New("unexpected legacy restart") }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{work: w}}), WithSelectedRestarts(func(context.Context, hostwrite.Selection) error { return errors.New("restart failed") }, nil)) }() <-w.entered w.valid.Store(false) close(w.resume) if err := <-done; err != nil { t.Fatal(err) } if live, err := LiveVersion(opt, "app.example"); err != nil || live != "v2" { t.Fatalf("stale rollback changed live link: %q, %v", live, err) } } func TestPassEnabledMissingHostWriteProviderRefusesBeforeChannelRead(t *testing.T) { reg := testRegistry(t) t.Cleanup(func() { reg.Close() }) addRoute(t, reg, "app.example", "myapp", "canary/x", false) err := Pass(t.Context(), reg, nil, nil, t.TempDir(), nil, WithHostWrites(hostwrite.Config{Enabled: true})) if !errors.Is(err, hostwrite.ErrUnavailable) { t.Fatalf("Pass = %v", err) } } func TestPassClaimRefusesHostnameOnlyRestart(t *testing.T) { s := NewDirStore(t.TempDir()) priv, pub := testKeys(t) reg := testRegistry(t) t.Cleanup(func() { reg.Close() }) shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("build")) if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v1"); err != nil { t.Fatal(err) } addRoute(t, reg, "app.example", "myapp", "canary/x", false) w := newPausedReleaseWork(0) restarts := 0 if err := Pass(t.Context(), reg, s, pub, t.TempDir(), func(string) error { restarts++; return nil }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{w}})); err != nil { t.Fatal(err) } rt, err := reg.Get("app.example") if err != nil { t.Fatal(err) } if restarts != 0 || rt.Version != "" { t.Fatalf("hostname-only restart accepted: restarts=%d version=%q", restarts, rt.Version) } } func TestPassClaimSelectedRestartBoundary(t *testing.T) { for _, stale := range []bool{false, true} { t.Run(map[bool]string{false: "current", true: "successor"}[stale], func(t *testing.T) { s := NewDirStore(t.TempDir()) priv, pub := testKeys(t) reg := testRegistry(t) t.Cleanup(func() { reg.Close() }) shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("build")) if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v1"); err != nil { t.Fatal(err) } addRoute(t, reg, "app.example", "myapp", "canary/x", false) w := newPausedReleaseWork(0) effects := 0 restart := func(ctx context.Context, selected hostwrite.Selection) error { if selected.Work != w || selected.Host != "app.example" || selected.Identity.Release == "" { t.Fatalf("lost selection: %+v", selected) } if stale { w.valid.Store(false) } return selected.With(ctx, func() error { effects++; return nil }) } if err := Pass(t.Context(), reg, s, pub, t.TempDir(), func(string) error { t.Fatal("legacy callback"); return nil }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{w}}), WithSelectedRestarts(restart, nil)); err != nil { t.Fatal(err) } rt, err := reg.Get("app.example") if err != nil { t.Fatal(err) } if stale && (effects != 0 || rt.Version != "") || !stale && (effects != 1 || rt.Version != "v1") { t.Fatalf("effects=%d version=%s", effects, rt.Version) } }) } } func TestPassClaimRollbackStartRetainsSelection(t *testing.T) { s := NewDirStore(t.TempDir()) priv, pub := testKeys(t) reg := testRegistry(t) t.Cleanup(func() { reg.Close() }) opt := t.TempDir() for _, v := range []string{"v1", "v2"} { shipRelease(t, s, "bab", "myapp", v, adoptPlatform, []byte(v)) } if err := StageArtifact(opt, "bab", "v1", "myapp", []byte("v1")); err != nil { t.Fatal(err) } if err := PointLive(opt, "app.example", "bab", "v1", "myapp"); err != nil { t.Fatal(err) } if err := Promote(t.Context(), s, priv, "bab", "myapp", "canary/x", "v2"); err != nil { t.Fatal(err) } addRoute(t, reg, "app.example", "myapp", "canary/x", false) w := newPausedReleaseWork(0) var first hostwrite.Selection starts, callbacks := 0, 0 restart := func(ctx context.Context, s hostwrite.Selection) error { first = s return s.With(ctx, func() error { return errors.New("restart failed") }) } start := func(ctx context.Context, s hostwrite.Selection) error { callbacks++ if s.Work != first.Work || s.Identity != first.Identity || s.Host != first.Host { t.Fatal("rollback recaptured authority") } w.valid.Store(false) return s.With(ctx, func() error { starts++; return nil }) } if err := Pass(t.Context(), reg, s, pub, opt, func(string) error { t.Fatal("legacy restart"); return nil }, WithHostWrites(hostwrite.Config{Enabled: true, Selector: oneReleaseSelector{w}}), WithSelectedRestarts(restart, start), WithStartUnit(func(string) error { t.Fatal("legacy start"); return nil })); err != nil { t.Fatal(err) } if callbacks != 1 || starts != 0 { t.Fatalf("callbacks=%d starts=%d", callbacks, starts) } if live, err := LiveVersion(opt, "app.example"); err != nil || live != "v1" { t.Fatalf("rollback=%s %v", live, err) } }