mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-11 12:09:03 +00:00
fix(bar): tolerate duplicate rows in alert pruning (#1553)
Bar alert pruning tolerates duplicate rows without crashing.
This commit is contained in:
1 parent
c0409862d2
commit
84ff4d57a4
2 files changed
+33
-3
No files matched your search
@@ -758,6 +758,35 @@ do {
|
|||||||
check(t1.firedKeys.isEmpty, "prune collapses stale-bucket + absent-account keys (bounded)")
|
check(t1.firedKeys.isEmpty, "prune collapses stale-bucket + absent-account keys (bounded)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (H) Duplicate account ids from a malformed summary must not crash pruning.
|
||||||
|
do {
|
||||||
|
let rows = [
|
||||||
|
BarSummaryRow(
|
||||||
|
accountId: "dup@example.com", provider: "agy", quotaPercentage: 5, quotaStatus: "ok",
|
||||||
|
nextReset: "2026-07-01T00:00:00Z"),
|
||||||
|
BarSummaryRow(
|
||||||
|
accountId: "dup@example.com", provider: "agy", quotaPercentage: 4, quotaStatus: "ok",
|
||||||
|
nextReset: "2026-08-01T00:00:00Z"),
|
||||||
|
]
|
||||||
|
let prior: Set<String> = [
|
||||||
|
"quotaRemainingBelow|agy:dup@example.com|2026-07-01T00:00:00Z|L10",
|
||||||
|
"quotaRemainingBelow|agy:dup@example.com|2026-08-01T00:00:00Z|L10",
|
||||||
|
"quotaRemainingBelow|agy:dup@example.com|2026-09-01T00:00:00Z|L10",
|
||||||
|
]
|
||||||
|
let ev = BarAlertEngine.evaluate(
|
||||||
|
rows: rows, analytics: nil, prefs: BarAlertPrefs(quotaLevels: [10]), priorFiredKeys: prior,
|
||||||
|
now: engineNow, calendar: utc)
|
||||||
|
check(
|
||||||
|
ev.firedKeys.contains("quotaRemainingBelow|agy:dup@example.com|2026-07-01T00:00:00Z|L10"),
|
||||||
|
"duplicate ids: prune keeps first present reset bucket")
|
||||||
|
check(
|
||||||
|
ev.firedKeys.contains("quotaRemainingBelow|agy:dup@example.com|2026-08-01T00:00:00Z|L10"),
|
||||||
|
"duplicate ids: prune keeps second present reset bucket")
|
||||||
|
check(
|
||||||
|
!ev.firedKeys.contains("quotaRemainingBelow|agy:dup@example.com|2026-09-01T00:00:00Z|L10"),
|
||||||
|
"duplicate ids: prune drops absent reset bucket")
|
||||||
|
}
|
||||||
|
|
||||||
// (H) Deterministic order: shuffled rows produce notifs in stable id order.
|
// (H) Deterministic order: shuffled rows produce notifs in stable id order.
|
||||||
do {
|
do {
|
||||||
let rows = [
|
let rows = [
|
||||||
|
|||||||
@@ -251,8 +251,9 @@ public enum BarAlertEngine {
|
|||||||
// PRUNE — keep the fired set bounded so it can't grow without limit across
|
// PRUNE — keep the fired set bounded so it can't grow without limit across
|
||||||
// day/month/reset rollovers or account churn.
|
// day/month/reset rollovers or account churn.
|
||||||
let presentIds = Set(rows.map { $0.id })
|
let presentIds = Set(rows.map { $0.id })
|
||||||
let presentResetBuckets: [String: String] = Dictionary(
|
let presentResetBuckets: [String: Set<String>] = rows.reduce(into: [:]) { buckets, row in
|
||||||
uniqueKeysWithValues: rows.map { ($0.id, $0.nextReset ?? "noreset") })
|
buckets[row.id, default: []].insert(row.nextReset ?? "noreset")
|
||||||
|
}
|
||||||
fired = fired.filter { key in
|
fired = fired.filter { key in
|
||||||
let parts = key.split(separator: "|", omittingEmptySubsequences: false).map(String.init)
|
let parts = key.split(separator: "|", omittingEmptySubsequences: false).map(String.init)
|
||||||
guard let kind = parts.first else { return false }
|
guard let kind = parts.first else { return false }
|
||||||
@@ -266,7 +267,7 @@ public enum BarAlertEngine {
|
|||||||
// parts: [kind, accountId, bucket, "L<level>"]; bucket must equal the
|
// parts: [kind, accountId, bucket, "L<level>"]; bucket must equal the
|
||||||
// account's CURRENT nextReset and the account must still be present.
|
// account's CURRENT nextReset and the account must still be present.
|
||||||
guard parts.count >= 3, presentIds.contains(parts[1]) else { return false }
|
guard parts.count >= 3, presentIds.contains(parts[1]) else { return false }
|
||||||
return presentResetBuckets[parts[1]] == parts[2]
|
return presentResetBuckets[parts[1]]?.contains(parts[2]) == true
|
||||||
case BarAlertKind.reauthNeeded.rawValue, BarAlertKind.accountCooldownOrPaused.rawValue:
|
case BarAlertKind.reauthNeeded.rawValue, BarAlertKind.accountCooldownOrPaused.rawValue:
|
||||||
// parts: [kind, accountId, "on"]; keep only for still-present accounts.
|
// parts: [kind, accountId, "on"]; keep only for still-present accounts.
|
||||||
return parts.count >= 2 && presentIds.contains(parts[1])
|
return parts.count >= 2 && presentIds.contains(parts[1])
|
||||||
|
|||||||
Reference in new issue
Block a user