maven: fix test mocks for CalendarEvents interface (verification)
- Add CalendarEvents method to recordingAPI in auth_test.go - Add CalendarEvents method to fakeCore in handlers_test.go Co-Authored-By: opencode <opencode@anthropic.com>
This commit is contained in:
@@ -27,12 +27,12 @@ type RuleTrace struct {
|
||||
|
||||
// GateDetail — snapshot of the values the gate checked.
|
||||
type GateDetail struct {
|
||||
SnoozeUntil *time.Time `json:"snooze_until,omitempty"`
|
||||
CooldownUntil *time.Time `json:"cooldown_until,omitempty"`
|
||||
QuietHours bool `json:"quiet_hours"`
|
||||
CalendarBusy bool `json:"calendar_busy"`
|
||||
Presence string `json:"presence"` // "present"|"away"
|
||||
InertKeysMissing []string `json:"inert_keys_missing,omitempty"`
|
||||
SnoozeUntil *time.Time `json:"snooze_until,omitempty"`
|
||||
CooldownUntil *time.Time `json:"cooldown_until,omitempty"`
|
||||
QuietHours bool `json:"quiet_hours"`
|
||||
CalendarBusy bool `json:"calendar_busy"`
|
||||
Presence string `json:"presence"` // "present"|"away"
|
||||
InertKeysMissing []string `json:"inert_keys_missing,omitempty"`
|
||||
}
|
||||
|
||||
// ExplainGate runs the same checks as Gate() but records the first blocker.
|
||||
|
||||
@@ -119,4 +119,4 @@ func ParseCooldownFact(f store.Fact) (time.Duration, bool) {
|
||||
func MarshalCooldown(d time.Duration) string {
|
||||
b, _ := json.Marshal(int64(d)) // int64 marshal never errors
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,10 +97,10 @@ func TestFeedbackKeyMatchesRuleName(t *testing.T) {
|
||||
func TestParseCooldownFactRoundTrip(t *testing.T) {
|
||||
d := 45 * time.Minute
|
||||
f := store.Fact{
|
||||
Key: "cooldown:water",
|
||||
Key: "cooldown:water",
|
||||
Source: FeedbackSource,
|
||||
Value: MarshalCooldown(d),
|
||||
Ts: refTime(),
|
||||
Value: MarshalCooldown(d),
|
||||
Ts: refTime(),
|
||||
}
|
||||
got, ok := ParseCooldownFact(f)
|
||||
if !ok {
|
||||
@@ -149,4 +149,4 @@ func repeat(v string, n int) []string {
|
||||
out[i] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,4 +242,4 @@ func collapseReminders(due []store.Reminder) []store.Reminder {
|
||||
Status: "pending",
|
||||
Collapsed: due,
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,4 +134,4 @@ func CooldownFor(base time.Duration, lastSend time.Time) time.Time {
|
||||
return time.Time{} // never sent → no cooldown active
|
||||
}
|
||||
return lastSend.Add(base)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,9 +129,9 @@ func TestTickCooldownSuppresses(t *testing.T) {
|
||||
now := refTime()
|
||||
// 4h since water (would fire) — but cooldown until now+10min. Suppressed.
|
||||
s := State{
|
||||
Now: now,
|
||||
Presence: store.Present,
|
||||
Facts: map[string]store.Fact{
|
||||
Now: now,
|
||||
Presence: store.Present,
|
||||
Facts: map[string]store.Fact{
|
||||
"water": factAt("water", "tap:water", `"250ml"`, now.Add(-4*time.Hour)),
|
||||
},
|
||||
CooldownUntil: map[string]time.Time{
|
||||
@@ -197,9 +197,9 @@ func TestGateNoDataInertShutsUp(t *testing.T) {
|
||||
// is missing in the snapshot — gate must still return false.
|
||||
s := State{Now: refTime(), Presence: store.Present}
|
||||
iwantfire := Rule{
|
||||
Name: "x",
|
||||
Severity: Sev1,
|
||||
Predicate: func(State) bool { return true },
|
||||
Name: "x",
|
||||
Severity: Sev1,
|
||||
Predicate: func(State) bool { return true },
|
||||
InertWhenNoData: []string{"missing_key"},
|
||||
}
|
||||
if Gate(s, iwantfire) {
|
||||
@@ -379,4 +379,4 @@ func TestGathererUsesFeedbackTunedCooldown(t *testing.T) {
|
||||
if d, ok := ParseCooldownFact(fb); !ok || d != tuned {
|
||||
t.Fatalf("ParseCooldownFact round-trip: want %v ok, got %v ok=%v", tuned, d, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+21
-21
@@ -15,10 +15,10 @@ import "time"
|
||||
// auto-tuner scales it over time (mostly ignored → lengthen; acted → leave).
|
||||
// Bounds belong at the daemon-config level; here we just carry the base.
|
||||
type Rule struct {
|
||||
Name string
|
||||
Severity Severity
|
||||
Cooldown // base cooldown + bounded-duration envelope for the auto-tuner
|
||||
Predicate func(State) bool
|
||||
Name string
|
||||
Severity Severity
|
||||
Cooldown // base cooldown + bounded-duration envelope for the auto-tuner
|
||||
Predicate func(State) bool
|
||||
|
||||
// InertWhenNoData — most rules should be silent when their substrate key is
|
||||
// missing (since(key)==null → don't fire). If the predicate already encodes
|
||||
@@ -43,9 +43,9 @@ type Cooldown struct {
|
||||
// Inert when no water fact exists at all (shuts up when uncertain).
|
||||
func WaterRule() Rule {
|
||||
return Rule{
|
||||
Name: "water",
|
||||
Severity: Sev1,
|
||||
Cooldown: Cooldown{Base: 30 * time.Minute, Min: 15 * time.Minute, Max: 6 * time.Hour},
|
||||
Name: "water",
|
||||
Severity: Sev1,
|
||||
Cooldown: Cooldown{Base: 30 * time.Minute, Min: 15 * time.Minute, Max: 6 * time.Hour},
|
||||
InertWhenNoData: []string{"water"},
|
||||
Predicate: func(s State) bool {
|
||||
d, ok := s.Since("water")
|
||||
@@ -60,16 +60,16 @@ func WaterRule() Rule {
|
||||
// MealRule — sev1 care: if ≥6h since an `meal` fact, fire. Inert without data.
|
||||
func MealRule() Rule {
|
||||
return Rule{
|
||||
Name: "meal",
|
||||
Severity: Sev1,
|
||||
Cooldown: Cooldown{Base: 60 * time.Minute, Min: 30 * time.Minute, Max: 8 * time.Hour},
|
||||
Name: "meal",
|
||||
Severity: Sev1,
|
||||
Cooldown: Cooldown{Base: 60 * time.Minute, Min: 30 * time.Minute, Max: 8 * time.Hour},
|
||||
InertWhenNoData: []string{"meal"},
|
||||
Predicate: func(s State) bool {
|
||||
d, ok := s.Since("meal")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return d >= 6 * time.Hour
|
||||
return d >= 6*time.Hour
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -79,9 +79,9 @@ func MealRule() Rule {
|
||||
// Inert unless both exist — can't claim continuous activity without both anchors.
|
||||
func BreakRule() Rule {
|
||||
return Rule{
|
||||
Name: "break",
|
||||
Severity: Sev2,
|
||||
Cooldown: Cooldown{Base: 45 * time.Minute, Min: 20 * time.Minute, Max: 4 * time.Hour},
|
||||
Name: "break",
|
||||
Severity: Sev2,
|
||||
Cooldown: Cooldown{Base: 45 * time.Minute, Min: 20 * time.Minute, Max: 4 * time.Hour},
|
||||
InertWhenNoData: []string{"desk_active", "break"},
|
||||
Predicate: func(s State) bool {
|
||||
dDesk, ok1 := s.Since("desk_active")
|
||||
@@ -101,9 +101,9 @@ func BreakRule() Rule {
|
||||
// a compromised poller writing under a different source can't forge the trigger.
|
||||
func ServiceDownRule() Rule {
|
||||
return Rule{
|
||||
Name: "service_down",
|
||||
Severity: Sev4,
|
||||
Cooldown: Cooldown{Base: 15 * time.Minute, Min: 5 * time.Minute, Max: 1 * time.Hour},
|
||||
Name: "service_down",
|
||||
Severity: Sev4,
|
||||
Cooldown: Cooldown{Base: 15 * time.Minute, Min: 5 * time.Minute, Max: 1 * time.Hour},
|
||||
InertWhenNoData: []string{"service_down"},
|
||||
Predicate: func(s State) bool {
|
||||
f, ok := s.Fact("service_down")
|
||||
@@ -123,9 +123,9 @@ func ServiceDownRule() Rule {
|
||||
// the hard page. Provenance-scoped to poll:netdata.
|
||||
func NetdataCriticalRule() Rule {
|
||||
return Rule{
|
||||
Name: "netdata_critical",
|
||||
Severity: Sev3,
|
||||
Cooldown: Cooldown{Base: 20 * time.Minute, Min: 10 * time.Minute, Max: 2 * time.Hour},
|
||||
Name: "netdata_critical",
|
||||
Severity: Sev3,
|
||||
Cooldown: Cooldown{Base: 20 * time.Minute, Min: 10 * time.Minute, Max: 2 * time.Hour},
|
||||
InertWhenNoData: []string{"netdata_alarm"},
|
||||
Predicate: func(s State) bool {
|
||||
f, ok := s.Fact("netdata_alarm")
|
||||
@@ -148,4 +148,4 @@ func DefaultRules() []Rule {
|
||||
ServiceDownRule(),
|
||||
NetdataCriticalRule(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,4 +106,4 @@ func (s State) Since(key string) (time.Duration, bool) {
|
||||
return 0, true
|
||||
}
|
||||
return s.Now.Sub(f.Ts), true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user