|
4 | 4 | baseLimitEnvVar, |
5 | 5 | checkOrgRateLimit, |
6 | 6 | getBaseLimit, |
| 7 | + getOrgSpendTier, |
7 | 8 | getPlanClass, |
8 | | - getSpendTierMultiplier, |
9 | 9 | isOrgRateLimitEnabled, |
10 | 10 | PATH_RATE_LIMITS, |
11 | 11 | resolvePathRateLimit, |
@@ -54,12 +54,8 @@ const ENV_KEYS = [ |
54 | 54 | "GATEWAY_RATE_LIMIT_CHAT_COMPLETIONS_RPM", |
55 | 55 | "GATEWAY_RATE_LIMIT_DEV_CHAT_COMPLETIONS_RPM", |
56 | 56 | "GATEWAY_RATE_LIMIT_CHATPLAN_CHAT_COMPLETIONS_RPM", |
57 | | - "GATEWAY_RATE_LIMIT_TIER_1_THRESHOLD", |
58 | | - "GATEWAY_RATE_LIMIT_TIER_2_THRESHOLD", |
59 | | - "GATEWAY_RATE_LIMIT_TIER_3_THRESHOLD", |
60 | | - "GATEWAY_RATE_LIMIT_TIER_1_MULTIPLIER", |
61 | | - "GATEWAY_RATE_LIMIT_TIER_3_MULTIPLIER", |
62 | | - "GATEWAY_RATE_LIMIT_TIER_0_MULTIPLIER", |
| 57 | + "GATEWAY_SPEND_TIER_1_SPEND_USD", |
| 58 | + "GATEWAY_SPEND_TIER_1_RPM_MULTIPLIER", |
63 | 59 | "GATEWAY_RATE_LIMIT_WINDOW_SECONDS", |
64 | 60 | ]; |
65 | 61 |
|
@@ -125,34 +121,68 @@ describe("isOrgRateLimitEnabled", () => { |
125 | 121 | }); |
126 | 122 | }); |
127 | 123 |
|
128 | | -describe("getSpendTierMultiplier", () => { |
129 | | - it("returns tier 0 below the first threshold", () => { |
130 | | - expect(getSpendTierMultiplier(0)).toEqual({ tier: 0, multiplier: 1 }); |
131 | | - expect(getSpendTierMultiplier(999)).toEqual({ tier: 0, multiplier: 1 }); |
| 124 | +describe("getOrgSpendTier", () => { |
| 125 | + const NOW = Date.UTC(2026, 0, 15); |
| 126 | + const daysAgo = (n: number) => { |
| 127 | + const offsetMs = n * 86_400_000; |
| 128 | + return new Date(NOW - offsetMs); |
| 129 | + }; |
| 130 | + |
| 131 | + it("classifies a brand-new $0 org as T0", () => { |
| 132 | + expect(getOrgSpendTier({ createdAt: daysAgo(0) }, 0, NOW)).toMatchObject({ |
| 133 | + tier: 0, |
| 134 | + rpmMultiplier: 1, |
| 135 | + dailyCapUsd: 5, |
| 136 | + monthlyCapUsd: 50, |
| 137 | + }); |
| 138 | + }); |
| 139 | + |
| 140 | + it("qualifies by account age alone (spend $0)", () => { |
| 141 | + expect(getOrgSpendTier({ createdAt: daysAgo(8) }, 0, NOW).tier).toBe(1); |
| 142 | + expect(getOrgSpendTier({ createdAt: daysAgo(31) }, 0, NOW).tier).toBe(2); |
| 143 | + expect(getOrgSpendTier({ createdAt: daysAgo(61) }, 0, NOW).tier).toBe(3); |
| 144 | + expect(getOrgSpendTier({ createdAt: daysAgo(90) }, 0, NOW).tier).toBe(4); |
132 | 145 | }); |
133 | 146 |
|
134 | | - it("returns tier 1 at $1k", () => { |
135 | | - expect(getSpendTierMultiplier(1_000)).toEqual({ tier: 1, multiplier: 2 }); |
136 | | - expect(getSpendTierMultiplier(9_999)).toEqual({ tier: 1, multiplier: 2 }); |
| 147 | + it("qualifies by lifetime spend alone on a brand-new org", () => { |
| 148 | + const org = { createdAt: daysAgo(0) }; |
| 149 | + expect(getOrgSpendTier(org, 10, NOW).tier).toBe(1); |
| 150 | + expect(getOrgSpendTier(org, 100, NOW).tier).toBe(2); |
| 151 | + expect(getOrgSpendTier(org, 1_000, NOW).tier).toBe(3); |
| 152 | + expect(getOrgSpendTier(org, 5_000, NOW).tier).toBe(4); |
| 153 | + expect(getOrgSpendTier(org, 1_000_000, NOW).tier).toBe(4); |
137 | 154 | }); |
138 | 155 |
|
139 | | - it("returns tier 2 at $10k", () => { |
140 | | - expect(getSpendTierMultiplier(10_000)).toEqual({ tier: 2, multiplier: 4 }); |
| 156 | + it("takes the highest of the age-or-spend qualifiers", () => { |
| 157 | + // 31 days old (age → T2) but $1,000 lifetime spend (spend → T3) => T3 |
| 158 | + expect(getOrgSpendTier({ createdAt: daysAgo(31) }, 1_000, NOW).tier).toBe( |
| 159 | + 3, |
| 160 | + ); |
141 | 161 | }); |
142 | 162 |
|
143 | | - it("returns tier 3 at $50k", () => { |
144 | | - expect(getSpendTierMultiplier(50_000)).toEqual({ tier: 3, multiplier: 10 }); |
145 | | - expect(getSpendTierMultiplier(1_000_000)).toEqual({ |
146 | | - tier: 3, |
147 | | - multiplier: 10, |
| 163 | + it("treats thresholds as inclusive", () => { |
| 164 | + expect(getOrgSpendTier({ createdAt: daysAgo(7) }, 0, NOW).tier).toBe(1); |
| 165 | + expect(getOrgSpendTier({ createdAt: daysAgo(6) }, 9, NOW).tier).toBe(0); |
| 166 | + }); |
| 167 | + |
| 168 | + it("exposes the caps and multiplier for the resolved tier", () => { |
| 169 | + expect( |
| 170 | + getOrgSpendTier({ createdAt: daysAgo(0) }, 5_000, NOW), |
| 171 | + ).toMatchObject({ |
| 172 | + tier: 4, |
| 173 | + rpmMultiplier: 20, |
| 174 | + dailyCapUsd: 15_000, |
| 175 | + monthlyCapUsd: 200_000, |
148 | 176 | }); |
149 | 177 | }); |
150 | 178 |
|
151 | | - it("respects env overrides for thresholds and multipliers", () => { |
152 | | - process.env.GATEWAY_RATE_LIMIT_TIER_1_THRESHOLD = "500"; |
153 | | - process.env.GATEWAY_RATE_LIMIT_TIER_1_MULTIPLIER = "3"; |
154 | | - expect(getSpendTierMultiplier(500)).toEqual({ tier: 1, multiplier: 3 }); |
155 | | - expect(getSpendTierMultiplier(499)).toEqual({ tier: 0, multiplier: 1 }); |
| 179 | + it("respects env overrides for tier thresholds and values", () => { |
| 180 | + process.env.GATEWAY_SPEND_TIER_1_SPEND_USD = "5"; |
| 181 | + process.env.GATEWAY_SPEND_TIER_1_RPM_MULTIPLIER = "3"; |
| 182 | + expect(getOrgSpendTier({ createdAt: daysAgo(0) }, 5, NOW)).toMatchObject({ |
| 183 | + tier: 1, |
| 184 | + rpmMultiplier: 3, |
| 185 | + }); |
156 | 186 | }); |
157 | 187 | }); |
158 | 188 |
|
|
0 commit comments