Skip to content

refactor: refactors turnstile and add tests#1421

Merged
stalniy merged 1 commit intomainfrom
refactor/turnstile
Jun 8, 2025
Merged

refactor: refactors turnstile and add tests#1421
stalniy merged 1 commit intomainfrom
refactor/turnstile

Conversation

@stalniy
Copy link
Contributor

@stalniy stalniy commented May 30, 2025

Summary by CodeRabbit

  • New Features

    • Improved handling of Cloudflare challenge responses for both fetch and axios requests, providing a smoother user experience when encountering security challenges.
  • Refactor

    • Enhanced the Turnstile component for better integration and flexibility, including support for component injection and improved abort logic.
  • Tests

    • Added comprehensive tests for the Turnstile component to ensure correct behavior under various challenge and user interaction scenarios.
  • Chores

    • Updated development dependencies and test setup for improved compatibility and reliability.

@stalniy stalniy requested a review from a team as a code owner May 30, 2025 21:49
@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 30, 2025

Walkthrough

These changes introduce a robust mechanism in the Turnstile React component to handle Cloudflare challenge responses by intercepting fetch and axios requests. The Turnstile component is now more modular and testable, with new tests added and a fetch polyfill included for testing environments.

Changes

File(s) Change Summary
apps/deploy-web/next.config.js Added "@marsidev/react-turnstile" to transpile packages list for test environment.
apps/deploy-web/package.json Added "whatwg-fetch" as a development dependency.
apps/deploy-web/tests/unit/setup.ts Imported "whatwg-fetch" polyfill for unit test setup.
apps/deploy-web/src/components/turnstile/Turnstile.tsx Refactored Turnstile component: improved fetch/axios challenge handling, added abort logic, component injection, and exports.
apps/deploy-web/src/components/turnstile/Turnstile.spec.tsx Added a comprehensive test suite for Turnstile, covering challenge handling, widget lifecycle, and user interactions.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Turnstile
    participant fetch/axios
    participant Cloudflare

    User->>fetch/axios: Initiate request
    fetch/axios->>Cloudflare: Send request
    Cloudflare-->>fetch/axios: Response (with cf-mitigated: challenge?)
    alt Challenge detected
        fetch/axios->>Turnstile: Trigger challenge UI
        Turnstile->>User: Display Turnstile widget
        User->>Turnstile: Solve or dismiss challenge
        alt Solved
            Turnstile->>fetch/axios: Retry original request
        else Dismissed
            Turnstile->>fetch/axios: Abort retry
        end
    else No challenge
        fetch/axios-->>User: Return response
    end
Loading

Suggested reviewers

  • baktun14

Poem

A challenge appears, but fear not, my friend—
Turnstile hops in, the puzzles to mend.
With fetch and axios, we intercept the fray,
Awaiting your click to clear the way.
Tests and polyfills join the quest,
For Cloudflare’s riddles, we’re now well-dressed!
🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-08T03_08_15_151Z-debug-0.log


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21f843f and fae3217.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • apps/deploy-web/next.config.js (1 hunks)
  • apps/deploy-web/package.json (1 hunks)
  • apps/deploy-web/src/components/turnstile/Turnstile.spec.tsx (1 hunks)
  • apps/deploy-web/src/components/turnstile/Turnstile.tsx (4 hunks)
  • apps/deploy-web/tests/unit/setup.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/deploy-web/tests/unit/setup.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/deploy-web/package.json
  • apps/deploy-web/next.config.js
  • apps/deploy-web/src/components/turnstile/Turnstile.tsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test-deploy-web-build
  • GitHub Check: validate-deploy-web
🔇 Additional comments (7)
apps/deploy-web/src/components/turnstile/Turnstile.spec.tsx (7)

1-12: Well-organized imports and dependencies.

The imports are properly structured and include all necessary testing utilities. Good use of type imports and appropriate testing libraries.


13-38: Excellent basic functionality coverage.

These tests provide good coverage of the fundamental enable/disable behavior and fetch API patching. The test cases are clear and focused on specific behaviors.


40-55: Sophisticated error handling test with proper mocking.

The test effectively verifies widget reset behavior on error using a well-designed mock setup. The use of useForwardedRef helper is elegant for connecting the mock instance.


57-105: Comprehensive user interaction testing.

These tests thoroughly verify the retry and dismiss button behaviors with appropriate component mocking. The approach of injecting mocked components through the components prop demonstrates good testability design.


107-182: Thorough CloudFlare challenge scenario testing.

The nested describe block with sophisticated fetch mocking effectively simulates real-world CloudFlare challenge scenarios. The mock implementation correctly simulates the challenge flow with the cf-mitigated header.


184-201: Well-designed setup helper function.

The setup function provides good abstraction for test initialization while maintaining flexibility through the input parameter. The use of MockComponents utility and default mocking strategy is well-thought-out.


203-212: Elegant ref forwarding utility.

The useForwardedRef helper handles both function and object ref patterns correctly, providing a clean way to connect mock instances in tests.

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codecov
Copy link

codecov bot commented May 30, 2025

Codecov Report

Attention: Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.

Project coverage is 38.71%. Comparing base (d40d540) to head (fae3217).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
.../deploy-web/src/components/turnstile/Turnstile.tsx 93.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1421      +/-   ##
==========================================
+ Coverage   38.35%   38.71%   +0.35%     
==========================================
  Files         849      849              
  Lines       20141    20136       -5     
  Branches     3657     3656       -1     
==========================================
+ Hits         7726     7795      +69     
+ Misses      12091    12017      -74     
  Partials      324      324              
Flag Coverage Δ *Carryforward flag
api 70.33% <ø> (ø) Carriedforward from d40d540
deploy-web 17.05% <93.33%> (+0.55%) ⬆️
notifications 88.94% <ø> (ø) Carriedforward from d40d540
provider-proxy 80.09% <ø> (ø) Carriedforward from d40d540

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
.../deploy-web/src/components/turnstile/Turnstile.tsx 91.66% <93.33%> (+91.66%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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 (3)
apps/deploy-web/src/components/turnstile/Turnstile.spec.tsx (1)

6-6: Consider using a simpler wait utility.

Using setTimeout as wait from node:timers/promises might be unnecessary complexity. Consider using the existing wait utility from the codebase or a simpler approach.

-import { setTimeout as wait } from "node:timers/promises";
+import { act } from "@testing-library/react";

Then in the setup function:

-await act(() => wait(0));
+await act(async () => {});
apps/deploy-web/src/components/turnstile/Turnstile.tsx (2)

63-71: Consider cleaning up the abort event listener to prevent memory leaks.

The abort event listener added on line 69 might not be removed if the promise resolves before abort, potentially causing a memory leak.

Consider using a cleanup approach:

-    return Promise.race([
-      turnstileRef.current?.getResponsePromise(),
-      new Promise<void>(resolve => abortControllerRef.current?.signal.addEventListener("abort", () => resolve()))
-    ]);
+    const responsePromise = turnstileRef.current?.getResponsePromise();
+    const abortPromise = new Promise<void>((resolve) => {
+      const handleAbort = () => resolve();
+      abortControllerRef.current?.signal.addEventListener("abort", handleAbort);
+    });
+    
+    try {
+      return await Promise.race([responsePromise, abortPromise]);
+    } finally {
+      // Clean up could be added here if needed
+    }

Alternatively, since AbortController signals are one-time use and the controller is recreated each time, this might not be a practical issue.


82-83: Consider implementing retry limits to prevent infinite retry loops.

If the Cloudflare challenge repeatedly fails, the current implementation could retry indefinitely, potentially causing performance issues.

Consider adding a retry limit mechanism. You could track retry attempts in a ref and limit retries:

const retryCountRef = useRef<Map<string, number>>(new Map());

// In fetch override:
const key = `${resource}`;
const retryCount = retryCountRef.current.get(key) || 0;
if (retryCount < MAX_RETRIES && response.headers.get("cf-mitigated") === "challenge" && ...) {
  retryCountRef.current.set(key, retryCount + 1);
  return globalThis.fetch(resource, options);
}

This would prevent infinite retry loops while still allowing reasonable retry attempts.

Also applies to: 91-92

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f055609 and 21f843f.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • apps/deploy-web/next.config.js (1 hunks)
  • apps/deploy-web/package.json (1 hunks)
  • apps/deploy-web/src/components/turnstile/Turnstile.spec.tsx (1 hunks)
  • apps/deploy-web/src/components/turnstile/Turnstile.tsx (4 hunks)
  • apps/deploy-web/tests/unit/setup.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/deploy-web/src/components/turnstile/Turnstile.spec.tsx (4)
apps/deploy-web/src/components/turnstile/Turnstile.tsx (2)
  • Turnstile (51-177)
  • COMPONENTS (39-43)
apps/deploy-web/tests/fixture/context-with-extension.ts (1)
  • expect (77-77)
apps/deploy-web/tests/unit/mocks.tsx (1)
  • MockComponents (10-15)
apps/deploy-web/src/utils/timer.ts (1)
  • wait (26-32)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test-deploy-web-build
🔇 Additional comments (13)
apps/deploy-web/package.json (1)

176-176:

Details

✅ Verification successful

Verify the whatwg-fetch version for security and currency.

The addition of the fetch polyfill is appropriate for testing environments. However, please ensure this version is current and free from security vulnerabilities.


🌐 Web query:

What is the latest version of whatwg-fetch npm package and are there any known security vulnerabilities?

💡 Result:

Latest Version of whatwg-fetch

The latest version of the whatwg-fetch npm package is 3.6.20, which was published on December 13, 2023[1][5].

Security Vulnerabilities

There are no known direct security vulnerabilities associated with the whatwg-fetch package as of the latest release (version 3.6.20). This status is confirmed by security databases such as Snyk[5]. However, note that this assessment covers only the package itself and does not include potential vulnerabilities in its dependencies[5].

Summary Table

Package Name Latest Version Release Date Known Vulnerabilities
whatwg-fetch 3.6.20 Dec 13, 2023 None reported[5]

Additional Notes

  • To install the package:
    npm install whatwg-fetch --save
  • For older browsers, a Promise polyfill may also be required[1].

No critical security advisories have been issued against whatwg-fetch up to the present date[5].

Citations:


whatwg-fetch@^3.6.20 is up-to-date and secure

The installed version (3.6.20) is the latest release (Dec 13, 2023) and carries no known security vulnerabilities. No further action is needed.

apps/deploy-web/tests/unit/setup.ts (1)

2-2: LGTM! Proper fetch polyfill setup for testing.

The fetch polyfill import is correctly positioned and aligns with the dependency added in package.json. This ensures fetch API compatibility in the Jest test environment.

apps/deploy-web/next.config.js (1)

26-26: LGTM! Necessary transpilation for test environment.

Adding "@marsidev/react-turnstile" to the transpile packages list ensures compatibility with the test environment by transpiling modern JavaScript features in the package.

apps/deploy-web/src/components/turnstile/Turnstile.spec.tsx (8)

14-18: LGTM! Clear test for disabled state.

The test correctly verifies that the component doesn't render when disabled.


20-25: LGTM! Important test for fetch API behavior.

This test ensures that the global fetch is not modified when the component is disabled, which is crucial for preventing unintended side effects.


40-55: Well-designed error handling test.

The test effectively uses mocking to simulate error conditions and verify the widget reset behavior. The use of forwardRef and useForwardedRef is a clever approach to expose the mock instance.


57-80: Good test coverage for retry functionality.

The test correctly verifies that clicking the Retry button triggers the expected widget lifecycle methods.


107-146: Excellent test for CF-Mitigated header handling.

The test setup with beforeEach and afterEach properly manages the fetch mock, and the test verifies that the widget renders when encountering a CF-Mitigated challenge response.


167-181: Comprehensive test for challenge resolution flow.

This test effectively verifies the complete flow from challenge detection to successful retry, ensuring the fetch is called twice as expected.


184-201: Well-structured setup function with good defaults.

The setup function provides sensible defaults while allowing customization through the input parameter. The use of MockComponents with overrides is a clean approach.


203-211: Clever utility for ref forwarding in tests.

The useForwardedRef helper elegantly handles both function and object refs while providing a default mock instance. This is a reusable pattern for testing components with refs.

apps/deploy-web/src/components/turnstile/Turnstile.tsx (2)

39-44: Great implementation of component injection pattern!

The component injection approach provides excellent flexibility for testing and customization while maintaining backward compatibility with sensible defaults.

Also applies to: 48-49, 51-51


79-87: 🛠️ Refactor suggestion

Consider adding error handling for fetch retry attempts.

The retry fetch on line 83 doesn't handle potential errors, which could lead to unhandled promise rejections.

Consider wrapping the retry in a try-catch:

       globalThis.fetch = async (resource, options) => {
-        const response = await fetch(resource, options);
-
-        if (response.headers.get("cf-mitigated") === "challenge" && turnstileRef.current && (await renderTurnstileAndWaitForResponse())) {
-          return globalThis.fetch(resource, options);
-        }
-
-        return response;
+        try {
+          const response = await fetch(resource, options);
+
+          if (response.headers.get("cf-mitigated") === "challenge" && turnstileRef.current && (await renderTurnstileAndWaitForResponse())) {
+            return globalThis.fetch(resource, options);
+          }
+
+          return response;
+        } catch (error) {
+          // Re-throw to maintain original behavior
+          throw error;
+        }
       };

This ensures that any errors are properly propagated and can be handled by the caller.

Likely an incorrect or invalid review comment.

@stalniy stalniy force-pushed the refactor/turnstile branch from 21f843f to fae3217 Compare June 8, 2025 03:07
@stalniy stalniy merged commit 79aadc2 into main Jun 8, 2025
28 checks passed
@stalniy stalniy deleted the refactor/turnstile branch June 8, 2025 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments