Skip to content

LCORE-4610: unit and integration tests for quota management - #3134

Open
max-svistunov wants to merge 3 commits into
lightspeed-core:mainfrom
max-svistunov:lcore-4610-quota-management-tests-part1
Open

max-svistunov wants to merge 3 commits into
lightspeed-core:mainfrom
max-svistunov:lcore-4610-quota-management-tests-part1

Conversation

@max-svistunov

@max-svistunov max-svistunov commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Description

Part 1 of 2 for LCORE-4610: unit and integration tests for quota management. Tests only; nothing under src/ changes. Part 2 (#3135) adds the e2e feature on top of this.

Integration (real limiters on a real SQLite file, real handlers, only the model mocked):

  • tests/integration/test_quota_scheduler.py: the periodic renewal that nothing executed so far. Reset and increase apply only once a limiter's period has elapsed and exactly once per period (both statements move the revoked_at stamp; a row the scheduler itself stamped renews again), the stamp the limiter writes on a new row is read back through SQLite's own datetime() and is the current time (the limiter's half of LCORE-4606: a stamp SQLite can not parse is never renewed, silently), a zero increase or zero initial quota runs no statement, a renewal touches only its own subject, one pass of the scheduler loop renews and sleeps for its period, the scheduler creates quota_limits on a database no limiter has opened (on a fresh deployment it starts before the first request builds the limiters), a database error on one limiter is logged and the next limiter is still renewed, and a connection lost between two ticks is replaced. Time is never waited for: revoked_at is moved into the past, with a ten second period wherever a slow runner could otherwise let a two second one elapse between two steps.
  • tests/integration/endpoints/test_quota_endpoints.py: /v1/query, /v1/streaming_query (end event), /v1/responses blocking and streaming (in_progress carries {}, the terminal event the balance), and /v1/infer for every quota_subject (user id, org id, system id, and the fallbacks) charge the turn and report the balance; a one-token user is served once and then gets the documented 429 body, also over HTTP, calling no model and creating no conversation; a streamed /v1/responses turn an input shield blocks calls no model, costs nothing and still ends with the balance; user and cluster limiters together, with the first exhausted one naming the cause; token history accumulation with and without limiters; a limiter reconnects after its connection died; an unreachable database is a 500 before the model is called, and a database that fails after the model answered (charging the turn, or reading the balance for the response) is a 500 as well.
  • Unit: tests/unit/runners/test_quota_scheduler.py (statement selection, subject mapping, early returns, reconnection attempts, PostgreSQL statement shape with a mocked connection; the order of the increase and the reset is deliberately not pinned, see below) and tests/unit/quota/test_token_usage_history.py.

PostgreSQL gets shape tests only: no PostgreSQL service exists in CI or locally.

One behaviour is documented rather than asserted: with initial_quota and quota_increase both set, the quota.quota_limiter module promises a reset to their sum (150 for 100 + 50). The scheduler runs the two statements separately and each stamps revoked_at, so whichever runs first hides the other: the code gives 120 today, and swapping the order would give 100, never 150. test_initial_quota_and_increase_together_reset_to_their_sum is xfail(strict=True) with that reason, so the discrepancy is visible and the test turns red the day it is fixed. Ticket to be filed.

Type of change

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library [pyproject.toml + uv.lock]
  • Bump-up dependent library [requirements.*.txt for Konflux]
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Konflux configuration change
  • Unit tests improvement
  • Integration tests improvement
  • End to end tests improvement
  • Benchmarks improvement

Tools used to create PR

Identify any AI code assistants used in this PR (for transparency and review context)

  • Assisted-by: Claude Opus 4.8
  • Generated by: Claude Opus 4.8

Related Tickets & Documents

  • Related Issue # LCORE-4610

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

Run the new tests:

uv run pytest tests/unit/runners/test_quota_scheduler.py tests/unit/quota/test_token_usage_history.py
uv run pytest tests/integration/test_quota_scheduler.py tests/integration/endpoints/test_quota_endpoints.py

Expected: 26 unit tests pass; 41 integration tests pass and one is reported as XFAIL (test_initial_quota_and_increase_together_reset_to_their_sum, see the description).

What was run here: the new tests pass and were run three times in a row without a flake; the full tests/unit and tests/integration suites (without container_lifecycle) pass; ruff, black, pylint and pyright report nothing new on the touched files. The tests were also checked against hand-made production mutants (an increase that does not move the stamp, a new row with a NULL stamp, a scheduler that skips table creation, a revocation error that escapes the loop, among others): every one of them is caught by at least one of the new tests, except the increase/reset order, which is left unpinned on purpose.

The quota scheduler and the token usage history had no direct tests:
nothing failed if a limiter type mapped to the wrong subject, if the
scheduler started without storage, or if the PostgreSQL statements took
different parameters from the SQLite ones.

Cover the subject mapping, the choice of statements, connection checks,
the startup conditions and reconnection attempts of the scheduler, and
the parameters of the PostgreSQL statements, which no test can run
against a real server here. For the token usage history cover the
running totals per user, provider and model, reconnecting after a closed
connection, running without storage, and a write failing loudly when its
table is gone.
LCORE-4606 showed that the renewal statements were never executed by any
test: on SQLite a quota was renewed at every scheduler run. These tests
run the real statements and scheduler functions on a real database.

They pin that a quota is renewed only once its period has elapsed and
once per period, for both the reset and the increase; that the time a
limiter stamps on a new row is read correctly by SQLite; that a zero
value skips its statement; that a renewal touches only the rows of its
own subject; and that the scheduler loop creates the table, survives a
failing limiter and reconnects after its connection died.

With initial_quota and quota_increase both set, the increase runs first
and stamps revoked_at, so the reset of the same run no longer matches
and the balance is not what the quota_limiter module documents. That is
pinned as a strict xfail rather than fixed here.
Add integration tests that send requests through the real endpoints with
real limiters on SQLite, so the client's view and the database are
compared.

They cover /v1/query, /v1/streaming_query, /v1/responses (blocking and
streamed) and /v1/infer: a turn is charged and the balance reported, an
exhausted user is rejected with the 429 body, a shield-blocked streamed
turn costs nothing, and /v1/infer is not enforced without a quota
subject. They also cover the shared cluster balance, which limiter is
named when both are exhausted, the token history, a limiter reconnecting
after its connection died, and an unreachable database giving a 500
instead of a served turn, also when the error comes after the model
answered.
@coderabbitai

coderabbitai Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 31 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 1aee84a and e3661b6.

Files selected for processing: 8
  • tests/integration/_quota_helpers.py
  • tests/integration/endpoints/README.md
  • tests/integration/endpoints/test_quota_endpoints.py
  • tests/integration/test_quota_scheduler.py
  • tests/unit/quota/README.md
  • tests/unit/quota/test_token_usage_history.py
  • tests/unit/runners/README.md
  • tests/unit/runners/test_quota_scheduler.py
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant