Files
weichen 86e9d3706b feat(sysadmin): admin env settings + maintenance FD handoff
Persist agent/secrets settings with masked secrets and runner hot-reload;
add maintenance windows, maintpage binary, and zero-downtime listener handoff.
2026-09-24 23:20:20 +00:00

125 lines
2.7 KiB
Go

package maintenance
import (
"context"
"sync"
"testing"
"time"
)
type memStore struct {
mu sync.Mutex
next int64
rows map[int64]Window
}
func newMem() *memStore {
return &memStore{rows: map[int64]Window{}, next: 1}
}
func (m *memStore) GetOpen(_ context.Context) (*Window, error) {
m.mu.Lock()
defer m.mu.Unlock()
for _, w := range m.rows {
if w.Status == StatusScheduled || w.Status == StatusActive {
cp := w
return &cp, nil
}
}
return nil, ErrNotFound
}
func (m *memStore) GetByID(_ context.Context, id int64) (*Window, error) {
m.mu.Lock()
defer m.mu.Unlock()
w, ok := m.rows[id]
if !ok {
return nil, ErrNotFound
}
cp := w
return &cp, nil
}
func (m *memStore) Insert(_ context.Context, in Window) (Window, error) {
m.mu.Lock()
defer m.mu.Unlock()
for _, w := range m.rows {
if w.Status == StatusScheduled || w.Status == StatusActive {
return Window{}, ErrConflictOpen
}
}
in.ID = m.next
m.next++
in.Status = StatusScheduled
in.CreatedAt = time.Now()
in.UpdatedAt = in.CreatedAt
m.rows[in.ID] = in
return in, nil
}
func (m *memStore) UpdateStatus(_ context.Context, id int64, from, to string) (Window, error) {
m.mu.Lock()
defer m.mu.Unlock()
w, ok := m.rows[id]
if !ok || w.Status != from {
return Window{}, ErrBadStatus
}
w.Status = to
w.UpdatedAt = time.Now()
now := w.UpdatedAt
if to == StatusActive {
w.ActivatedAt = &now
}
if to == StatusEnded || to == StatusCancelled {
w.EndedAt = &now
}
m.rows[id] = w
return w, nil
}
func (m *memStore) ListRecent(_ context.Context, limit int) ([]Window, error) {
m.mu.Lock()
defer m.mu.Unlock()
out := make([]Window, 0, len(m.rows))
for _, w := range m.rows {
out = append(out, w)
}
if limit > 0 && len(out) > limit {
out = out[:limit]
}
return out, nil
}
func TestScheduleAndStatus(t *testing.T) {
svc := NewService(newMem())
start := time.Now().Add(2 * time.Hour)
end := start.Add(3 * time.Hour)
w, err := svc.Schedule(context.Background(), ScheduleInput{
Start: start, End: end, Message: "升级", CreatedBy: 1,
})
if err != nil {
t.Fatal(err)
}
view, err := svc.CurrentStatus(context.Background())
if err != nil || !view.Scheduled || view.WindowID != w.ID {
t.Fatalf("view=%+v err=%v", view, err)
}
if _, err := svc.Cancel(context.Background(), w.ID); err != nil {
t.Fatal(err)
}
view, _ = svc.CurrentStatus(context.Background())
if view.Scheduled || view.Active {
t.Fatalf("expected cleared, got %+v", view)
}
}
func TestInvalidWindow(t *testing.T) {
svc := NewService(newMem())
_, err := svc.Schedule(context.Background(), ScheduleInput{
Start: time.Now().Add(time.Hour), End: time.Now(), Message: "x",
})
if err == nil {
t.Fatal("expected error")
}
}