Skip to content

Commit 5395836

Browse files
authored
Merge pull request #3264 from useautumn/charlie/alert-basis-edge-tests
test(balances): usage alert basis edge cases and free-tier daily cap end to end
2 parents b561e78 + 48cc8c1 commit 5395836

120 files changed

Lines changed: 5467 additions & 774 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/atmn/src/compose/models/planModels.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ type UsageLimit = {
7979
enabled?: boolean;
8080
limit: number;
8181
interval: "day" | "week" | "month" | "year";
82+
anchor?: "billing_cycle" | "utc";
83+
filter?: UsageLimitFilter;
84+
};
85+
86+
type UsageLimitFilter = {
87+
properties: Record<string, string>;
8288
};
8389

8490
type UsageAlert = {
@@ -90,6 +96,8 @@ type UsageAlert = {
9096
| "usage_percentage"
9197
| "remaining"
9298
| "remaining_percentage";
99+
basis?: "balance" | "included" | "recurring" | "usage_limit";
100+
filter?: UsageLimitFilter;
93101
name?: string;
94102
};
95103

server/src/internal/balances/trackWebhooks/checkLimitReached.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import {
44
apiBalanceToAllowed,
55
type Feature,
66
type FullCustomer,
7+
type FullSubject,
78
fullCustomerToTags,
8-
usageLimitFilterMatchesProperties,
99
WebhookEventType,
1010
} from "@autumn/shared";
1111
import { sendSvixEvent } from "@/external/svix/svixHelpers.js";
1212
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
13+
import { findBlockedFilterOnSubject } from "./limitReached/findBlockedFilterOnSubject.js";
14+
import { findBlockingUsageLimit } from "./limitReached/findBlockingUsageLimit.js";
1315

1416
// Subjects must be built via buildEvaluationSubject, or plan-level / percentage
1517
// caps are invisible here and the allowed -> blocked transition never fires.
@@ -18,6 +20,7 @@ export const checkLimitReached = async ({
1820
oldEvalSubject,
1921
newEvalSubject,
2022
newFullCus,
23+
newFullSubject,
2124
feature,
2225
entityId,
2326
eventProperties,
@@ -26,6 +29,7 @@ export const checkLimitReached = async ({
2629
oldEvalSubject: ApiCustomerV5 | ApiEntityV2;
2730
newEvalSubject: ApiCustomerV5 | ApiEntityV2;
2831
newFullCus: FullCustomer;
32+
newFullSubject?: FullSubject;
2933
feature: Feature;
3034
entityId?: string;
3135
eventProperties?: Record<string, unknown> | null;
@@ -54,21 +58,24 @@ export const checkLimitReached = async ({
5458

5559
if (!oldResult.allowed || newResult.allowed) return;
5660

57-
// When the blocking cap is a filtered usage limit, attach its filter so
58-
// the receiver knows WHICH slice (e.g. which API key) hit its cap.
59-
const blockedFilter =
60-
newResult.limitType === "usage_limit" && eventProperties
61-
? newEvalSubject.billing_controls?.usage_limits?.find(
62-
(usageLimit) =>
63-
usageLimit.feature_id === feature.id &&
64-
usageLimit.enabled !== false &&
65-
usageLimit.filter != null &&
66-
usageLimitFilterMatchesProperties({
67-
filterProperties: usageLimit.filter.properties,
68-
eventProperties,
69-
}) &&
70-
(usageLimit.usage ?? 0) >= usageLimit.limit,
71-
)?.filter
61+
const blockedByUsageLimit = newResult.limitType === "usage_limit";
62+
const blockingUsageLimit =
63+
blockedByUsageLimit && newFullSubject
64+
? findBlockingUsageLimit({
65+
ctx,
66+
fullSubject: newFullSubject,
67+
feature,
68+
eventProperties,
69+
})
70+
: undefined;
71+
const blockedFilter = blockingUsageLimit
72+
? blockingUsageLimit.filter
73+
: blockedByUsageLimit && eventProperties
74+
? findBlockedFilterOnSubject({
75+
subject: newEvalSubject,
76+
feature,
77+
eventProperties,
78+
})
7279
: undefined;
7380

7481
const customerId = newFullCus.id || newFullCus.internal_id;
@@ -83,6 +90,7 @@ export const checkLimitReached = async ({
8390
limit_type: newResult.limitType ?? "included",
8491
...(entityId && { entity_id: entityId }),
8592
...(blockedFilter && { filter: blockedFilter }),
93+
...(blockingUsageLimit && { usage_limit: blockingUsageLimit.block }),
8694
},
8795
tags,
8896
});

0 commit comments

Comments
 (0)