test(05-01): add tests for budget policy evaluation and incident lifecycle

- Budget policy CRUD (save, update, delete)
- getActivePoliciesForRun with multi-scope matching
- getSpentInWindow with cents aggregation per scope (company/agent/project)
- evaluateBudgetGate strictest-wins logic (D-01)
- upsertBudgetIncident with deduplication (D-08)
- resolveIncidentsForPolicy and autoCloseExpiredIncidents (D-06)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-26 20:43:50 -04:00
1 parent 2be909b67c
commit 245eb4ccf3
2 files changed
+717

No files matched your search

+378
View File
@@ -0,0 +1,378 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
/**
* Tests for budget policy evaluation and cost aggregation logic.
*
* Tests pure functions extracted from the budget enforcement service:
* - getSpentInWindow: aggregates cost_entries by scope in cents
* - getActivePoliciesForRun: finds matching policies for company+agent+project
* - saveBudgetPolicy / deleteBudgetPolicy CRUD
* - evaluateBudgetGate: returns blocked when ANY scope is exhausted (strictest-wins D-01)
*/
import type { BudgetPolicyRecord } from "../../src/shared/types";
// ---------------------------------------------------------------------------
// Helper: simulate getSpentInWindow (SUM(amount_usd) * 100 rounded to cents)
// ---------------------------------------------------------------------------
function computeSpentCents(costEntries: Array<{ amountUsd: number | null }>): number {
const total = costEntries.reduce((sum, c) => sum + (c.amountUsd ?? 0), 0);
return Math.round(total * 100);
}
// ---------------------------------------------------------------------------
// Helper: simulate getCalendarMonthWindow
// ---------------------------------------------------------------------------
function getCalendarMonthWindow(now: Date = new Date()): { start: string; end: string } {
const year = now.getUTCFullYear();
const month = now.getUTCMonth();
const start = new Date(Date.UTC(year, month, 1)).toISOString();
const end = new Date(Date.UTC(year, month + 1, 1)).toISOString();
return { start, end };
}
// ---------------------------------------------------------------------------
// Helper: simulate evaluateBudgetGate (strictest-wins per D-01)
// ---------------------------------------------------------------------------
interface BudgetGateResult {
allowed: boolean;
blockedBy: Array<{
policyId: string;
scopeType: string;
scopeId: string;
spent: number;
limit: number;
}>;
}
function evaluateBudgetGate(
policies: BudgetPolicyRecord[],
getSpent: (policy: BudgetPolicyRecord) => number,
): BudgetGateResult {
if (policies.length === 0) {
return { allowed: true, blockedBy: [] };
}
const blockedBy: BudgetGateResult["blockedBy"] = [];
for (const policy of policies) {
if (!policy.hardStopEnabled) continue;
const spent = getSpent(policy);
if (spent >= policy.amount) {
blockedBy.push({
policyId: policy.id,
scopeType: policy.scopeType,
scopeId: policy.scopeId,
spent,
limit: policy.amount,
});
}
}
return { allowed: blockedBy.length === 0, blockedBy };
}
// ---------------------------------------------------------------------------
// Mock database for CRUD testing
// ---------------------------------------------------------------------------
function createMockDb() {
const policies: BudgetPolicyRecord[] = [];
const costEntries: Array<{
companyId: string;
agentId: string | null;
projectId: string | null;
amountUsd: number;
createdAt: string;
}> = [];
return {
policies,
costEntries,
saveBudgetPolicy(input: Partial<BudgetPolicyRecord> & { companyId: string; scopeType: string; scopeId: string; amount: number }) {
const id = input.id ?? crypto.randomUUID();
const existing = policies.findIndex((p) => p.id === id);
const ts = new Date().toISOString();
const policy: BudgetPolicyRecord = {
id,
companyId: input.companyId,
scopeType: input.scopeType as "company" | "agent" | "project",
scopeId: input.scopeId,
metric: input.metric ?? "billed_cents",
windowKind: input.windowKind ?? "calendar_month_utc",
amount: input.amount,
warnPercent: input.warnPercent ?? 80,
hardStopEnabled: input.hardStopEnabled ?? true,
notifyEnabled: input.notifyEnabled ?? true,
isActive: input.isActive ?? true,
createdAt: existing >= 0 ? policies[existing]!.createdAt : ts,
updatedAt: ts,
};
if (existing >= 0) {
policies[existing] = policy;
} else {
policies.push(policy);
}
return id;
},
deleteBudgetPolicy(companyId: string, policyId: string) {
const idx = policies.findIndex((p) => p.id === policyId && p.companyId === companyId);
if (idx >= 0) policies.splice(idx, 1);
},
listBudgetPolicies(companyId: string) {
return policies
.filter((p) => p.companyId === companyId)
.sort((a, b) => a.scopeType.localeCompare(b.scopeType) || a.createdAt.localeCompare(b.createdAt));
},
getActivePoliciesForRun(companyId: string, agentId: string, projectId: string | null) {
return policies.filter(
(p) =>
p.companyId === companyId &&
p.isActive &&
((p.scopeType === "company" && p.scopeId === companyId) ||
(p.scopeType === "agent" && p.scopeId === agentId) ||
(p.scopeType === "project" && projectId !== null && p.scopeId === projectId)),
);
},
getSpentInWindow(policy: BudgetPolicyRecord): number {
const window = getCalendarMonthWindow();
let filtered = costEntries.filter(
(c) => c.companyId === policy.companyId && c.createdAt >= window.start && c.createdAt < window.end,
);
if (policy.scopeType === "agent") {
filtered = filtered.filter((c) => c.agentId === policy.scopeId);
} else if (policy.scopeType === "project") {
filtered = filtered.filter((c) => c.projectId === policy.scopeId);
}
return computeSpentCents(filtered);
},
};
}
// ======================================================================
// Tests
// ======================================================================
describe("Budget Policy CRUD", () => {
let db: ReturnType<typeof createMockDb>;
beforeEach(() => {
db = createMockDb();
});
it("saveBudgetPolicy creates new policy and returns its ID", () => {
const id = db.saveBudgetPolicy({
companyId: "c1",
scopeType: "company",
scopeId: "c1",
amount: 10000,
});
expect(id).toBeTruthy();
expect(db.policies).toHaveLength(1);
expect(db.policies[0]!.amount).toBe(10000);
expect(db.policies[0]!.scopeType).toBe("company");
});
it("saveBudgetPolicy updates existing policy when ID provided", () => {
const id = db.saveBudgetPolicy({
companyId: "c1",
scopeType: "company",
scopeId: "c1",
amount: 10000,
});
db.saveBudgetPolicy({
id,
companyId: "c1",
scopeType: "company",
scopeId: "c1",
amount: 20000,
});
expect(db.policies).toHaveLength(1);
expect(db.policies[0]!.amount).toBe(20000);
});
it("deleteBudgetPolicy removes policy by ID", () => {
const id = db.saveBudgetPolicy({
companyId: "c1",
scopeType: "company",
scopeId: "c1",
amount: 10000,
});
expect(db.policies).toHaveLength(1);
db.deleteBudgetPolicy("c1", id);
expect(db.policies).toHaveLength(0);
});
});
describe("getActivePoliciesForRun", () => {
let db: ReturnType<typeof createMockDb>;
beforeEach(() => {
db = createMockDb();
});
it("returns all active policies matching company+agent+project scopes", () => {
db.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 });
db.saveBudgetPolicy({ companyId: "c1", scopeType: "agent", scopeId: "a1", amount: 10000 });
db.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 });
// Different company — should not match
db.saveBudgetPolicy({ companyId: "c2", scopeType: "company", scopeId: "c2", amount: 99999 });
const result = db.getActivePoliciesForRun("c1", "a1", "p1");
expect(result).toHaveLength(3);
expect(result.map((p) => p.scopeType).sort()).toEqual(["agent", "company", "project"]);
});
it("returns empty array when no policies exist (D-02: no policy = unlimited)", () => {
const result = db.getActivePoliciesForRun("c1", "a1", null);
expect(result).toEqual([]);
});
it("excludes project scope when projectId is null", () => {
db.saveBudgetPolicy({ companyId: "c1", scopeType: "company", scopeId: "c1", amount: 50000 });
db.saveBudgetPolicy({ companyId: "c1", scopeType: "project", scopeId: "p1", amount: 20000 });
const result = db.getActivePoliciesForRun("c1", "a1", null);
expect(result).toHaveLength(1);
expect(result[0]!.scopeType).toBe("company");
});
});
describe("getSpentInWindow", () => {
let db: ReturnType<typeof createMockDb>;
beforeEach(() => {
db = createMockDb();
});
it("returns SUM(amount_usd)*100 rounded to integer cents for company scope", () => {
const now = new Date();
db.costEntries.push(
{ companyId: "c1", agentId: "a1", projectId: null, amountUsd: 1.50, createdAt: now.toISOString() },
{ companyId: "c1", agentId: "a2", projectId: null, amountUsd: 2.30, createdAt: now.toISOString() },
);
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "company", scopeId: "c1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(),
};
const spent = db.getSpentInWindow(policy);
expect(spent).toBe(380); // (1.50 + 2.30) * 100 = 380 cents
});
it("filters by agent_id for agent scope", () => {
const now = new Date();
db.costEntries.push(
{ companyId: "c1", agentId: "a1", projectId: null, amountUsd: 5.00, createdAt: now.toISOString() },
{ companyId: "c1", agentId: "a2", projectId: null, amountUsd: 3.00, createdAt: now.toISOString() },
);
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "agent", scopeId: "a1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(),
};
const spent = db.getSpentInWindow(policy);
expect(spent).toBe(500); // Only a1's $5.00 = 500 cents
});
it("filters by project_id for project scope", () => {
const now = new Date();
db.costEntries.push(
{ companyId: "c1", agentId: "a1", projectId: "p1", amountUsd: 7.50, createdAt: now.toISOString() },
{ companyId: "c1", agentId: "a1", projectId: "p2", amountUsd: 2.00, createdAt: now.toISOString() },
);
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "project", scopeId: "p1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: now.toISOString(), updatedAt: now.toISOString(),
};
const spent = db.getSpentInWindow(policy);
expect(spent).toBe(750); // Only p1's $7.50 = 750 cents
});
});
describe("evaluateBudgetGate", () => {
it("returns allowed:true with empty blockedBy when no policies exist (D-02)", () => {
const result = evaluateBudgetGate([], vi.fn());
expect(result).toEqual({ allowed: true, blockedBy: [] });
});
it("returns allowed:true when no policies are exhausted", () => {
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "company", scopeId: "c1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 10000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: "", updatedAt: "",
};
const getSpent = vi.fn().mockReturnValue(5000); // 50% spent
const result = evaluateBudgetGate([policy], getSpent);
expect(result.allowed).toBe(true);
expect(result.blockedBy).toHaveLength(0);
});
it("returns blocked when ANY scope is exhausted (strictest-wins D-01)", () => {
const companyPolicy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "company", scopeId: "c1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 100000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: "", updatedAt: "",
};
const agentPolicy: BudgetPolicyRecord = {
id: "pol2", companyId: "c1", scopeType: "agent", scopeId: "a1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 5000, warnPercent: 80, hardStopEnabled: true, notifyEnabled: true,
isActive: true, createdAt: "", updatedAt: "",
};
const getSpent = vi.fn((policy: BudgetPolicyRecord) => {
if (policy.scopeType === "company") return 50000; // 50% of company
return 5500; // 110% of agent
});
const result = evaluateBudgetGate([companyPolicy, agentPolicy], getSpent);
expect(result.allowed).toBe(false);
expect(result.blockedBy).toHaveLength(1);
expect(result.blockedBy[0]!.policyId).toBe("pol2");
expect(result.blockedBy[0]!.scopeType).toBe("agent");
});
it("skips policies where hardStopEnabled is false", () => {
const policy: BudgetPolicyRecord = {
id: "pol1", companyId: "c1", scopeType: "company", scopeId: "c1",
metric: "billed_cents", windowKind: "calendar_month_utc",
amount: 1000, warnPercent: 80, hardStopEnabled: false, notifyEnabled: true,
isActive: true, createdAt: "", updatedAt: "",
};
const getSpent = vi.fn().mockReturnValue(5000); // Way over limit
const result = evaluateBudgetGate([policy], getSpent);
expect(result.allowed).toBe(true);
expect(result.blockedBy).toHaveLength(0);
});
});
describe("getCalendarMonthWindow", () => {
it("returns start of current month and start of next month in UTC", () => {
const testDate = new Date("2026-03-15T10:30:00Z");
const window = getCalendarMonthWindow(testDate);
expect(window.start).toBe("2026-03-01T00:00:00.000Z");
expect(window.end).toBe("2026-04-01T00:00:00.000Z");
});
it("handles December rollover to January next year", () => {
const testDate = new Date("2026-12-25T10:30:00Z");
const window = getCalendarMonthWindow(testDate);
expect(window.start).toBe("2026-12-01T00:00:00.000Z");
expect(window.end).toBe("2027-01-01T00:00:00.000Z");
});
});
+339
View File
@@ -0,0 +1,339 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
/**
* Tests for budget incident lifecycle: creation, deduplication (D-08),
* resolution, and auto-close of expired incidents (D-06).
*/
import type { BudgetPolicyRecord, BudgetIncidentRecord } from "../../src/shared/types";
// ---------------------------------------------------------------------------
// Mock incident store simulating upsertBudgetIncident, resolveIncidentsForPolicy,
// autoCloseExpiredIncidents behaviors from database.ts
// ---------------------------------------------------------------------------
function createMockIncidentDb() {
const incidents: BudgetIncidentRecord[] = [];
let idCounter = 0;
function getCalendarMonthWindow(now: Date = new Date()): { start: string; end: string } {
const year = now.getUTCFullYear();
const month = now.getUTCMonth();
const start = new Date(Date.UTC(year, month, 1)).toISOString();
const end = new Date(Date.UTC(year, month + 1, 1)).toISOString();
return { start, end };
}
return {
incidents,
getCalendarMonthWindow,
upsertBudgetIncident(
policy: BudgetPolicyRecord,
thresholdType: "warn_threshold" | "hard_stop",
amountObserved: number,
): string {
const window = getCalendarMonthWindow();
const existing = incidents.find(
(i) =>
i.policyId === policy.id &&
i.windowStart === window.start &&
i.thresholdType === thresholdType &&
i.status === "open",
);
if (existing) {
existing.amountObserved = amountObserved;
existing.updatedAt = new Date().toISOString();
return existing.id;
}
const id = `inc-${++idCounter}`;
const ts = new Date().toISOString();
incidents.push({
id,
companyId: policy.companyId,
policyId: policy.id,
scopeType: policy.scopeType,
scopeId: policy.scopeId,
metric: policy.metric,
windowKind: policy.windowKind,
windowStart: window.start,
windowEnd: window.end,
thresholdType,
amountLimit: policy.amount,
amountObserved,
status: "open",
resolvedAt: null,
createdAt: ts,
updatedAt: ts,
});
return id;
},
resolveIncidentsForPolicy(policyId: string, resolutionStatus: string): number {
let count = 0;
const ts = new Date().toISOString();
for (const incident of incidents) {
if (incident.policyId === policyId && incident.status === "open") {
incident.status = resolutionStatus as BudgetIncidentRecord["status"];
incident.resolvedAt = ts;
incident.updatedAt = ts;
count++;
}
}
return count;
},
resolveWarnIncidentsForPolicy(policyId: string, resolutionStatus: string): number {
let count = 0;
const ts = new Date().toISOString();
for (const incident of incidents) {
if (incident.policyId === policyId && incident.status === "open" && incident.thresholdType === "warn_threshold") {
incident.status = resolutionStatus as BudgetIncidentRecord["status"];
incident.resolvedAt = ts;
incident.updatedAt = ts;
count++;
}
}
return count;
},
autoCloseExpiredIncidents(companyId: string): number {
let count = 0;
const now = new Date().toISOString();
for (const incident of incidents) {
if (
incident.companyId === companyId &&
incident.status === "open" &&
incident.windowEnd <= now
) {
incident.status = "auto_closed";
incident.resolvedAt = now;
incident.updatedAt = now;
count++;
}
}
return count;
},
listBudgetIncidents(companyId: string, statusFilter: string = "all"): BudgetIncidentRecord[] {
return incidents
.filter((i) => i.companyId === companyId && (statusFilter === "all" || i.status === statusFilter))
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
},
resolveBudgetIncident(incidentId: string, companyId: string): boolean {
const incident = incidents.find((i) => i.id === incidentId && i.companyId === companyId && i.status === "open");
if (!incident) return false;
const ts = new Date().toISOString();
incident.status = "resolved";
incident.resolvedAt = ts;
incident.updatedAt = ts;
return true;
},
};
}
function makePolicy(overrides: Partial<BudgetPolicyRecord> = {}): BudgetPolicyRecord {
return {
id: "pol-1",
companyId: "c1",
scopeType: "agent",
scopeId: "a1",
metric: "billed_cents",
windowKind: "calendar_month_utc",
amount: 10000,
warnPercent: 80,
hardStopEnabled: true,
notifyEnabled: true,
isActive: true,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...overrides,
};
}
// ======================================================================
// Tests
// ======================================================================
describe("upsertBudgetIncident", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("creates new incident when none exists for policy+window+threshold_type", () => {
const policy = makePolicy();
const id = db.upsertBudgetIncident(policy, "warn_threshold", 8500);
expect(id).toBeTruthy();
expect(db.incidents).toHaveLength(1);
expect(db.incidents[0]!.thresholdType).toBe("warn_threshold");
expect(db.incidents[0]!.amountObserved).toBe(8500);
expect(db.incidents[0]!.status).toBe("open");
});
it("updates amount_observed when open incident already exists (D-08 dedup)", () => {
const policy = makePolicy();
const id1 = db.upsertBudgetIncident(policy, "warn_threshold", 8500);
const id2 = db.upsertBudgetIncident(policy, "warn_threshold", 9200);
expect(id1).toBe(id2); // Same incident, not a new one
expect(db.incidents).toHaveLength(1); // Still just one
expect(db.incidents[0]!.amountObserved).toBe(9200); // Updated
});
it("creates separate incidents for different threshold types", () => {
const policy = makePolicy();
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.upsertBudgetIncident(policy, "hard_stop", 10500);
expect(db.incidents).toHaveLength(2);
expect(db.incidents[0]!.thresholdType).toBe("warn_threshold");
expect(db.incidents[1]!.thresholdType).toBe("hard_stop");
});
});
describe("resolveIncidentsForPolicy", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("sets status to provided resolution reason for all open incidents", () => {
const policy = makePolicy();
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.upsertBudgetIncident(policy, "hard_stop", 10500);
const count = db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
expect(count).toBe(2);
expect(db.incidents[0]!.status).toBe("auto_resolved_limit_increase");
expect(db.incidents[1]!.status).toBe("auto_resolved_limit_increase");
expect(db.incidents[0]!.resolvedAt).toBeTruthy();
});
it("does not affect already-resolved incidents", () => {
const policy = makePolicy();
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.resolveIncidentsForPolicy("pol-1", "resolved");
// Now try to resolve again
const count = db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
expect(count).toBe(0);
expect(db.incidents[0]!.status).toBe("resolved"); // Unchanged
});
});
describe("autoCloseExpiredIncidents", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("closes incidents whose window_end is in the past", () => {
const policy = makePolicy();
// Manually create an expired incident
db.incidents.push({
id: "expired-1",
companyId: "c1",
policyId: "pol-1",
scopeType: "agent",
scopeId: "a1",
metric: "billed_cents",
windowKind: "calendar_month_utc",
windowStart: "2025-01-01T00:00:00.000Z",
windowEnd: "2025-02-01T00:00:00.000Z", // In the past
thresholdType: "warn_threshold",
amountLimit: 10000,
amountObserved: 8500,
status: "open",
resolvedAt: null,
createdAt: "2025-01-15T00:00:00.000Z",
updatedAt: "2025-01-15T00:00:00.000Z",
});
const count = db.autoCloseExpiredIncidents("c1");
expect(count).toBe(1);
expect(db.incidents[0]!.status).toBe("auto_closed");
expect(db.incidents[0]!.resolvedAt).toBeTruthy();
});
it("does not close incidents whose window_end is in the future", () => {
const policy = makePolicy();
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
// The auto-created incident has window_end in the future (end of current month)
const count = db.autoCloseExpiredIncidents("c1");
expect(count).toBe(0);
expect(db.incidents[0]!.status).toBe("open");
});
});
describe("listBudgetIncidents", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("returns all incidents for a company when status is 'all'", () => {
const policy = makePolicy();
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.upsertBudgetIncident(policy, "hard_stop", 10500);
db.resolveIncidentsForPolicy("pol-1", "resolved");
const all = db.listBudgetIncidents("c1", "all");
expect(all).toHaveLength(2);
});
it("filters by status when specified", () => {
const policy = makePolicy();
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.upsertBudgetIncident(policy, "hard_stop", 10500);
// Resolve only warn
db.resolveWarnIncidentsForPolicy("pol-1", "resolved");
const open = db.listBudgetIncidents("c1", "open");
expect(open).toHaveLength(1);
expect(open[0]!.thresholdType).toBe("hard_stop");
});
});
describe("checkAutoResolve (D-06)", () => {
let db: ReturnType<typeof createMockIncidentDb>;
beforeEach(() => {
db = createMockIncidentDb();
});
it("auto-resolves incidents when policy limit increases above observed amount", () => {
const policy = makePolicy({ amount: 10000 });
db.upsertBudgetIncident(policy, "hard_stop", 10500);
expect(db.incidents[0]!.status).toBe("open");
// Simulate limit increase: now checking if spent < new limit
// If spent (10500) < new policy.amount (20000), resolve
const spent = 10500;
const newAmount = 20000;
if (spent < newAmount) {
db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
}
expect(db.incidents[0]!.status).toBe("auto_resolved_limit_increase");
});
it("auto-resolves only warn incidents when below warn threshold but above hard stop", () => {
const policy = makePolicy({ amount: 10000, warnPercent: 80 });
db.upsertBudgetIncident(policy, "warn_threshold", 8500);
db.upsertBudgetIncident(policy, "hard_stop", 10500);
// Spent is now 7500 (below warn threshold of 8000 = 10000*80/100)
const spent = 7500;
const warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100);
if (spent < policy.amount) {
db.resolveIncidentsForPolicy("pol-1", "auto_resolved_limit_increase");
}
// Both resolved since spent < policy.amount
expect(db.incidents.every((i) => i.status === "auto_resolved_limit_increase")).toBe(true);
});
});