Skip to content

fix: 当用户限额未设置时,key 限额不生效#531

Merged
ding113 merged 1 commit intodevfrom
fix/key-quota-without-user-quota
Jan 4, 2026
Merged

fix: 当用户限额未设置时,key 限额不生效#531
ding113 merged 1 commit intodevfrom
fix/key-quota-without-user-quota

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Jan 4, 2026

背景

当用户层面限额被取消(如 user.dailyQuota = null)时,当前代理限流链路未检查 Key 的每日限额(key.limitDailyUsd),导致 Key 层限额不生效。

Root Cause:
PR #499 将新用户的默认 dailyQuota 从 100 改为 null (无限制),暴露了限流检查顺序的缺陷:原逻辑在用户每日额度(步骤 7)检查中遇到 null 时直接跳过,从不检查 Key 每日限额。

Related Work:

变更

  • 核心修复:按 Key -> User 的优先级补齐 Key 每日限额检查(步骤 7),在用户每日额度检查(步骤 8)之前执行,确保 Key 限额始终被执行。
  • 错误消息修复:修复 RateLimitService.checkCostLimits fast-path 对 user 类型误标为'供应商'的问题。
  • 测试补齐:新增限额相关单测(Key/User/Provider + Redis cache miss/DB fallback + rolling/fixed),并新增限额专用覆盖率配置 (vitest.quota.config.ts)。

核心变更

1. src/app/v1/_lib/proxy/rate-limit-guard.ts

  • 在步骤 7 新增 Key 每日限额检查(调用 checkCostLimits),位于用户每日检查之前
  • 更新注释:检查顺序变更为:
    • 5-8. 短期周期限额:Key 5h → User 5h → Key 每日 (NEW) → User 每日
    • 9-12. 中长期周期限额:Key 周 → User 周 → Key 月 → User 月
  • Key 每日限额超限时,抛出 RateLimitError 并中断链路

2. src/lib/rate-limit/service.ts

  • 修复 checkCostLimits 中 user 类型的错误提示从"供应商"改为"User"

3. 测试文件(新增)

  • tests/unit/lib/rate-limit/cost-limits.test.ts (247 行)
  • tests/unit/lib/rate-limit/service-extra.test.ts (396 行)
  • tests/unit/lib/rate-limit/time-utils.test.ts (59 行)
  • tests/unit/proxy/rate-limit-guard.test.ts (409 行)

4. vitest.quota.config.ts(新增)

  • 限额专用测试配置,覆盖率阈值 lines/functions/statements: 80%, branches: 70%
  • 包含范围:src/lib/rate-limit/** 和 rate-limit-guard.ts

影响范围

模块 影响 变更类型
限流检查链路 Key 每日限额现在在 User 每日限额之前检查 修复
错误消息 User 类型错误消息更准确 修复
测试覆盖率 新增 1111 行限额相关单测 增强

Breaking Changes

无破坏性变更

  • API 签名未变化
  • 仅调整了内部检查顺序,外部行为符合预期(Key 限额应始终生效)
  • 现有配置无需迁移

验证

  • ✅ bun run lint
  • ✅ bun run typecheck
  • ✅ bun run test
  • ✅ bun run build
  • ✅ bun run test:coverage
  • ✅ bun run test:coverage:quota

测试场景

场景 1: 用户限额为 null,Key 限额为 10

  • 当前 Key 消费: 20
  • 预期: 拦截并返回 "Key 每日消费上限已达到"
  • 验证: ✅ tests/unit/proxy/rate-limit-guard.test.ts:109-138

场景 2: Key 限额为 null,用户限额为 10

  • 当前用户消费: 20
  • 预期: 拦截并返回 "用户每日消费上限已达到"
  • 验证: ✅ tests/unit/proxy/rate-limit-guard.test.ts:162-188

场景 3: Key 限额超限,用户限额正常

  • 预期: 在检查用户限额之前就拦截(Key 优先)
  • 验证: ✅ tests/unit/proxy/rate-limit-guard.test.ts:140-160

关联

  • 复现:用户取消限额后仅设置 default key 限额时仍放行的问题。

Description enhanced by Claude AI

- 修复:Key 日限额优先于 User 日额度检查\n- 修复:checkCostLimits 对 user 类型的文案标识不一致\n- 测试:补齐限额相关单测,新增 quota 覆盖率配置\n\n验证:\n- bun run lint\n- bun run typecheck\n- bun run test\n- bun run build\n- bun run test:coverage\n- bun run test:coverage:quota
@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

此 PR 增强了速率限制和配额管理功能。核心变化包括重组速率限制检查逻辑、引入新的密钥级日配额检查、添加语义增强以改进错误消息,以及扩展单元测试覆盖和新的 Vitest 配额配置文件。

Changes

Cohort / File(s) 变化摘要
配置与清单
.gitignore, package.json, vitest.quota.config.ts
添加 /coverage-quota 忽略模式;新增 test:coverage:quota npm 脚本(执行 vitest 配额配置和覆盖报告);新增 Vitest 配置文件,配置测试选项、覆盖阈值、模块别名映射
速率限制核心逻辑
src/app/v1/_lib/proxy/rate-limit-guard.ts, src/lib/rate-limit/service.ts
重新排序并扩展日级和周期性速率限制检查;引入密钥级日配额检查(含重置逻辑和本地化错误消息);规范化速率限制违反响应中的类型名称显示文本
单元测试套件
tests/unit/lib/rate-limit/cost-limits.test.ts, tests/unit/lib/rate-limit/service-extra.test.ts, tests/unit/lib/rate-limit/time-utils.test.ts, tests/unit/proxy/rate-limit-guard.test.ts
新增四个单元测试文件,覆盖成本限制、会话跟踪、用户日成本、时间工具函数和速率限制守卫的多个场景;包括固定/滚动模式重置、缓存/数据库回退、错误处理和速率限制错误验证

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR标题清晰准确地描述了主要变更内容:修复当用户限额未设置时Key限额不生效的问题,是本PR的核心改动。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed PR 描述详细阐述了问题背景、根本原因、核心修复内容和测试覆盖,完全对应变更集(Key日限额检查顺序调整、错误消息修复、新增限额单测)。
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/key-quota-without-user-quota

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在解决一个关键的限额管理缺陷,即当用户未设置每日限额时,Key 的每日限额会失效。通过调整限额检查的执行顺序,确保 Key 级别的每日限额始终优先于用户级别进行评估。此外,还改进了限额超出的错误提示信息,使其更加准确。最重要的是,此 PR 大幅增强了限额系统的测试覆盖率,引入了针对各种限额场景(包括不同实体类型、缓存行为和重置模式)的详细单元测试,以提高系统的健壮性和可靠性。

Highlights

  • 修复 Key 每日限额不生效问题: 当用户层面的每日限额被取消(设置为 null)时,Key 的每日限额未能正确生效。此 PR 调整了限额检查的优先级,确保 Key 的每日限额在用户每日限额之前被检查,从而修复了此问题。
  • 统一限额提示信息: 修复了 RateLimitService.checkCostLimits 方法中,当用户类型触发限额时,错误地将其标记为“供应商”的问题,现在提示信息能正确显示为“User”类型。
  • 新增限额相关单测: 为 Key、User 和 Provider 的限额逻辑(包括 Redis 缓存未命中回退到数据库、滚动窗口和固定窗口模式)补充了全面的单元测试,并新增了专门的限额覆盖率配置。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

你好,感谢你的贡献。这次的 Pull Request 修复了一个关键的限额检查漏洞,确保了在用户未设置限额时,Key 的限额能够正确生效。你通过调整检查优先级,补全了 Key 的每日限额检查,这是一个重要的修正。

此外,你还统一了限额提示信息,并增加了非常全面的单元测试,覆盖了多种限额场景、缓存未命中和数据库回退等情况。为限额相关的代码建立独立的测试覆盖率配置也是一个很好的实践,有助于长期维护代码质量。

代码整体质量很高。我只发现了一个可以改进的地方,即在 rate-limit-guard.ts 中存在一些重复的错误处理逻辑,建议可以提取成一个辅助函数来优化。

总体来说,这是一次出色的修复和增强!

Comment on lines +242 to +284
// 7. Key 每日限额(Key 独有的每日预算)- null 表示无限制
const keyDailyCheck = await RateLimitService.checkCostLimits(key.id, "key", {
limit_5h_usd: null,
limit_daily_usd: key.limitDailyUsd,
daily_reset_mode: key.dailyResetMode,
daily_reset_time: key.dailyResetTime,
limit_weekly_usd: null,
limit_monthly_usd: null,
});

if (!keyDailyCheck.allowed) {
logger.warn(`[RateLimit] Key daily limit exceeded: key=${key.id}, ${keyDailyCheck.reason}`);

const { currentUsage, limitValue } = parseLimitInfo(keyDailyCheck.reason!);

const resetInfo = getResetInfoWithMode("daily", key.dailyResetTime, key.dailyResetMode);
// rolling 模式没有 resetAt,使用 24 小时后作为 fallback
const resetTime =
resetInfo.resetAt?.toISOString() ??
new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();

const { getLocale } = await import("next-intl/server");
const locale = await getLocale();
const message = await getErrorMessageServer(
locale,
ERROR_CODES.RATE_LIMIT_DAILY_QUOTA_EXCEEDED,
{
current: currentUsage.toFixed(4),
limit: limitValue.toFixed(4),
resetTime,
}
);

throw new RateLimitError(
"rate_limit_error",
message,
"daily_quota",
currentUsage,
limitValue,
resetTime,
null
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

这部分新增的错误处理逻辑与文件中的其他限额检查(如 key5hCheck, user5hCheck, keyWeeklyCheck 等)存在大量重复代码。重复的代码块包括:记录警告日志、解析限额信息、获取本地化设置、生成错误消息以及抛出 RateLimitError

为了提高代码的可维护性和可读性,建议将这部分重复的逻辑提取到一个私有的辅助函数中。例如,可以创建一个 handleLimitExceeded 辅助函数来统一处理限额超出的情况。

这样做可以显著减少代码量,并使 ensure 方法的逻辑更清晰,专注于限额检查的顺序。当未来需要修改错误处理逻辑时,也只需要修改一个地方。

@github-actions github-actions bot added bug Something isn't working area:Rate Limit area:core size/XL Extra Large PR (> 1000 lines) labels Jan 4, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 4, 2026

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/unit/lib/rate-limit/cost-limits.test.ts (1)

142-246: LGTM!全面覆盖了缓存降级和滚动窗口场景

这些测试用例全面验证了:

  • Redis cache miss 时的数据库降级逻辑(lines 142-160)
  • 总消费限额的各种边界情况(lines 162-202)
  • 用户每日消费在 fixed 和 rolling 模式下的检查(lines 204-246)
  • ZSET 缓存回填机制(lines 229-246)

Line 243-245 验证 pipeline 命令序列的做法很好,确保了 ZSET 正确重建。

可选改进: Line 166 使用 undefined as any 是为了测试边界情况,虽然在测试代码中可接受,但如果类型允许可考虑更精确的类型处理。

src/app/v1/_lib/proxy/rate-limit-guard.ts (1)

263-273: 可选优化:考虑提取重复的 locale 获取逻辑。

文件中多次重复 await import("next-intl/server")getLocale() 调用。可以考虑在函数顶部一次性获取 locale,或提取一个辅助函数来构建本地化错误消息。

不过这是已有模式,不阻塞本次修复。

🔎 可选:提取辅助函数
// 在文件顶部或 ensure 方法开头
async function buildRateLimitMessage(
  errorCode: string,
  params: Record<string, string>
): Promise<string> {
  const { getLocale } = await import("next-intl/server");
  const locale = await getLocale();
  return getErrorMessageServer(locale, errorCode, params);
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to Reviews > Disable Cache setting

📥 Commits

Reviewing files that changed from the base of the PR and between f872f05 and 37e8569.

📒 Files selected for processing (9)
  • .gitignore
  • package.json
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
  • src/lib/rate-limit/service.ts
  • tests/unit/lib/rate-limit/cost-limits.test.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
  • tests/unit/lib/rate-limit/time-utils.test.ts
  • tests/unit/proxy/rate-limit-guard.test.ts
  • vitest.quota.config.ts
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use 2-space indentation in all code files

Files:

  • src/lib/rate-limit/service.ts
  • package.json
  • tests/unit/lib/rate-limit/time-utils.test.ts
  • vitest.quota.config.ts
  • tests/unit/lib/rate-limit/cost-limits.test.ts
  • tests/unit/proxy/rate-limit-guard.test.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use double quotes for strings instead of single quotes
Use trailing commas in multi-line structures
Enforce maximum line length of 100 characters
Use path alias @/* to reference files from ./src/* directory

**/*.{ts,tsx,js,jsx}: Use Biome for linting and formatting with 2-space indent, double quotes, trailing commas, and 100 character max line length
Use path alias @/* to reference files in ./src/* directory

Files:

  • src/lib/rate-limit/service.ts
  • tests/unit/lib/rate-limit/time-utils.test.ts
  • vitest.quota.config.ts
  • tests/unit/lib/rate-limit/cost-limits.test.ts
  • tests/unit/proxy/rate-limit-guard.test.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
src/lib/rate-limit/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Rate Limiting must track dimensions: RPM, cost (5h/week/month), concurrent sessions at User, Key, and Provider levels using Redis Lua scripts

Use Redis with Lua scripts for atomic rate limiting operations

Files:

  • src/lib/rate-limit/service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict mode for type safety
Use readonly or const assertions for immutable data structures

Files:

  • src/lib/rate-limit/service.ts
  • tests/unit/lib/rate-limit/time-utils.test.ts
  • vitest.quota.config.ts
  • tests/unit/lib/rate-limit/cost-limits.test.ts
  • tests/unit/proxy/rate-limit-guard.test.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.ts: Hash API keys using SHA-256 before storing in database, never store plaintext keys
Mask API keys and sensitive data in application logs
Validate required environment variables at startup with clear error messages

Files:

  • src/lib/rate-limit/service.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
src/lib/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use connection pooling for database and Redis connections

Files:

  • src/lib/rate-limit/service.ts
**/*.{tsx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW

Files:

  • package.json
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches

Files:

  • tests/unit/lib/rate-limit/time-utils.test.ts
  • tests/unit/lib/rate-limit/cost-limits.test.ts
  • tests/unit/proxy/rate-limit-guard.test.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

Ensure test database names contain 'test' keyword for safety validation

Files:

  • tests/unit/lib/rate-limit/time-utils.test.ts
  • tests/unit/lib/rate-limit/cost-limits.test.ts
  • tests/unit/proxy/rate-limit-guard.test.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
src/app/v1/_lib/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Guard pipeline must execute in order: ProxyAuthenticator, SensitiveWordGuard, VersionGuard, ProxySessionGuard, ProxyRateLimitGuard, ProxyProviderResolver

Files:

  • src/app/v1/_lib/proxy/rate-limit-guard.ts
src/app/v1/_lib/proxy/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/app/v1/_lib/proxy/**/*.ts: Implement guard pipeline pattern for cross-cutting concerns in request processing (auth, rate limiting, session)
Use undici library for HTTP requests instead of node-fetch for better performance

Files:

  • src/app/v1/_lib/proxy/rate-limit-guard.ts
src/**/*{guard,auth}*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use constant-time comparison for API key validation to prevent timing attacks

Files:

  • src/app/v1/_lib/proxy/rate-limit-guard.ts
src/app/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Implement Content-Security-Policy headers for XSS prevention

Files:

  • src/app/v1/_lib/proxy/rate-limit-guard.ts
src/app/v1/_lib/proxy/**/*guard*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Implement fail-open strategy: allow requests when Redis is unavailable

Files:

  • src/app/v1/_lib/proxy/rate-limit-guard.ts
src/app/v1/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use Hono router for ultrafast, lightweight routing in proxy endpoints

Files:

  • src/app/v1/_lib/proxy/rate-limit-guard.ts
🧠 Learnings (11)
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/lib/rate-limit/**/*.ts : Rate Limiting must track dimensions: RPM, cost (5h/week/month), concurrent sessions at User, Key, and Provider levels using Redis Lua scripts

Applied to files:

  • src/lib/rate-limit/service.ts
  • tests/unit/lib/rate-limit/time-utils.test.ts
  • tests/unit/lib/rate-limit/cost-limits.test.ts
  • tests/unit/proxy/rate-limit-guard.test.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest for unit testing with Node environment, coverage thresholds: 50% lines/functions, 40% branches

Applied to files:

  • package.json
  • tests/unit/lib/rate-limit/time-utils.test.ts
  • vitest.quota.config.ts
  • .gitignore
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.test.ts : Ensure test database names contain 'test' keyword for safety validation

Applied to files:

  • package.json
  • vitest.quota.config.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to tests/integration/**/*.test.{ts,tsx} : Integration tests requiring database must be placed in `tests/integration/` and use test database with 'test' in the name

Applied to files:

  • package.json
  • vitest.quota.config.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to tests/integration/**/*.test.ts : Place integration tests requiring database in `tests/integration/` (excluded by default)

Applied to files:

  • package.json
  • vitest.quota.config.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/lib/rate-limit/**/*.ts : Use Redis with Lua scripts for atomic rate limiting operations

Applied to files:

  • tests/unit/lib/rate-limit/cost-limits.test.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
  • tests/unit/lib/rate-limit/service-extra.test.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/**/*{message,response,log}*.ts : Log request duration, token usage, and cost to message_request table for analytics

Applied to files:

  • tests/unit/lib/rate-limit/cost-limits.test.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*.ts : Implement guard pipeline pattern for cross-cutting concerns in request processing (auth, rate limiting, session)

Applied to files:

  • tests/unit/proxy/rate-limit-guard.test.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
📚 Learning: 2026-01-03T09:08:20.573Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T09:08:20.573Z
Learning: Applies to src/app/v1/_lib/**/*.ts : Guard pipeline must execute in order: ProxyAuthenticator, SensitiveWordGuard, VersionGuard, ProxySessionGuard, ProxyRateLimitGuard, ProxyProviderResolver

Applied to files:

  • tests/unit/proxy/rate-limit-guard.test.ts
  • src/app/v1/_lib/proxy/rate-limit-guard.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to src/app/v1/_lib/proxy/**/*guard*.ts : Implement fail-open strategy: allow requests when Redis is unavailable

Applied to files:

  • tests/unit/proxy/rate-limit-guard.test.ts
📚 Learning: 2026-01-03T09:08:49.019Z
Learnt from: CR
Repo: ding113/claude-code-hub PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-03T09:08:49.019Z
Learning: Applies to **/*.{tsx,json} : Use next-intl for internationalization with 5 locales: en, ja, ru, zh-CN, zh-TW

Applied to files:

  • .gitignore
🧬 Code graph analysis (3)
tests/unit/lib/rate-limit/time-utils.test.ts (1)
src/lib/rate-limit/time-utils.ts (7)
  • normalizeResetTime (286-289)
  • getTimeRangeForPeriodWithMode (88-104)
  • getResetInfoWithMode (219-232)
  • getTTLForPeriodWithMode (155-165)
  • getTTLForPeriod (113-147)
  • getSecondsUntilMidnight (295-314)
  • getDailyResetTime (319-337)
vitest.quota.config.ts (2)
scripts/sync-settings-keys.js (1)
  • path (9-9)
scripts/validate-migrations.js (1)
  • __dirname (18-18)
tests/unit/proxy/rate-limit-guard.test.ts (1)
src/app/v1/_lib/proxy/rate-limit-guard.ts (1)
  • ProxyRateLimitGuard (20-473)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Greptile Review
  • GitHub Check: pr-description
  • GitHub Check: pr-label
  • GitHub Check: 🌐 API Tests
  • GitHub Check: pr-review
  • GitHub Check: Docker Build Test
  • GitHub Check: check-codex-status
  • GitHub Check: dev-build-deploy
🔇 Additional comments (21)
.gitignore (1)

15-15: LGTM!新增的配额覆盖率输出目录忽略规则

正确添加了 /coverage-quota 忽略规则,与新增的配额测试配置(vitest.quota.config.tstest:coverage:quota 脚本)保持一致。

src/lib/rate-limit/service.ts (1)

251-254: LGTM!修复了用户限额类型标识错误的问题

此更改将类型名称逻辑提取为独立变量 typeName,确保在 fast-path 中正确标识 User 类型(避免错误标注为"供应商")。这正是 PR 描述中提到的核心修复:统一限额提示信息。

代码逻辑清晰,与数据库降级路径(line 466)保持一致。

package.json (1)

21-21: LGTM!新增配额测试覆盖率脚本

新增的 test:coverage:quota 脚本正确配置了配额专用的测试覆盖率运行,使用独立的 vitest.quota.config.ts 配置文件,符合 PR 目标中提到的"新增限额专用覆盖率配置"。

tests/unit/lib/rate-limit/cost-limits.test.ts (2)

1-67: LGTM!测试基础设施设置完善

测试 mock 设置非常全面:

  • Pipeline mock(lines 3-21)能够追踪命令执行,便于验证 ZSET 操作
  • Redis client mock(lines 23-31)提供完整的方法集
  • Statistics mock(lines 37-51)覆盖固定窗口和滚动窗口查询
  • 使用 fake timers 确保时间相关测试的确定性

这为后续的限额检查测试提供了坚实的基础。


123-140: LGTM!关键测试验证了 User 类型标识修复

此测试用例(lines 123-140)直接验证了 PR 的核心修复:确保 User 类型在 fast-path 中正确显示为 "User" 而非错误的"供应商"。line 139 的断言明确检查了错误消息中的类型名称。

这与 src/lib/rate-limit/service.ts 的 line 251 修复相呼应。

tests/unit/lib/rate-limit/time-utils.test.ts (1)

1-59: LGTM!时间工具函数测试覆盖全面

此测试文件完整验证了 time-utils 模块的关键功能:

  • normalizeResetTime 的容错处理(lines 24-28)
  • 滚动窗口的时间范围计算(lines 30-35)
  • 不同模式下的 TTL 计算(lines 43-49)
  • 每日重置时间的计算逻辑(lines 51-58)

使用 fake timers(lines 15-22)确保了测试的确定性和可重复性,这是测试时间相关代码的最佳实践。

根据学习记录:符合 Vitest 单元测试规范,使用 Node 环境运行。

vitest.quota.config.ts (1)

1-45: 配置结构良好,覆盖率阈值设置合理。

配额测试配置文件设置正确:

  • 路径别名 @server-only 配置正确
  • 测试环境使用 node,符合编码规范
  • 覆盖率阈值(80% lines/functions/statements,70% branches)高于默认阈值,适合关键限流代码路径
  • isolatemockResetrestoreMocksclearMocks 配置确保测试隔离性
src/app/v1/_lib/proxy/rate-limit-guard.ts (3)

27-28: 注释更新正确反映了新的检查顺序。

检查顺序更新为 5-8(短期)和 9-12(中长期),正确体现了新增的 Key 每日限额检查。


242-284: Key 每日限额检查实现正确,这是本 PR 的核心修复。

新增的 Key 每日限额检查逻辑:

  • 正确传递 daily_reset_modedaily_reset_time 配置
  • rolling 模式下使用 24 小时作为 resetTime 的 fallback 是合理的
  • 错误类型 daily_quota 与 User 每日检查保持一致

此修复确保当 user.dailyQuota = null 时,key.limitDailyUsd 仍能生效。


286-327: User 每日额度检查逻辑保持不变,仅调整了执行顺序。

User 每日检查现在作为第 8 步执行(在 Key 每日检查之后),保留了原有的 user.dailyQuota !== null 判断,确保用户未设置限额时跳过此检查。

tests/unit/lib/rate-limit/service-extra.test.ts (6)

1-42: Mock 设置结构良好,pipeline 模拟完整。

makePipeline 工厂函数正确模拟了 Redis pipeline 的链式调用模式,包括 evalgetincrbyfloatexpirezremrangebyscorezcardzaddexec 方法。pipelineCalls 数组用于追踪调用记录,便于断言验证。


82-104: 测试设置完善,时间控制和 Mock 重置正确。

  • 使用 vi.useFakeTimers()vi.setSystemTime() 确保时间相关测试的确定性
  • beforeEach 中重置所有 Mock 和 pipelineCalls 数组
  • afterEach 恢复真实计时器,避免影响其他测试

106-165: Session 限制测试覆盖全面。

测试用例覆盖了:

  • limit <= 0 时放行
  • Key 并发达到上限时拦截
  • Provider 并发未达上限时放行
  • Redis 非 ready 时 Fail Open 策略

符合 src/app/v1/_lib/proxy/**/*guard*.ts 的 Fail Open 设计原则。基于 learnings 中的编码规范。


166-181: Fixed 和 Rolling 模式的成本追踪测试正确区分了不同的 Redis 数据结构。

  • Fixed 模式验证使用 STRING + TTLincrbyfloat + expire
  • Rolling 模式验证使用 ZSET Lua 脚本eval

这与 learnings 中"Use Redis with Lua scripts for atomic rate limiting operations"的要求一致。


279-301: Daily rolling cache miss 场景测试验证了 DB 回退和 ZSET 预热逻辑。

测试确保当 Redis 缓存不存在时(exists 返回 0),系统能够:

  1. 从 DB 查询历史成本记录
  2. 使用 zadd 预热 ZSET 缓存
  3. 正确计算并返回限额超限状态

380-395: 错误处理测试验证了单个查询失败时的跳过逻辑。

当 pipeline 返回中某个查询出错时,该项保持默认值 0,其余项正常解析。这确保了部分 Redis 错误不会导致整个批量查询失败。

tests/unit/proxy/rate-limit-guard.test.ts (5)

1-42: Mock 设置完整,覆盖所有依赖模块。

正确模拟了:

  • @/lib/rate-limit 中的 RateLimitService 各方法
  • @/lib/logger
  • next-intl/servergetLocale
  • @/lib/utils/error-messagesERROR_CODESgetErrorMessageServer

43-98: createSession 工厂函数设计合理,便于测试数据复用。

默认值设置为"无限制"状态(rpm: null, dailyQuota: null 等),允许测试用例按需覆盖特定字段。这符合测试的最小化原则。


110-140: 核心测试用例:验证 PR 的主要修复。

此测试确保当 user.dailyQuota = null 时,key.limitDailyUsd 仍能正确拦截请求:

  • 验证 checkUserDailyCost 未被调用(因为 Key 检查先失败)
  • 验证传递给 checkCostLimits 的参数包含正确的 daily_reset_modedaily_reset_time

这直接验证了 PR 描述中的问题场景。


142-191: Key 优先级和 User 回退测试完善。

  • 测试 2 验证 Key 每日限额优先于 User 每日限额检查
  • 测试 3 验证当 Key 未设置每日限额时,User 每日限额仍能正常拦截

这确保了修复不会破坏原有的 User 每日限额功能。


193-409: 完整覆盖所有 12 个限额检查场景。

测试用例覆盖了:

  1. Key/User 总限额 (usd_total)
  2. Key 并发 Session (concurrent_sessions)
  3. User RPM (rpm)
  4. Key/User 5h (usd_5h)
  5. Key/User 每日 (daily_quota)
  6. Key/User 周 (usd_weekly)
  7. Key/User 月 (usd_monthly)
  8. 所有限额通过时放行

使用 mockResolvedValueOnce 链式调用正确模拟了检查顺序。

@ding113 ding113 merged commit b7d5bcd into dev Jan 4, 2026
18 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 4, 2026
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

No significant issues identified in this PR.

PR Size: XL

  • Lines changed: 1219
  • Files changed: 9
  • Split suggestion: Consider splitting into (1) rate-limit behavior fixes (src/app/v1/_lib/proxy/rate-limit-guard.ts, src/lib/rate-limit/service.ts) and (2) test additions + coverage config (tests/unit/**, vitest.quota.config.ts).

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Automated review by Codex AI

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

This PR successfully fixes a critical bug where Key daily limits were not enforced when User daily quotas were set to null. The implementation is clean, well-tested, and follows existing patterns.

PR Size: XL

  • Lines changed: 1,219 (1,211 additions, 8 deletions)
  • Files changed: 9

Split Recommendation: This PR is appropriately sized. While it's XL due to extensive test coverage (1,111 test lines), the core logic change is minimal (~44 lines). The tests are essential for ensuring the fix works correctly and should remain in this PR.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Excellent (1,111 new test lines with dedicated coverage config)
  • Code clarity - Good

Key Strengths

  1. Minimal, Focused Change: The core fix adds exactly one daily limit check for Keys in the correct order (step 7, before User daily check at step 8).

  2. Pattern Consistency: The new code duplicates the existing User daily quota check pattern (lines 242-283), ensuring maintainability.

  3. Comprehensive Test Coverage:

    • 1,111 lines of unit tests
    • Dedicated vitest.quota.config.ts with 80% coverage threshold
    • Tests explicitly cover the bug scenario (Key limit active, User limit null)
    • Tests cover both cache hit/miss and rolling/fixed modes
  4. Error Message Fix: Correctly distinguishes User from Provider in error messages (service.ts:465).

  5. Documentation Updated: Comment block correctly reflects new check order (5-8 instead of 5-7).


Automated review by Claude AI

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed PR #531 and pulled the full diff + changed file list.

  • Applied the size/XL label.
  • Submitted a PR review summary comment (no high-confidence issues found in the diff lines).
  • No inline review comments were posted because nothing met the “must-fix” bar within the changed lines.

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

Labels

area:core area:Rate Limit bug Something isn't working size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant