rate-limits refactor for single point of truth 2

This commit is contained in:
simonredfern 2025-12-23 22:46:35 +01:00
parent a9a7384088
commit 1eaaa50d8f
4 changed files with 108 additions and 38 deletions

View File

@ -4157,14 +4157,14 @@ object SwaggerDefinitionsJSON {
)
lazy val activeCallLimitsJsonV600 = ActiveCallLimitsJsonV600(
call_limits = List(callLimitJsonV600),
considered_rate_limit_ids = List("80e1e0b2-d8bf-4f85-a579-e69ef36e3305"),
active_at_date = DateWithDayExampleObject,
total_per_second_call_limit = 100,
total_per_minute_call_limit = 1000,
total_per_hour_call_limit = -1,
total_per_day_call_limit = -1,
total_per_week_call_limit = -1,
total_per_month_call_limit = -1
active_per_second_rate_limit = 100,
active_per_minute_rate_limit = 1000,
active_per_hour_rate_limit = -1,
active_per_day_rate_limit = -1,
active_per_week_rate_limit = -1,
active_per_month_rate_limit = -1
)
lazy val accountWebhookPostJson = AccountWebhookPostJson(

View File

@ -457,10 +457,10 @@ trait APIMethods600 {
implementedInApiVersion,
nameOf(getActiveCallLimitsAtDate),
"GET",
"/management/consumers/CONSUMER_ID/consumer/rate-limits/active-at-date/DATE",
"/management/consumers/CONSUMER_ID/consumer/active-rate-limits/DATE",
"Get Active Rate Limits at Date",
s"""
|Get the sum of rate limits at a certain date time. This returns a SUM of all the records that span that time.
|Get the active rate limits for a consumer at a specific date. Returns the aggregated rate limits from all active records at that time.
|
|Date format: YYYY-MM-DDTHH:MM:SSZ (e.g. 1099-12-31T23:00:00Z)
|
@ -482,7 +482,7 @@ trait APIMethods600 {
lazy val getActiveCallLimitsAtDate: OBPEndpoint = {
case "management" :: "consumers" :: consumerId :: "consumer" :: "rate-limits" :: "active-at-date" :: dateString :: Nil JsonGet _ =>
case "management" :: "consumers" :: consumerId :: "consumer" :: "active-rate-limits" :: dateString :: Nil JsonGet _ =>
cc =>
implicit val ec = EndpointContext(Some(cc))
for {
@ -494,8 +494,10 @@ trait APIMethods600 {
format.parse(dateString)
}
rateLimit <- RateLimitingUtil.getActiveRateLimits(consumerId, date)
rateLimitRecords <- RateLimitingDI.rateLimiting.vend.getActiveCallLimitsByConsumerIdAtDate(consumerId, date)
rateLimitIds = rateLimitRecords.map(_.rateLimitingId)
} yield {
(JSONFactory600.createActiveCallLimitsJsonV600FromCallLimit(rateLimit, date), HttpCode.`200`(callContext))
(JSONFactory600.createActiveCallLimitsJsonV600FromCallLimit(rateLimit, rateLimitIds, date), HttpCode.`200`(callContext))
}
}

View File

@ -99,14 +99,14 @@ case class CallLimitJsonV600(
)
case class ActiveCallLimitsJsonV600(
call_limits: List[CallLimitJsonV600],
considered_rate_limit_ids: List[String],
active_at_date: java.util.Date,
total_per_second_call_limit: Long,
total_per_minute_call_limit: Long,
total_per_hour_call_limit: Long,
total_per_day_call_limit: Long,
total_per_week_call_limit: Long,
total_per_month_call_limit: Long
active_per_second_rate_limit: Long,
active_per_minute_rate_limit: Long,
active_per_hour_rate_limit: Long,
active_per_day_rate_limit: Long,
active_per_week_rate_limit: Long,
active_per_month_rate_limit: Long
)
case class RateLimitV600(
@ -574,32 +574,34 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
rateLimitings: List[code.ratelimiting.RateLimiting],
activeDate: java.util.Date
): ActiveCallLimitsJsonV600 = {
val callLimits = rateLimitings.map(createCallLimitJsonV600)
val rateLimitIds = rateLimitings.map(_.rateLimitingId)
ActiveCallLimitsJsonV600(
call_limits = callLimits,
considered_rate_limit_ids = rateLimitIds,
active_at_date = activeDate,
total_per_second_call_limit = rateLimitings.map(_.perSecondCallLimit).sum,
total_per_minute_call_limit = rateLimitings.map(_.perMinuteCallLimit).sum,
total_per_hour_call_limit = rateLimitings.map(_.perHourCallLimit).sum,
total_per_day_call_limit = rateLimitings.map(_.perDayCallLimit).sum,
total_per_week_call_limit = rateLimitings.map(_.perWeekCallLimit).sum,
total_per_month_call_limit = rateLimitings.map(_.perMonthCallLimit).sum
active_per_second_rate_limit = rateLimitings.map(_.perSecondCallLimit).sum,
active_per_minute_rate_limit = rateLimitings.map(_.perMinuteCallLimit).sum,
active_per_hour_rate_limit = rateLimitings.map(_.perHourCallLimit).sum,
active_per_day_rate_limit = rateLimitings.map(_.perDayCallLimit).sum,
active_per_week_rate_limit = rateLimitings.map(_.perWeekCallLimit).sum,
active_per_month_rate_limit = rateLimitings.map(_.perMonthCallLimit).sum
)
}
def createActiveCallLimitsJsonV600FromCallLimit(
rateLimit: code.api.util.RateLimitingJson.CallLimit,
rateLimitIds: List[String],
activeDate: java.util.Date
): ActiveCallLimitsJsonV600 = {
ActiveCallLimitsJsonV600(
call_limits = List.empty,
considered_rate_limit_ids = rateLimitIds,
active_at_date = activeDate,
total_per_second_call_limit = rateLimit.per_second,
total_per_minute_call_limit = rateLimit.per_minute,
total_per_hour_call_limit = rateLimit.per_hour,
total_per_day_call_limit = rateLimit.per_day,
total_per_week_call_limit = rateLimit.per_week,
total_per_month_call_limit = rateLimit.per_month
active_per_second_rate_limit = rateLimit.per_second,
active_per_minute_rate_limit = rateLimit.per_minute,
active_per_hour_rate_limit = rateLimit.per_hour,
active_per_day_rate_limit = rateLimit.per_day,
active_per_week_rate_limit = rateLimit.per_week,
active_per_month_rate_limit = rateLimit.per_month
)
}

View File

@ -41,7 +41,7 @@ import java.time.format.DateTimeFormatter
import java.time.{ZoneOffset, ZonedDateTime}
import java.util.Date
class CallLimitsTest extends V600ServerSetup {
class RateLimitsTest extends V600ServerSetup {
object VersionOfApi extends Tag(ApiVersion.v6_0_0.toString)
object ApiEndpoint1 extends Tag(nameOf(Implementations6_0_0.createCallLimits))
@ -171,15 +171,15 @@ class CallLimitsTest extends V600ServerSetup {
val currentDateString = ZonedDateTime
.now(ZoneOffset.UTC)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"))
val getRequest = (v6_0_0_Request / "management" / "consumers" / consumerId / "consumer" / "rate-limits" / "active-at-date" / currentDateString).GET <@ (user1)
val getRequest = (v6_0_0_Request / "management" / "consumers" / consumerId / "consumer" / "active-rate-limits" / currentDateString).GET <@ (user1)
val getResponse = makeGetRequest(getRequest)
Then("We should get a 200")
getResponse.code should equal(200)
And("we should get the active call limits response")
val activeCallLimits = getResponse.body.extract[ActiveCallLimitsJsonV600]
activeCallLimits.call_limits.size == 0
activeCallLimits.total_per_second_call_limit == 0L
activeCallLimits.considered_rate_limit_ids.size >= 0
activeCallLimits.active_per_second_rate_limit == 0L
}
scenario("We will try to get active call limits without proper role", ApiEndpoint3, VersionOfApi) {
@ -189,7 +189,7 @@ class CallLimitsTest extends V600ServerSetup {
val currentDateString = ZonedDateTime
.now(ZoneOffset.UTC)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"))
val getRequest = (v6_0_0_Request / "management" / "consumers" / consumerId / "consumer" / "rate-limits" / "active-at-date" / currentDateString).GET <@ (user1)
val getRequest = (v6_0_0_Request / "management" / "consumers" / consumerId / "consumer" / "active-rate-limits" / currentDateString).GET <@ (user1)
val getResponse = makeGetRequest(getRequest)
Then("We should get a 403")
@ -197,5 +197,71 @@ class CallLimitsTest extends V600ServerSetup {
And("error should be " + UserHasMissingRoles + CanGetRateLimits)
getResponse.body.extract[ErrorMessage].message should equal(UserHasMissingRoles + CanGetRateLimits)
}
scenario("We will get aggregated call limits for two overlapping rate limit records", ApiEndpoint3, VersionOfApi) {
Given("We create two call limit records with overlapping date ranges")
val Some((c, _)) = user1
val consumerId = Consumers.consumers.vend.getConsumerByConsumerKey(c.key).map(_.consumerId.get).getOrElse("")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateRateLimits.toString)
// Create first rate limit record
val fromDate1 = new Date()
val toDate1 = new Date(System.currentTimeMillis() + 172800000L) // +2 days
val rateLimit1 = CallLimitPostJsonV600(
from_date = fromDate1,
to_date = toDate1,
api_version = Some("v6.0.0"),
api_name = Some("testEndpoint1"),
bank_id = None,
per_second_call_limit = "10",
per_minute_call_limit = "100",
per_hour_call_limit = "1000",
per_day_call_limit = "5000",
per_week_call_limit = "-1",
per_month_call_limit = "-1"
)
val request1 = (v6_0_0_Request / "management" / "consumers" / consumerId / "consumer" / "rate-limits").POST <@ (user1)
val createResponse1 = makePostRequest(request1, write(rateLimit1))
createResponse1.code should equal(201)
// Create second rate limit record with same date range
val rateLimit2 = CallLimitPostJsonV600(
from_date = fromDate1,
to_date = toDate1,
api_version = Some("v6.0.0"),
api_name = Some("testEndpoint2"),
bank_id = None,
per_second_call_limit = "5",
per_minute_call_limit = "50",
per_hour_call_limit = "500",
per_day_call_limit = "2500",
per_week_call_limit = "-1",
per_month_call_limit = "-1"
)
val request2 = (v6_0_0_Request / "management" / "consumers" / consumerId / "consumer" / "rate-limits").POST <@ (user1)
val createResponse2 = makePostRequest(request2, write(rateLimit2))
createResponse2.code should equal(201)
When("We get active call limits at a date within the overlapping range")
Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetRateLimits.toString)
val targetDate = ZonedDateTime
.now(ZoneOffset.UTC)
.plusDays(1) // Check 1 day from now (within the range)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"))
val getRequest = (v6_0_0_Request / "management" / "consumers" / consumerId / "consumer" / "active-rate-limits" / targetDate).GET <@ (user1)
val getResponse = makeGetRequest(getRequest)
Then("We should get a 200")
getResponse.code should equal(200)
And("the totals should be the sum of both records (using single source of truth aggregation)")
val activeCallLimits = getResponse.body.extract[ActiveCallLimitsJsonV600]
activeCallLimits.active_per_second_rate_limit should equal(15L) // 10 + 5
activeCallLimits.active_per_minute_rate_limit should equal(150L) // 100 + 50
activeCallLimits.active_per_hour_rate_limit should equal(1500L) // 1000 + 500
activeCallLimits.active_per_day_rate_limit should equal(7500L) // 5000 + 2500
activeCallLimits.active_per_week_rate_limit should equal(-1L) // -1 (both are -1, so unlimited)
activeCallLimits.active_per_month_rate_limit should equal(-1L) // -1 (both are -1, so unlimited)
}
}
}