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") } }