Skip to content

test: expand unit test coverage across utils, APIs, and services#934

Open
PeterDaveHello wants to merge 1 commit intoChatGPTBox-dev:masterfrom
PeterDaveHello:test/coverage-expansion
Open

test: expand unit test coverage across utils, APIs, and services#934
PeterDaveHello wants to merge 1 commit intoChatGPTBox-dev:masterfrom
PeterDaveHello:test/coverage-expansion

Conversation

@PeterDaveHello
Copy link
Member

@PeterDaveHello PeterDaveHello commented Feb 28, 2026

User description

Add 172 tests (110 → 282) covering 14 new/extended test files.

Utilities & config:

  • model-name-convert: extend to 100% (modelNameToDesc, getModelValue, isUsingModelName)
  • eventsource-parser: extend to 100% (CR/LF, BOM, comments, partial buffers)
  • crop-text: new tests for cropping, tiktoken paths, clamp behavior
  • jwt-token-generator: new tests for generation, caching, expiry
  • config predicates: complete isUsing* predicates + getPreferredLanguageKey

Content script:

  • selection-tools: new tests for all 11 tools' genPrompt functions

Services & sessions:

  • local-session: new CRUD tests (create, switch, delete, reset)
  • init-session: new session shape validation tests
  • wrappers/registerPortListener: new tests for port message flow, token getters

API adapters:

  • custom-api: new tests for all response schemas, [DONE], finish_reason, errors
  • azure-openai-api: new tests for URL composition, deployment fallback, headers
  • claude-api: new tests for streaming, message_stop, header construction
  • thin adapters (aiml, deepseek, moonshot, openrouter, chatglm): delegation + key forwarding

Background:

  • Extract redactSensitiveFields to src/background/redact.mjs for testability
  • New tests for sensitive key redaction, prompt/selection integration, depth limits

Bug fixes found during testing:

  • custom-api.mjs: add optional chaining on data.choices (lines 79-81, 92) to prevent TypeError when APIs return {response} format without choices field (e.g. Ollama)

PR Type

Tests, Bug fix


Description

  • Add 172 tests (110 → 282) across 14 new/extended test files

    • Custom API: 9 tests for response schemas, finish_reason, error handling
    • Azure OpenAI & Claude APIs: 8 tests each for URL composition, headers, streaming
    • Thin adapters (AIML, DeepSeek, Moonshot, OpenRouter, ChatGLM): delegation tests
    • Utilities: model-name-convert (100%), eventsource-parser (100%), crop-text, jwt-token-generator
    • Services: local-session CRUD, init-session validation, wrappers/registerPortListener
    • Content script: selection-tools genPrompt for all 11 tools
    • Background: redactSensitiveFields extraction with circular reference handling
  • Fix optional chaining on data.choices in custom-api.mjs

    • Prevents TypeError when APIs return {response} format without choices field (e.g. Ollama)
  • Extract redactSensitiveFields to src/background/redact.mjs for testability

    • Handles nested objects, arrays, circular references, depth limits

Diagram Walkthrough

flowchart LR
  Tests["14 Test Files<br/>172 New Tests"]
  Utils["Utils: model-name,<br/>eventsource, crop-text,<br/>jwt-token-generator"]
  Services["Services: local-session,<br/>init-session, wrappers"]
  APIs["APIs: custom, azure,<br/>claude, thin adapters"]
  Content["Content: selection-tools<br/>11 tools genPrompt"]
  Background["Background: redact<br/>sensitive fields"]
  BugFix["Bug Fix: optional<br/>chaining on choices"]
  
  Tests --> Utils
  Tests --> Services
  Tests --> APIs
  Tests --> Content
  Tests --> Background
  Background --> BugFix
Loading

File Walkthrough

Relevant files
Tests
15 files
custom-api.test.mjs
Comprehensive custom API response schema tests                     
+556/-0 
wrappers-register.test.mjs
Port listener and token getter integration tests                 
+399/-0 
azure-openai-api.test.mjs
Azure OpenAI URL composition and header tests                       
+261/-0 
claude-api.test.mjs
Claude API streaming and message_stop tests                           
+254/-0 
crop-text.test.mjs
Text cropping with tiktoken and clamp behavior                     
+195/-0 
selection-tools.test.mjs
Selection tools genPrompt for all 11 tools                             
+188/-0 
model-name-convert.test.mjs
Extended model name conversion to 100% coverage                   
+160/-0 
config-predicates.test.mjs
Complete isUsing* predicates for all providers                     
+113/-1 
local-session.test.mjs
Session CRUD operations and persistence tests                       
+139/-0 
thin-adapters.test.mjs
Thin adapter delegation and key forwarding tests                 
+132/-0 
redact.test.mjs
Sensitive field redaction and circular reference handling
+110/-0 
eventsource-parser.test.mjs
Extended SSE parser coverage for edge cases                           
+112/-0 
init-session.test.mjs
Session initialization and shape validation tests               
+111/-0 
jwt-token-generator.test.mjs
JWT generation, caching, and expiry tests                               
+86/-0   
jsx-loader-hooks.mjs
ESM loader hooks for JSX and CJS interop                                 
+59/-0   
Enhancement
2 files
redact.mjs
Extract redactSensitiveFields for testability                       
+73/-0   
index.mjs
Import redactSensitiveFields from redact module                   
+1/-74   
Bug fix
1 files
custom-api.mjs
Add optional chaining on data.choices access                         
+4/-4     

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed error handling for custom API responses with missing or incomplete data fields, preventing crashes when response structure is unexpected.
  • Quality & Stability

    • Improved sensitive data protection with enhanced redaction mechanisms.
    • Expanded test coverage across API integrations and core utilities to ensure reliability.

Add 172 tests (110 → 282) covering 14 new/extended test files.

Utilities & config:
- model-name-convert: extend to 100% (modelNameToDesc, getModelValue, isUsingModelName)
- eventsource-parser: extend to 100% (CR/LF, BOM, comments, partial buffers)
- crop-text: new tests for cropping, tiktoken paths, clamp behavior
- jwt-token-generator: new tests for generation, caching, expiry
- config predicates: complete isUsing* predicates + getPreferredLanguageKey

Content script:
- selection-tools: new tests for all 11 tools' genPrompt functions

Services & sessions:
- local-session: new CRUD tests (create, switch, delete, reset)
- init-session: new session shape validation tests
- wrappers/registerPortListener: new tests for port message flow, token getters

API adapters:
- custom-api: new tests for all response schemas, [DONE], finish_reason, errors
- azure-openai-api: new tests for URL composition, deployment fallback, headers
- claude-api: new tests for streaming, message_stop, header construction
- thin adapters (aiml, deepseek, moonshot, openrouter, chatglm): delegation + key forwarding

Background:
- Extract redactSensitiveFields to src/background/redact.mjs for testability
- New tests for sensitive key redaction, prompt/selection integration, depth limits

Bug fixes found during testing:
- custom-api.mjs: add optional chaining on data.choices (lines 79-81, 92) to prevent
  TypeError when APIs return {response} format without choices field (e.g. Ollama)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

📝 Walkthrough

Walkthrough

This PR refactors redaction logic into a dedicated module, adds optional chaining safety guards to API response parsing, introduces a new ESM loader hook for JSX and CommonJS interoperability in tests, and adds comprehensive unit test coverage across the codebase.

Changes

Cohort / File(s) Summary
Redaction Module Extraction
src/background/index.mjs, src/background/redact.mjs
Extracted redaction utilities (SENSITIVE_KEYWORDS, isPromptOrSelectionLikeKey, redactSensitiveFields) from index.mjs into a new dedicated redact.mjs module. Existing calls now import from the new module without control flow changes.
API Safety Guards
src/services/apis/custom-api.mjs
Added optional chaining operators to safely access data.choices[0] fields (delta.content, message.content, text, finish_reason), preventing errors when choices is undefined or empty.
Test Infrastructure
tests/setup/jsx-loader-hooks.mjs
New ESM loader hook providing CJS-to-ESM interop for select packages (e.g., countries-list) and JSX transformation for .mjs test files using esbuild with preact as JSX runtime.
Redaction Tests
tests/unit/background/redact.test.mjs
Comprehensive unit tests for redactSensitiveFields and isPromptOrSelectionLikeKey covering sensitive keyword detection, nested objects, arrays, depth limits, circular references, and edge cases.
Config & Selection Tests
tests/unit/config/config-predicates.test.mjs, tests/unit/content-script/selection-tools.test.mjs
Extended predicate tests for model providers and language resolution; new selection-tools test suite verifying genPrompt configurations across multiple scenarios.
API Integration Tests
tests/unit/services/apis/azure-openai-api.test.mjs, tests/unit/services/apis/claude-api.test.mjs, tests/unit/services/apis/custom-api.test.mjs, tests/unit/services/apis/thin-adapters.test.mjs
New comprehensive test suites for Azure OpenAI, Claude, custom API, and thin adapters; covering URL construction, headers, streaming SSE responses, error handling, and state updates.
Session Management Tests
tests/unit/services/init-session.test.mjs, tests/unit/services/local-session.test.mjs, tests/unit/services/wrappers-register.test.mjs
New test suites validating session initialization (UUID, timestamps, default fields), local session CRUD operations, and port listener registration with proper error handling and token/cookie helpers.
Utility Tests
tests/unit/utils/crop-text.test.mjs, tests/unit/utils/eventsource-parser.test.mjs, tests/unit/utils/jwt-token-generator.test.mjs, tests/unit/utils/model-name-convert.test.mjs
New unit tests for text cropping, SSE event parsing, JWT generation, and model name conversion utilities; covering edge cases, format variations, and error scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Review effort 4/5

Suggested reviewers

  • hariatisalikon283-star

Poem

🐰 A rabbit hops through test arrays,
Files refactored in merry ways,
Redaction moves to its own home,
Safety guards on every roam,
Tests bloom bright—no stone left alone! 🌿✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main change: expanding unit test coverage across utilities, APIs, and services. It is clear, concise, and directly reflects the primary objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

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

@gemini-code-assist
Copy link

Summary of Changes

Hello, 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 focuses on substantially improving the project's test suite by expanding coverage for existing utilities and services, and introducing new tests for previously untested modules and API integrations. The changes aim to enhance code robustness, prevent regressions, and ensure consistent behavior across various model providers and internal functionalities. A minor bug related to custom API response parsing was also addressed as a direct result of the expanded testing efforts.

Highlights

  • Expanded Unit Test Coverage: Significantly increased unit test coverage across various modules, including model-name-convert, eventsource-parser, crop-text, jwt-token-generator, and config predicates, adding 172 new tests.
  • New Test Files for Core Functionality: Introduced new test files for critical components such as selection-tools (covering all 11 tool's genPrompt functions), local-session (CRUD operations), init-session (session shape validation), and wrappers/registerPortListener (port message flow and token getters).
  • API Adapter Test Enhancements: Added comprehensive tests for API adapters, including custom-api (response schemas, [DONE] marker, finish_reason, error handling), azure-openai-api (URL composition, deployment fallback, headers), and claude-api (streaming, message_stop, header construction). Thin adapters (aiml, deepseek, moonshot, openrouter, chatglm) now have tests for delegation and key forwarding.
  • Sensitive Field Redaction Refactor and Tests: Extracted the redactSensitiveFields utility to its own module (src/background/redact.mjs) for improved testability and added new tests covering sensitive key redaction, prompt/selection integration, and depth limits.
  • Bug Fix in Custom API Handling: Implemented a bug fix in custom-api.mjs by adding optional chaining to data.choices access, preventing TypeError when APIs return responses without a choices field (e.g., Ollama).

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/background/index.mjs
    • Imported redactSensitiveFields from its new dedicated module.
    • Removed the local definition of SENSITIVE_KEYWORDS, isPromptOrSelectionLikeKey, and redactSensitiveFields.
  • src/background/redact.mjs
    • Added new file containing SENSITIVE_KEYWORDS array, isPromptOrSelectionLikeKey function, and redactSensitiveFields function.
  • src/services/apis/custom-api.mjs
    • Added optional chaining (?.) to data.choices access to prevent TypeError when the choices array is absent in API responses.
  • tests/setup/jsx-loader-hooks.mjs
    • Added new file to provide ESM loader hooks for transforming JSX via esbuild and handling CJS-to-ESM interop for specific packages.
  • tests/unit/background/redact.test.mjs
    • Added new test file for redactSensitiveFields covering sensitive keyword redaction, nested objects, arrays, maxDepth limits, null/primitive inputs, circular references, and prompt/selection-like keys.
    • Added tests for isPromptOrSelectionLikeKey to verify correct key matching.
  • tests/unit/config/config-predicates.test.mjs
    • Imported additional isUsing* predicate functions and getPreferredLanguageKey.
    • Expanded test coverage for isUsingMoonshotWebModel, isUsingGeminiWebModel, isUsingClaudeApiModel, isUsingMoonshotApiModel, isUsingDeepSeekApiModel, isUsingOpenRouterApiModel, isUsingAimlApiModel, isUsingChatGLMApiModel, isUsingOllamaApiModel, isUsingAzureOpenAiApiModel, isUsingGithubThirdPartyApiModel, and isUsingCustomNameOnlyModel.
    • Added tests for getPreferredLanguageKey to verify language preference retrieval and fallback logic.
  • tests/unit/content-script/selection-tools.test.mjs
    • Added new test file for selection-tools to verify genPrompt output for all 11 tools, including language prefixes, translation targets, bidirectional instructions, and empty selection handling.
  • tests/unit/services/apis/azure-openai-api.test.mjs
    • Added new test file for generateAnswersWithAzureOpenaiApi covering URL composition, API key headers, deployment fallback, max_tokens and temperature parameters, SSE delta aggregation, listener cleanup, and error handling.
  • tests/unit/services/apis/claude-api.test.mjs
    • Added new test file for generateAnswersWithClaudeApi covering URL and header construction, model and parameter passing, SSE streaming with content_block_delta and message_stop, record pushing, listener cleanup, and error handling for unparseable JSON.
  • tests/unit/services/apis/custom-api.test.mjs
    • Added new test file for generateAnswersWithCustomApi covering SSE chunk aggregation, various response schemas (message.content, choices[].text, direct response field), [DONE] marker handling, graceful skipping of unparseable JSON, metadata-only chunks, error responses, network errors, and conversation history inclusion.
  • tests/unit/services/apis/thin-adapters.test.mjs
    • Added new test file to verify common behavior of thin API adapters (aiml, deepseek, moonshot, openrouter, chatglm), ensuring correct base URL, API key forwarding, and delegation to the compatibility layer.
  • tests/unit/services/init-session.test.mjs
    • Added new test file for initSession to validate default field values, UUID generation, timestamp setting, uniqueness of session IDs, aiName resolution, and provider-specific field defaults.
  • tests/unit/services/local-session.test.mjs
    • Added new test file for local session management functions (createSession, deleteSession, getSession, getSessions, resetSessions, updateSession), covering CRUD operations, session initialization, and persistence.
  • tests/unit/services/wrappers-register.test.mjs
    • Added new test file for registerPortListener covering executor invocation, default model/API mode handling, aiName assignment, message filtering, error handling, and listener cleanup.
    • Added tests for token getter functions (getChatGptAccessToken, getBingAccessToken, getBardCookies, getClaudeSessionKey) covering cached tokens, fetching from endpoints, cookie parsing, and error conditions.
  • tests/unit/utils/crop-text.test.mjs
    • Added new test file for cropText utility covering text cropping logic, punctuation splitting, start/end preservation, empty input handling, model-specific max lengths, and behavior with tiktoken enabled.
  • tests/unit/utils/eventsource-parser.test.mjs
    • Expanded test coverage for createParser to include handling of \r\n and \r line endings, comment lines, data fields with no space after colon, empty data fields, null bytes in id fields, field lines without colon, partial buffers, BOM-like characters, and non-numeric retry values.
  • tests/unit/utils/jwt-token-generator.test.mjs
    • Added new test file for getToken covering valid JWT generation, verification with API key secret, token caching, regeneration after expiry, and error handling for invalid API key formats.
  • tests/unit/utils/model-name-convert.test.mjs
    • Imported additional utility functions from model-name-convert and ModelGroups.
    • Expanded test coverage for modelNameToDesc (including custom models and suffixes), modelNameToCustomPart, apiModeToModelName (for custom API modes), getApiModesStringArrayFromConfig, isApiModeSelected, isInApiModeGroup, modelNameToValue, and getModelValue.
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.

@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Use explicit undefined check

In custom-api.mjs, change the check for content from a truthy check to an
explicit content !== undefined check to correctly handle responses that are
empty strings.

src/services/apis/custom-api.mjs [80-88]

 const content = data.choices?.[0]?.message?.content
 // ...
 if (delta !== undefined) {
   answer += delta
-} else if (content) {
+} else if (content !== undefined) {
   answer = content
 } else if (text) {
   answer += text
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that an empty string content would be skipped and proposes a valid fix using an explicit undefined check, improving the robustness of response handling.

Medium
Possible issue
Correct test to use actual BOM

Correct the test for stripping BOMs to use the actual UTF-8 BOM byte sequence
[239, 187, 191] instead of a byte sequence that decodes to BOM-like characters.

tests/unit/utils/eventsource-parser.test.mjs [149-164]

-test('createParser strips BOM-like characters from decoded buffer', () => {
+test('createParser strips BOM from the beginning of the stream', () => {
   const parsed = []
   const parser = createParser((event) => parsed.push(event))
 
-  // Construct bytes that decode to chars with charCodes [239, 187, 191] () followed by SSE data
-  const bomChars = new Uint8Array([195, 175, 194, 187, 194, 191])
+  // A UTF-8 byte order mark (BOM) is the byte sequence 0xEF,0xBB,0xBF.
+  const bom = new Uint8Array([239, 187, 191])
   const sseData = toBytes('data: after-bom\n\n')
-  const combined = new Uint8Array(bomChars.length + sseData.length)
-  combined.set(bomChars)
-  combined.set(sseData, bomChars.length)
+  const combined = new Uint8Array(bom.length + sseData.length)
+  combined.set(bom)
+  combined.set(sseData, bom.length)
 
   parser.feed(combined)
 
   assert.equal(parsed.length, 1)
   assert.equal(parsed[0].data, 'after-bom')
 })
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a flaw in the test logic, where it tests for decoded BOM-like characters instead of the actual BOM byte sequence, and provides a correct fix.

Low
  • More

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Expands unit test coverage across core utilities, API adapters, content-script selection tools, and session/wrapper services, and includes a small runtime hardening fix for the custom API SSE parser plus a refactor to make background redaction logic testable.

Changes:

  • Add/extend unit tests across utils, config predicates, content-script tools, services/sessions, and multiple API adapters.
  • Refactor background sensitive-field redaction into src/background/redact.mjs and add dedicated tests.
  • Fix custom-api.mjs SSE parsing to avoid TypeError when choices is absent by using optional chaining.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/utils/model-name-convert.test.mjs Expands coverage for model name conversions, grouping, and selection predicates.
tests/unit/utils/jwt-token-generator.test.mjs Adds tests for JWT generation, verification, caching, and invalid key formats.
tests/unit/utils/eventsource-parser.test.mjs Extends parser coverage for CR/LF variants, comments, partial buffers, and retry handling.
tests/unit/utils/crop-text.test.mjs Adds tests for cropping behavior, punctuation splitting, clamp boundaries, and token-vs-char modes.
tests/unit/services/wrappers-register.test.mjs Adds tests for port listener flow and cookie/token getter helpers.
tests/unit/services/local-session.test.mjs Adds CRUD/reset/persistence tests for local session storage helpers.
tests/unit/services/init-session.test.mjs Adds tests for session shape defaults and aiName derivation behavior.
tests/unit/services/apis/thin-adapters.test.mjs Adds delegation/header forwarding tests for thin API adapters.
tests/unit/services/apis/custom-api.test.mjs Adds comprehensive SSE schema/edge-case/error-path tests for the custom API adapter.
tests/unit/services/apis/claude-api.test.mjs Adds streaming, header/body composition, and error handling tests for Claude API adapter.
tests/unit/services/apis/azure-openai-api.test.mjs Adds URL composition, deployment fallback, headers, and streaming tests for Azure OpenAI adapter.
tests/unit/content-script/selection-tools.test.mjs Adds genPrompt output tests for all selection tools, including language preference behavior.
tests/unit/config/config-predicates.test.mjs Extends coverage for provider isUsing* predicates and preferred language resolution.
tests/unit/background/redact.test.mjs Adds tests for sensitive key redaction, prompt/selection integration, depth and circular handling.
tests/setup/jsx-loader-hooks.mjs Introduces a custom ESM loader hook to enable importing JSX-containing .mjs in tests.
src/services/apis/custom-api.mjs Prevents crashes on SSE chunks missing choices by optional chaining access.
src/background/redact.mjs Extracts redaction logic into a standalone module for reuse and testability.
src/background/index.mjs Switches background logging redaction to the extracted module (removes inlined implementation).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import { afterEach, before, describe, test } from 'node:test'

// Register JSX/CJS loader hooks so the selection-tools module can be imported.
register('./tests/setup/jsx-loader-hooks.mjs', 'file://' + process.cwd() + '/')
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

module.register() is being called with a parent URL built via 'file://' + process.cwd() + '/'. On Windows this produces an invalid file URL (e.g. file://C:\...) and can cause the loader hook registration to fail. Use a proper file URL (e.g. import.meta.url as the parent URL, or pathToFileURL(process.cwd() + '/').href) so tests are cross-platform.

Suggested change
register('./tests/setup/jsx-loader-hooks.mjs', 'file://' + process.cwd() + '/')
register('./tests/setup/jsx-loader-hooks.mjs', import.meta.url)

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +20
export function isPromptOrSelectionLikeKey(lowerKey) {
const normalizedKey = lowerKey.replace(/[^a-z0-9]/g, '')
return (
normalizedKey.endsWith('question') ||
normalizedKey.endsWith('prompt') ||
normalizedKey.endsWith('query') ||
normalizedKey === 'selection' ||
normalizedKey === 'selectiontext'
)
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

isPromptOrSelectionLikeKey assumes its argument is already lowercased, but it’s exported and will return incorrect results for mixed-case keys (uppercase letters get stripped by /[^a-z0-9]/g, so e.g. userQuestion won’t match). To make the helper safe for direct use, normalize inside the function (e.g. lowercase before stripping) or rename it to make the requirement explicit and keep it non-exported.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@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: 2

🧹 Nitpick comments (4)
tests/unit/utils/eventsource-parser.test.mjs (1)

65-175: Optional: extract repeated parser setup into a tiny helper.

This block repeats the same parsed/parser initialization in each test. A helper would reduce noise and make future additions easier.

♻️ Suggested refactor
 const encoder = new TextEncoder()

 const toBytes = (text) => encoder.encode(text)
+
+const setupParser = () => {
+  const parsed = []
+  const parser = createParser((event) => parsed.push(event))
+  return { parsed, parser }
+}

 test('createParser handles \\r\\n line endings', () => {
-  const parsed = []
-  const parser = createParser((event) => parsed.push(event))
+  const { parsed, parser } = setupParser()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/utils/eventsource-parser.test.mjs` around lines 65 - 175, The
tests repeat the same parsed/parser setup; extract a small helper (e.g.,
withParsedParser or createTestParser) that encapsulates "const parsed = [];
const parser = createParser((event) => parsed.push(event));" and returns or
invokes the test body with parsed and parser so each test uses that helper
instead of duplicating those lines; update all tests that call
createParser/toBytes to use the new helper (references: createParser, parsed,
parser, toBytes) to keep behavior identical while reducing repetition.
tests/unit/utils/crop-text.test.mjs (1)

190-194: Assertion may be brittle if lengths coincidentally match.

The assert.notEqual assumes tiktoken and character modes will always produce different cropped lengths for this input. While usually true, edge cases exist where both modes could produce the same length by coincidence, causing a flaky test.

Consider asserting that cropping occurred in both modes (which you already do) and either removing this assertion or accepting the small flakiness risk for the added coverage value.

💡 Alternative: soften assertion to informational check
-  // Token counting vs char counting should produce different results
-  assert.notEqual(
-    resultTiktoken.length,
-    resultChars.length,
-    'tiktoken and char modes should produce different crop lengths',
-  )
+  // Token counting vs char counting typically produce different results
+  // (not strictly required, but expected for most inputs)
+  if (resultTiktoken.length === resultChars.length) {
+    console.log('Note: tiktoken and char modes produced same length (possible but rare)')
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/utils/crop-text.test.mjs` around lines 190 - 194, The brittle
assertion comparing resultTiktoken.length and resultChars.length should be
removed or softened; instead, drop the assert.notEqual check and rely on
existing assertions that cropping occurred for both modes
(resultTiktoken.cropped and resultChars.cropped) or convert the length
comparison into a non-failing informational/logging check so the test doesn't
flake when lengths coincidentally match; update the test that references
resultTiktoken and resultChars accordingly.
tests/unit/services/wrappers-register.test.mjs (1)

205-221: Consider using a more deterministic wait mechanism.

The 50ms setTimeout delays (lines 217, 244) for waiting on async handlers could occasionally cause flakiness in CI environments under load. While this works in practice, consider using a polling approach or exposing the internal promise for more deterministic testing.

// Alternative: poll for expected state
const waitFor = async (predicate, timeout = 500, interval = 10) => {
  const start = Date.now()
  while (Date.now() - start < timeout) {
    if (predicate()) return
    await new Promise((r) => setTimeout(r, interval))
  }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/services/wrappers-register.test.mjs` around lines 205 - 221, The
test uses a fixed 50ms setTimeout to wait for async handlers which can be flaky;
replace the hard sleep in tests/unit/services/wrappers-register.test.mjs with a
deterministic wait (e.g., add a local waitFor(predicate, timeout, interval)
helper) and use it to poll until executor.mock.calls.length === expected value
(or port.postedMessages length) after calling registerPortListener and
port.emitMessage; alternatively adjust the tested code (registerPortListener) to
expose or return a promise that the test can await and use that promise instead
of setTimeout.
src/background/redact.mjs (1)

1-10: Consider adding 'cookie' to SENSITIVE_KEYWORDS.

The codebase handles various cookies (Bard, Bing, Claude session keys) that contain authentication data. Adding 'cookie' to SENSITIVE_KEYWORDS would provide defense-in-depth for any future cookie-related fields in logged objects.

💡 Suggested addition
 const SENSITIVE_KEYWORDS = [
   'apikey', // Covers apiKey, customApiKey, claudeApiKey, etc.
   'token', // Covers accessToken, refreshToken, etc.
   'secret',
   'password',
   'kimimoonshotrefreshtoken',
   'credential',
   'jwt',
   'session',
+  'cookie',
 ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/background/redact.mjs` around lines 1 - 10, Add the string 'cookie' to
the SENSITIVE_KEYWORDS array so cookie-related fields (e.g., Bard/Bing/Claude
session cookies) are treated as sensitive; update the SENSITIVE_KEYWORDS
constant in redact.mjs (where it's defined) to include 'cookie' alongside
'apikey', 'token', etc., and run/update any unit tests that validate redaction
behavior to cover cookie-named fields to ensure case-insensitive matching
continues to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/setup/jsx-loader-hooks.mjs`:
- Around line 30-37: The generated shim strings currently include semicolons at
the end of statements (see the template lines producing `import { createRequire
as _cr } from 'node:module';`, `const _req = _cr(...) ;`, `const _mod =
_req(...) ;`, each `export const ${id} = _mod[...] ;` and `export default
_mod;`); update those template literals to remove the trailing semicolons so the
produced module source matches the repo style (no semicolons) while preserving
the exact identifiers (`_cr`, `_req`, `_mod`, the generated `export const ${id}`
lines and `export default _mod`) and string interpolation logic.
- Line 8: JSX_RE is too narrow (only matches capitalized component tags) and
misses lowercase HTML tags and fragments; update the regex used for JSX
detection (JSX_RE) to match any tag name and fragment starts — e.g. replace
/<[A-Z][A-Za-z0-9]*[\s/>]/ with a pattern that also matches lowercase names and
the fragment opener like /<(?:[A-Za-z][A-Za-z0-9-]*|>)[\s/>]/ (and apply the
same replacement to the other occurrences referenced around lines 47-55) so .mjs
JSX files and <>...</> fragments are detected and transformed.

---

Nitpick comments:
In `@src/background/redact.mjs`:
- Around line 1-10: Add the string 'cookie' to the SENSITIVE_KEYWORDS array so
cookie-related fields (e.g., Bard/Bing/Claude session cookies) are treated as
sensitive; update the SENSITIVE_KEYWORDS constant in redact.mjs (where it's
defined) to include 'cookie' alongside 'apikey', 'token', etc., and run/update
any unit tests that validate redaction behavior to cover cookie-named fields to
ensure case-insensitive matching continues to work.

In `@tests/unit/services/wrappers-register.test.mjs`:
- Around line 205-221: The test uses a fixed 50ms setTimeout to wait for async
handlers which can be flaky; replace the hard sleep in
tests/unit/services/wrappers-register.test.mjs with a deterministic wait (e.g.,
add a local waitFor(predicate, timeout, interval) helper) and use it to poll
until executor.mock.calls.length === expected value (or port.postedMessages
length) after calling registerPortListener and port.emitMessage; alternatively
adjust the tested code (registerPortListener) to expose or return a promise that
the test can await and use that promise instead of setTimeout.

In `@tests/unit/utils/crop-text.test.mjs`:
- Around line 190-194: The brittle assertion comparing resultTiktoken.length and
resultChars.length should be removed or softened; instead, drop the
assert.notEqual check and rely on existing assertions that cropping occurred for
both modes (resultTiktoken.cropped and resultChars.cropped) or convert the
length comparison into a non-failing informational/logging check so the test
doesn't flake when lengths coincidentally match; update the test that references
resultTiktoken and resultChars accordingly.

In `@tests/unit/utils/eventsource-parser.test.mjs`:
- Around line 65-175: The tests repeat the same parsed/parser setup; extract a
small helper (e.g., withParsedParser or createTestParser) that encapsulates
"const parsed = []; const parser = createParser((event) => parsed.push(event));"
and returns or invokes the test body with parsed and parser so each test uses
that helper instead of duplicating those lines; update all tests that call
createParser/toBytes to use the new helper (references: createParser, parsed,
parser, toBytes) to keep behavior identical while reducing repetition.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 31331b7 and 1e3261e.

📒 Files selected for processing (18)
  • src/background/index.mjs
  • src/background/redact.mjs
  • src/services/apis/custom-api.mjs
  • tests/setup/jsx-loader-hooks.mjs
  • tests/unit/background/redact.test.mjs
  • tests/unit/config/config-predicates.test.mjs
  • tests/unit/content-script/selection-tools.test.mjs
  • tests/unit/services/apis/azure-openai-api.test.mjs
  • tests/unit/services/apis/claude-api.test.mjs
  • tests/unit/services/apis/custom-api.test.mjs
  • tests/unit/services/apis/thin-adapters.test.mjs
  • tests/unit/services/init-session.test.mjs
  • tests/unit/services/local-session.test.mjs
  • tests/unit/services/wrappers-register.test.mjs
  • tests/unit/utils/crop-text.test.mjs
  • tests/unit/utils/eventsource-parser.test.mjs
  • tests/unit/utils/jwt-token-generator.test.mjs
  • tests/unit/utils/model-name-convert.test.mjs

import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'

const JSX_RE = /<[A-Z][A-Za-z0-9]*[\s/>]/
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Expand JSX detection beyond uppercase component tags.

JSX_RE currently misses valid JSX like <div /> and fragments (<>...</>), so those .mjs files won’t be transformed and can fail at load time.

🔧 Suggested fix
-const JSX_RE = /<[A-Z][A-Za-z0-9]*[\s/>]/
+const JSX_RE = /<(?:[A-Za-z][A-Za-z0-9-]*|>)[\s/>]?/

Also applies to: 47-55

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/setup/jsx-loader-hooks.mjs` at line 8, JSX_RE is too narrow (only
matches capitalized component tags) and misses lowercase HTML tags and
fragments; update the regex used for JSX detection (JSX_RE) to match any tag
name and fragment starts — e.g. replace /<[A-Z][A-Za-z0-9]*[\s/>]/ with a
pattern that also matches lowercase names and the fragment opener like
/<(?:[A-Za-z][A-Za-z0-9-]*|>)[\s/>]/ (and apply the same replacement to the
other occurrences referenced around lines 47-55) so .mjs JSX files and <>...</>
fragments are detected and transformed.

Comment on lines +30 to +37
`import { createRequire as _cr } from 'node:module';`,
`const _req = _cr(${JSON.stringify(url)});`,
`const _mod = _req(${JSON.stringify(pkg)});`,
...names.map((n) => {
const id = IDENT_RE.test(n) ? n : toSafe(n)
return `export const ${id} = _mod[${JSON.stringify(n)}];`
}),
`export default _mod;`,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove semicolons in generated shim source to match repo style.

The generated module source currently emits semicolon-terminated statements.

🎯 Suggested fix
-        `import { createRequire as _cr } from 'node:module';`,
-        `const _req = _cr(${JSON.stringify(url)});`,
-        `const _mod = _req(${JSON.stringify(pkg)});`,
+        `import { createRequire as _cr } from 'node:module'`,
+        `const _req = _cr(${JSON.stringify(url)})`,
+        `const _mod = _req(${JSON.stringify(pkg)})`,
         ...names.map((n) => {
           const id = IDENT_RE.test(n) ? n : toSafe(n)
-          return `export const ${id} = _mod[${JSON.stringify(n)}];`
+          return `export const ${id} = _mod[${JSON.stringify(n)}]`
         }),
-        `export default _mod;`,
+        `export default _mod`,
As per coding guidelines: "Do not use semicolons at the end of statements."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`import { createRequire as _cr } from 'node:module';`,
`const _req = _cr(${JSON.stringify(url)});`,
`const _mod = _req(${JSON.stringify(pkg)});`,
...names.map((n) => {
const id = IDENT_RE.test(n) ? n : toSafe(n)
return `export const ${id} = _mod[${JSON.stringify(n)}];`
}),
`export default _mod;`,
`import { createRequire as _cr } from 'node:module'`,
`const _req = _cr(${JSON.stringify(url)})`,
`const _mod = _req(${JSON.stringify(pkg)})`,
...names.map((n) => {
const id = IDENT_RE.test(n) ? n : toSafe(n)
return `export const ${id} = _mod[${JSON.stringify(n)}]`
}),
`export default _mod`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/setup/jsx-loader-hooks.mjs` around lines 30 - 37, The generated shim
strings currently include semicolons at the end of statements (see the template
lines producing `import { createRequire as _cr } from 'node:module';`, `const
_req = _cr(...) ;`, `const _mod = _req(...) ;`, each `export const ${id} =
_mod[...] ;` and `export default _mod;`); update those template literals to
remove the trailing semicolons so the produced module source matches the repo
style (no semicolons) while preserving the exact identifiers (`_cr`, `_req`,
`_mod`, the generated `export const ${id}` lines and `export default _mod`) and
string interpolation logic.

Copy link

@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 pull request significantly expands unit test coverage across various parts of the application, including utilities, APIs, and services, which is a great improvement for code quality and reliability. The changes also include a bug fix in the custom API adapter and a refactoring of the sensitive data redaction logic for better testability. My review focuses on a few opportunities to improve code clarity and maintainability in the new redact.mjs module.

Comment on lines +12 to +21
export function isPromptOrSelectionLikeKey(lowerKey) {
const normalizedKey = lowerKey.replace(/[^a-z0-9]/g, '')
return (
normalizedKey.endsWith('question') ||
normalizedKey.endsWith('prompt') ||
normalizedKey.endsWith('query') ||
normalizedKey === 'selection' ||
normalizedKey === 'selectiontext'
)
}

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 maintainability, you can refactor this function to use arrays for storing matching patterns and then use array methods like some() and includes() for checking. This approach avoids a long chain of || operators, making it easier to add or remove conditions in the future.

export function isPromptOrSelectionLikeKey(lowerKey) {
  const normalizedKey = lowerKey.replace(/[^a-z0-9]/g, '');
  const suffixes = ['question', 'prompt', 'query'];
  const exactMatches = ['selection', 'selectiontext'];
  return (
    suffixes.some((suffix) => normalizedKey.endsWith(suffix)) ||
    exactMatches.includes(normalizedKey)
  );
}

Comment on lines +54 to +62
let isKeySensitive = isPromptOrSelectionLikeKey(lowerKey)
if (!isKeySensitive) {
for (const keyword of SENSITIVE_KEYWORDS) {
if (lowerKey.includes(keyword)) {
isKeySensitive = true
break
}
}
}

Choose a reason for hiding this comment

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

medium

The logic for determining if a key is sensitive can be simplified. Instead of using a for...of loop inside an if block, you can combine the checks into a single, more concise expression using the some() array method. This improves readability and reduces nesting.

      const isKeySensitive =
        isPromptOrSelectionLikeKey(lowerKey) ||
        SENSITIVE_KEYWORDS.some((keyword) => lowerKey.includes(keyword));

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants