Skip to content

test: add comprehensive tests for request filter binding types#483

Closed
miraserver wants to merge 4 commits intoding113:devfrom
miraserver:feat/req-filters-adv
Closed

test: add comprehensive tests for request filter binding types#483
miraserver wants to merge 4 commits intoding113:devfrom
miraserver:feat/req-filters-adv

Conversation

@miraserver
Copy link
Contributor

@miraserver miraserver commented Dec 29, 2025

Summary

Enhances the Request Filter system with provider/group binding capability, allowing filters to be applied to specific providers or provider groups instead of only globally. Also adds comprehensive test coverage (28 tests) for the binding functionality.

Problem

Previously, request filters could only be applied globally to all providers. This made it impossible to:

  • Apply different header modifications to specific providers
  • Create targeted filters for provider groups (e.g., "set custom headers only for OpenAI-compatible providers")
  • Fine-tune request filtering at a per-provider level

Related to #459 - Provider grouping and batch operations feature request

Solution

Added a three-tier binding system for request filters:

  1. Global (`binding_type='global'`) - Apply to all providers (existing behavior, now explicit)
  2. Providers (`binding_type='providers'`) - Apply to specific provider IDs
  3. Groups (`binding_type='groups'`) - Apply to providers with specific `group_tag` values

Database Schema Changes

Migration: `drizzle/0041_sticky_jackal.sql`
```sql
ALTER TABLE "request_filters" ADD COLUMN "binding_type" varchar(20) DEFAULT 'global' NOT NULL;
ALTER TABLE "request_filters" ADD COLUMN "provider_ids" jsonb;
ALTER TABLE "request_filters" ADD COLUMN "group_tags" jsonb;
CREATE INDEX "idx_request_filters_binding" ON "request_filters" ("is_enabled", "binding_type");
```

Schema: `src/drizzle/schema.ts`

  • Added `bindingType` column: `'global' | 'providers' | 'groups'`
  • Added `providerIds` column: `number[] | null` for specific provider selection
  • Added `groupTags` column: `string[] | null` for group tag selection
  • Added composite index on `(is_enabled, binding_type)` for efficient filtering

Core Engine Changes

`src/lib/request-filter-engine.ts`

  • Added `RequestFilterBindingType` type definition
  • Modified `reload()` to fetch all filter types (global/provider/group)
  • Implemented `applyProvider()` method for provider-specific filtering
  • Implemented `applyGroup()` method for group-based filtering
  • Updated `apply()` to merge results from all three filter types
  • Filters are sorted by priority within each type before application
  • Fixed `setFiltersForTest()` to sort filters by priority (was missing this)

`src/app/v1/_lib/proxy/provider-request-filter.ts` (NEW)

  • Created dedicated guard for provider-scoped filter application
  • Integrated into guard pipeline after provider resolution

`src/app/v1/_lib/proxy/guard-pipeline.ts`

  • Added `ProviderRequestFilter` guard step after `ProxyProviderResolver`
  • Ensures provider is known before applying provider/group filters

`src/app/v1/_lib/proxy/request-filter.ts`

  • Updated to apply global filters only (provider-specific filters moved)

UI Components

Filter Dialog (`src/app/[locale]/settings/request-filters/_components/filter-dialog.tsx`)

  • Added "Apply To" (binding_type) radio button selection
  • Added multi-select for providers when `binding_type='providers'`
  • Added multi-select for groups when `binding_type='groups'`
  • Auto-clears selections when switching binding types
  • Validation: requires at least one provider/group selection for respective types

New Components:

  • `provider-multi-select.tsx` - Multi-select dropdown for providers
  • `group-multi-select.tsx` - Multi-select dropdown for provider groups

Filter Table (`src/app/[locale]/settings/request-filters/_components/filter-table.tsx`)

  • Added "Apply To" column display with badges
  • Added provider/group name display
  • Improved with truncation and tooltips for long content

Server Actions

`src/actions/request-filters.ts`

  • Added `getProviderOptions()` - Fetches all enabled providers for filter binding
  • Added `getGroupTagOptions()` - Fetches distinct provider group tags
  • Updated `createRequestFilter()` validation for binding type constraints
  • Updated `updateRequestFilter()` validation for binding type changes

`src/repository/request-filters.ts`

  • Added support for querying filters with binding metadata

Internationalization

Added translations to all 5 locales (`messages/{locale}/settings.json`):

  • `bindingType` - "Apply To" / "適用先" / "Применить к" / "应用范围" / "套用範圍"
  • `bindingGlobal` - "All Providers (Global)"
  • `bindingProviders` - "Specific Providers"
  • `bindingGroups` - "Provider Groups"

Test Coverage

New Test File: `tests/unit/request-filter-binding.test.ts` (645 lines, 28 tests)

Category Tests Coverage
Global filters (applyGlobal) 7 ✅ Sorting, priority, disabled, empty
Provider-specific filters 6 ✅ Matching/non-matching provider IDs
Group-specific filters 7 ✅ Matching/non-matching group tags
Combined filters 4 ✅ Global + provider + group interaction
Edge cases 4 ✅ Null arrays, empty arrays, missing fields
Total 28 ✅ All branches covered

Test scenarios:

  • Priority sorting across filter types
  • Provider ID matching logic
  • Group tag matching logic
  • Filter combination precedence (global → provider → group)
  • Null/empty array handling
  • Missing optional field handling

Changes

Core Changes

  • Database schema: Added `binding_type`, `provider_ids`, `group_tags` columns
  • Engine: Implemented provider/group filter application logic
  • Pipeline: Added provider-scoped filter guard step

Supporting Changes

  • UI: Filter dialog with binding type selection and multi-selects
  • UI: Filter table with binding type display
  • Server Actions: Provider/group option fetchers, validation updates
  • i18n: 5 locales updated with binding type labels
  • Tests: 28 comprehensive unit tests for binding logic

Breaking Changes

Database Migration Required

Change Impact Migration
New columns: `binding_type`, `provider_ids`, `group_tags` Existing filters automatically get `binding_type='global'` Run `bun run db:migrate` to apply migration `0041_sticky_jackal.sql`
Schema type changes TypeScript types updated automatically No code changes required for existing consumers

Backward Compatibility:

  • ✅ Existing filters default to `binding_type='global'` (same behavior as before)
  • ✅ No API changes to request filter application endpoints
  • ✅ UI defaults to global type when creating new filters

Testing

Automated Tests

  • 28 new unit tests added (all passing)
  • Existing tests still pass (224 total)
  • Typecheck passes

Manual Testing

  1. Global Filter (Existing Behavior)

    • Create filter with "Apply To: All Providers (Global)"
    • Send request to any provider
    • Verify filter is applied
  2. Provider-Specific Filter

    • Create filter with "Apply To: Specific Providers"
    • Select providers: "provider-1", "provider-2"
    • Send requests to provider-1, provider-2, provider-3
    • Verify filter applies only to provider-1 and provider-2
  3. Group-Specific Filter

    • Ensure providers have `group_tag` set (e.g., "openai-compatible", "claude-native")
    • Create filter with "Apply To: Provider Groups"
    • Select groups: "openai-compatible"
    • Send requests to providers in different groups
    • Verify filter applies only to "openai-compatible" group
  4. Combined Filters

    • Create global filter: SET header `X-Global = true`
    • Create provider filter: SET header `X-Provider = true`
    • Create group filter: SET header `X-Group = true`
    • Send request matching provider and group
    • Verify all three headers are set
  5. Priority Testing

    • Create multiple filters with different priorities
    • Verify higher priority filters are applied first

Checklist

  • Code follows project conventions (Biome 2-space, double quotes)
  • Self-review completed
  • Tests pass locally (28 new + 224 existing = 252 total)
  • Documentation updated (i18n for all 5 locales)
  • Database migration included
  • Breaking changes documented

🤖 Generated with Claude Code

John Doe and others added 4 commits December 29, 2025 20:53
Allow Request Filters to be applied globally, to specific providers, or to provider groups.

## Key Changes

### Database Schema
- Add `bindingType` field ('global' | 'providers' | 'groups')
- Add `providerIds` field (array of provider IDs)
- Add `groupTags` field (array of group tags)
- Migration: 0041_sticky_jackal.sql

### Backend Logic
- Split filter engine into global and provider-specific filters
- `applyGlobal()`: runs BEFORE provider selection
- `applyForProvider()`: runs AFTER provider selection
- Provider matching: checks provider IDs or group tags

### Guard Pipeline
- Add new `providerRequestFilter` guard step
- CHAT_PIPELINE order: [..., provider, providerRequestFilter, messageContext]
- COUNT_TOKENS_PIPELINE includes provider filters

### UI Components
- New: `ProviderMultiSelect` - multi-select for providers
- New: `GroupMultiSelect` - multi-select for group tags
- Updated: `FilterDialog` - binding type selector moved after Name field
- Updated: `FilterTable` - compact layout with icon-only display
- Binding display: Global (icon only), Providers (icon + count), Groups (icon + count)

### Localization
- Add translations for 5 languages (en, ru, ja, zh-CN, zh-TW)
- Column name changed from "Apply To" to "Apply"

### Bug Fixes
- Fix binding type change validation (use explicit undefined check instead of ??)
- Prevent stale binding data when switching types

### Other Improvements
- Compact last 4 columns in filter table (Priority, Apply, Status, Actions)
- Improved UX: binding configuration at top of dialog

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add truncate to name, target, and replacement columns to keep table rows single-line
- Add tooltips on Apply column showing provider names or group tags on hover
- Fetch provider list to display names instead of IDs in tooltips
- Optimize provider lookup with useMemo Map (O(1) access)
- Add i18n translations for tooltip labels (en, ja, ru, zh-CN, zh-TW)
- Optimize request filter engine with caching and early exits

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Set max-w-2xl width for consistency with error rules dialog
- Add max-h-80vh height limit to prevent overflow
- Restructure layout with fixed header and footer
- Make only content area scrollable when form has many fields
- Ensures Cancel/Save buttons and close button always accessible

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add 28 unit tests for RequestFilterEngine binding functionality
- Cover global, provider-specific, and group-specific filters
- Test combined filters and edge cases
- Fix setFiltersForTest to sort by priority (matching reload behavior)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @miraserver, 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!

This pull request introduces a major enhancement to the request filtering system by implementing different binding types for filters. Filters can now be configured to apply globally, to specific providers, or to custom provider groups. This change provides much finer control over request modification and routing. The update includes necessary database schema migrations, a refactored backend engine with performance improvements, new proxy pipeline integration, and a user-friendly interface for managing these advanced filter settings, all backed by a comprehensive suite of new unit tests.

Highlights

  • Enhanced Request Filter Binding: Introduced new binding types for request filters, allowing them to be applied globally, to specific providers, or to provider groups, significantly increasing filtering granularity.
  • Comprehensive Unit Tests: Added 28 new unit tests for the RequestFilterEngine covering global, provider-specific, group-specific, combined, and edge-case filter binding scenarios to ensure robustness.
  • Database Schema Update: The request_filters table now includes binding_type, provider_ids, and group_tags columns, along with a new index, to support the new filter binding functionality.
  • Refactored Filter Application Logic: The RequestFilterEngine has been refactored to separate global and provider-bound filter application, including performance optimizations like pre-compiled regex and Set caches for faster lookups.
  • New Proxy Guard Step: A new ProxyProviderRequestFilter guard step has been added to the proxy pipeline, ensuring provider-specific filters are applied after a provider has been selected.
  • User Interface Enhancements: New UI components (GroupMultiSelect, ProviderMultiSelect) and localization keys have been added to the request filters settings page, enabling users to easily configure the new binding types.
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.

@github-actions github-actions bot added size/L Large PR (< 1000 lines) enhancement New feature or request area:core area:UI area:i18n labels Dec 29, 2025
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

This is an excellent pull request that significantly enhances the request filtering functionality. The introduction of provider and group-based filter bindings is a great feature, and the implementation is solid across the stack, from the database schema and backend logic to the new UI components.

I'm particularly impressed with the performance optimizations in RequestFilterEngine, such as pre-compiling regexes and using Sets for faster lookups. The comprehensive suite of 28 new unit tests is also a fantastic addition that ensures the new logic is robust and reliable.

I have a couple of minor suggestions for code simplification and micro-optimization, but overall, this is a very high-quality contribution. Great work!

Comment on lines +294 to +299
if (row.groupTag) {
const tags = row.groupTag.split(",").map((tag) => tag.trim());
for (const tag of tags) {
if (tag) allTags.add(tag);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For better readability and conciseness, you can use a more functional approach with method chaining to parse and add tags to the Set. This avoids nested loops and if conditions.

      row.groupTag
        ?.split(",")
        .map((tag) => tag.trim())
        .filter(Boolean)
        .forEach((tag) => allTags.add(tag));

Comment on lines +286 to +291
} else if (filter.bindingType === "groups" && providerTagsSet) {
// Optimization #3: O(m) instead of O(m*n)
matches = filter.groupTagsSet
? Array.from(filter.groupTagsSet).some((tag) => providerTagsSet!.has(tag))
: false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To slightly improve performance and readability, you can iterate directly over filter.groupTagsSet to check for intersection with providerTagsSet. This avoids creating an intermediate array with Array.from() and allows for an early exit using break once a match is found.

Suggested change
} else if (filter.bindingType === "groups" && providerTagsSet) {
// Optimization #3: O(m) instead of O(m*n)
matches = filter.groupTagsSet
? Array.from(filter.groupTagsSet).some((tag) => providerTagsSet!.has(tag))
: false;
}
} else if (filter.bindingType === "groups" && providerTagsSet && filter.groupTagsSet) {
for (const tag of filter.groupTagsSet) {
if (providerTagsSet.has(tag)) {
matches = true;
break;
}
}
}

@greptile-apps
Copy link

greptile-apps bot commented Dec 29, 2025

Greptile found no issues!

From now on, if a review finishes and we haven't found any issues, we will not post anything, but you can confirm that we reviewed your changes in the status check section.

This feature can be toggled off in your Code Review Settings by deselecting "Create a status check for each PR".

@miraserver miraserver closed this Dec 29, 2025
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Dec 29, 2025
// Optimization #1: Memory leak cleanup
private eventEmitterCleanup: (() => void) | null = null;

// Optimization #5: Skip tag parsing when no group filters
Copy link
Contributor

Choose a reason for hiding this comment

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

[Critical] [ERROR-SILENT] Memory leak: destroy() method is never called

Why this is a problem: The PR adds a destroy() method to fix event listener memory leaks (Optimization #1), but this method is never called anywhere in the codebase. The event listener cleanup function is stored but never executed, meaning the memory leak is NOT fixed - it's just made possible to fix.

The singleton requestFilterEngine is created at module level and never destroyed. In a long-running Node.js process with hot reloads or module re-imports, the old event listeners will accumulate without being cleaned up.

Suggested fix:

// Call destroy() before setting up new listener in setupEventListener
private async setupEventListener(): Promise<void> {
  if (typeof process !== "undefined" && process.env.NEXT_RUNTIME !== "edge") {
    try {
      // Clean up existing listener before adding new one
      if (this.eventEmitterCleanup) {
        this.eventEmitterCleanup();
      }
      
      const { eventEmitter } = await import("@/lib/event-emitter");
      const handler = () => { void this.reload(); };
      eventEmitter.on("requestFiltersUpdated", handler);
      
      this.eventEmitterCleanup = () => {
        eventEmitter.off("requestFiltersUpdated", handler);
      };
    } catch { /* ignore */ }
  }
}

If destroy() is intended for future use, add a TODO comment documenting this.

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 adds provider-specific and group-specific request filter bindings, along with comprehensive tests. The implementation is well-tested and documented, but has one critical issue that needs addressing.

PR Size: L

  • Lines changed: +3669/-69 (net +3600)
  • Files changed: 21

Note: This is an L-size PR. Consider splitting the database schema changes, engine refactor, and UI changes into separate PRs to improve reviewability and reduce merge risk.

Issues Found

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

Critical Issues (Must Fix)

1. Memory leak: destroy() method is never called - src/lib/request-filter-engine.ts:125

The PR adds a destroy() method to fix event listener memory leaks (Optimization #1), but this method is never called anywhere in the codebase. The singleton requestFilterEngine is created at module level and never destroyed. In a long-running Node.js process with hot reloads or module re-imports, old event listeners will accumulate.

Fix: Either call destroy() in setupEventListener() before adding a new listener, or add a TODO comment documenting that this is for future graceful shutdown.

High Priority Issues (Should Fix)

None identified.

What Was Done Well

  • Comprehensive test coverage: 28 new unit tests covering all binding types and edge cases
  • Performance optimizations: Pre-compiled regex, Set-based lookups, early exit patterns
  • Proper validation: Binding type constraints validated in actions layer
  • Good documentation: Russian comments explaining the purpose of each method
  • Type safety: Proper use of TypeScript discriminated unions for binding types
  • Database indexing: Added composite index for efficient binding type queries

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - 1 issue found
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Excellent (28 new tests)
  • Code clarity - Good

Automated review by Claude AI

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

Labels

area:core area:i18n area:UI enhancement New feature or request size/L Large PR (< 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant

Comments