Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swo 98 backend determine retry for streaming hls video flow #56

Merged

Conversation

shinaBR2
Copy link
Owner

@shinaBR2 shinaBR2 commented Feb 16, 2025

Summary by CodeRabbit

  • New Features

    • Enhanced error reporting and transaction management for video streaming, leading to more precise messaging when media fetching, processing, or thumbnail generation issues occur.
    • Expanded error code support now distinguishes between network, server, client, and database issues, which improves clarity for handling failures.
  • Tests

    • Updated test suites validate the refined error handling and transaction processes, ensuring a more reliable and consistent streaming experience for users.
    • Introduced comprehensive tests for the new fetch utility, simulating various fetch scenarios to ensure robust error handling.
    • Added tests for new HTTP and database error codes, ensuring proper validation and coverage.

@shinaBR2 shinaBR2 self-assigned this Feb 16, 2025
Copy link

coderabbitai bot commented Feb 16, 2025

Walkthrough

This pull request refines error handling, transaction management, and streaming logic across several video processing modules. The updates modify the stream handlers and M3U8 helper functions to use improved error propagation—via enhanced CustomError methods and new error code definitions—and integrate a centralized fetch wrapper (fetchWithError). Test suites are updated accordingly with new mocks (including Sequelize transactions) and revised assertions. Additionally, configuration references (e.g., videoConfig) are standardized, and HTTP/database error codes are introduced to broaden error categorization.

Changes

File(s) Change Summary
src/apps/io/videos/routes/stream-hls/index.ts
src/apps/io/videos/routes/stream-hls/index.test.ts
Updated streamHLSHandler to initiate transactions using sequelize, commit or rollback as needed, and throw structured errors with DATABASE_ERRORS via CustomError. Test cases now use enhanced error assertions and mocks.
src/services/videos/helpers/m3u8/*.ts
src/services/videos/helpers/m3u8/*.test.ts
Refactored M3U8 handling by replacing direct fetch calls with fetchWithError, removing redundant try-catch blocks, and updating tests to check for proper error propagation (including renamed and new test scenarios).
src/utils/custom-error/index.ts Modified CustomError.critical implementation to include additional details and updated the type for originalError to support unknown.
src/utils/error-codes/*.ts
src/utils/error-codes/*.test.ts
Introduced new error code constants for HTTP and database errors; updated the ErrorCode union type to encompass these new codes, with extended tests verifying expected error codes.
src/utils/fetch/*.ts
src/utils/fetch/*.test.ts
Added the fetchWithError function which wraps the native fetch for enhanced error handling (covering client/server/network errors) and provided a comprehensive test suite simulating various fetch scenarios.
package.json Removed @types/node-fetch and node-fetch dependencies, indicating a shift away from the previous fetch implementation.
src/services/videos/helpers/gcp-cloud-storage/index.test.ts Removed mock implementation for node-fetch, indicating a transition to the new fetch handling approach.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant StreamHandler as streamHLSHandler
    participant DB as sequelize
    participant VideoSvc as VideoService

    Client->>StreamHandler: Request video stream
    StreamHandler->>DB: Start transaction
    StreamHandler->>VideoSvc: finalizeVideo()
    VideoSvc-->>StreamHandler: Video finalized
    StreamHandler->>VideoSvc: completeTask()
    VideoSvc-->>StreamHandler: Task completed
    StreamHandler->>DB: Commit transaction
    StreamHandler-->>Client: Return success

    %% Error scenario:
    Note over StreamHandler: On error, rollback transaction and throw CustomError with DATABASE_ERRORS.DB_ERROR
Loading
sequenceDiagram
    participant Caller
    participant FetchUtil as fetchWithError
    participant FetchAPI as fetch

    Caller->>FetchUtil: call fetchWithError(url, options)
    FetchUtil->>FetchAPI: perform fetch(url)
    alt Successful Response
        FetchAPI-->>FetchUtil: 2xx response
        FetchUtil-->>Caller: Return response
    else HTTP / Network Error
        FetchAPI-->>FetchUtil: Error response / throw error
        FetchUtil->>Caller: Throw CustomError with error code and retry flag
    end
Loading

Possibly related PRs

  • Swo 96 video thumbnail when upload #54: Introduces a new route handler for fixing video thumbnails, involving modifications to error handling akin to the changes in this PR.
  • Swo 87 new feature download directly m3u8 #15: Focuses on error handling and streaming functionalities in the context of M3U8 playlists, closely aligning with the revised error propagation in streaming functions.
  • Swo 89 full flow testing #39: Deals with improvements in mocking Sequelize transactions and error management, which is strongly related to the transaction handling updates in this PR.

Poem

I hopped through lines of code so bright,
Fixing errors with all my might.
Transactions roll, and fetch calls sing,
In streaming tests, new codes take wing.
A rabbit’s cheer for robust design—
Leaping bugs away in every line!
🐰🥕 Happy hops in our code universe!

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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. (Beta)
  • @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.

Copy link

codecov bot commented Feb 16, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Files with missing lines Coverage Δ
src/apps/io/videos/routes/stream-hls/index.ts 100.00% <100.00%> (ø)
src/services/videos/helpers/m3u8/helpers.ts 100.00% <100.00%> (ø)
src/services/videos/helpers/m3u8/index.ts 100.00% <100.00%> (ø)
src/utils/custom-error/index.ts 100.00% <ø> (ø)
src/utils/error-codes/index.ts 100.00% <100.00%> (ø)
src/utils/fetch/index.ts 100.00% <100.00%> (ø)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (2)
src/services/videos/helpers/m3u8/helpers.ts (1)

219-226: 🛠️ Refactor suggestion

Use fetchWithError for consistent error handling.

The function uses direct fetch while other parts of the codebase use fetchWithError. Consider using fetchWithError for consistency:

 const streamSegmentFile = async (segmentUrl: string, storagePath: string) => {
-  const response = await fetch(segmentUrl, {
+  const response = await fetchWithError(segmentUrl, {
     timeout: systemConfig.defaultExternalRequestTimeout,
     size: 32 * 1024 * 1024, // 32MB as safe maximum for high quality segments
   });
-  if (!response.ok || !response.body) {
+  if (!response.body) {
     throw new Error(`Failed to fetch segment: ${response.status} ${response.statusText}`);
   }
src/services/videos/helpers/m3u8/helpers.test.ts (1)

590-651: 🛠️ Refactor suggestion

Update streamSegmentFile tests to use fetchWithError.

For consistency with the implementation, update the tests to use fetchWithError:

 test('should throw error when fetch fails', async () => {
-  const mockResponse = {
-    ok: false,
-    body: null,
-  };
-  (fetch as any).mockResolvedValue(mockResponse);
+  vi.mocked(fetchWithError).mockRejectedValue(new Error('Failed to fetch segment: Network error'));

   await expect(streamSegmentFile('http://example.com/segment.ts', 'test-path/segment.ts')).rejects.toThrow(
-    'Failed to fetch segment'
+    'Failed to fetch segment: Network error'
   );
 });

 test('should throw error when response body is null', async () => {
-  const mockResponse = {
-    ok: true,
-    body: null,
-  };
-  (fetch as any).mockResolvedValue(mockResponse);
+  vi.mocked(fetchWithError).mockResolvedValue({
+    body: null,
+  } as any);

   await expect(streamSegmentFile('http://example.com/segment.ts', 'test-path/segment.ts')).rejects.toThrow(
     'Failed to fetch segment'
   );
 });
🧹 Nitpick comments (9)
src/services/videos/helpers/m3u8/index.ts (1)

44-48: Consider adding more fields to the context.

Having additional identifiers (e.g., user ID, video ID) in the context can help diagnose issues more quickly if broader context is needed for debugging.

src/utils/fetch/index.ts (1)

10-55: Implementation provides robust error handling, but consider using timeout.

The mechanism cleanly distinguishes between server errors, client errors, and network failures. However, the optional timeout property is not currently implemented. To fully support request timeouts, consider using an AbortController or similar approach to enforce the specified timeout.

src/services/videos/helpers/m3u8/index.test.ts (3)

24-32: Improve error object creation in CustomError mock.

The current mock creates plain Error objects which may cause instanceof checks to fail. Consider extending Error properly:

-      const error = new Error(message);
-      Object.assign(error, details);
-      return error;
+      class CustomErrorMock extends Error {
+        constructor(message: string, details: any) {
+          super(message);
+          Object.assign(this, details);
+          this.name = 'CustomError';
+        }
+      }
+      return new CustomErrorMock(message, details);

119-135: Enhance empty segments test coverage.

The test verifies error message and error code but doesn't check the duration value. Consider adding an assertion:

     expect(CustomError.medium).toHaveBeenCalledWith('Empty HLS content', {
       errorCode: VIDEO_ERRORS.INVALID_LENGTH,
       context: expectedContext,
       source: 'services/videos/helpers/m3u8/index.ts',
     });
     expect(streamPlaylistFile).not.toHaveBeenCalled();
     expect(streamSegments).not.toHaveBeenCalled();
+    expect(result.duration).toBe(0);

171-189: Enhance error messages in streaming failure tests.

The error messages could be more specific to help with debugging. Consider including more context:

-    const playlistStreamError = new Error('Playlist stream failed');
+    const playlistStreamError = new Error('Failed to stream playlist: network timeout');
     vi.mocked(streamPlaylistFile).mockRejectedValue(playlistStreamError);

-    await expect(streamM3U8(mockM3u8Url, mockStoragePath)).rejects.toThrow('Failed to stream file to storage');
+    await expect(streamM3U8(mockM3u8Url, mockStoragePath)).rejects.toThrow('Failed to stream playlist file to storage');

     expect(CustomError.medium).toHaveBeenCalledWith('Failed to stream file to storage', {
       originalError: playlistStreamError,
       errorCode: VIDEO_ERRORS.STORAGE_UPLOAD_FAILED,
       shouldRetry: true,
       context: expectedContext,
       source: 'services/videos/helpers/m3u8/index.ts',
+      stack: expect.any(String), // Verify error stack is preserved
     });
src/services/videos/helpers/m3u8/helpers.ts (1)

61-63: Add input validation to parseM3U8Content.

The function should validate the m3u8Url parameter to prevent errors with malformed URLs:

 const parseM3U8Content = async (m3u8Url: string, excludePatterns: RegExp[] = []): Promise<ParsedResult> => {
+  if (!m3u8Url || !m3u8Url.match(/^https?:\/\/.+/)) {
+    throw new Error('Invalid M3U8 URL format');
+  }
   const response = await fetchWithError(m3u8Url);
   const content = await response.text();
src/apps/io/videos/routes/stream-hls/index.test.ts (2)

135-170: Enhance error scenario testing utility.

The testErrorScenario function could verify error details more thoroughly:

 const testErrorScenario = async (
   setupMocks: () => void,
   expectedErrorCode: string,
   expectedErrorMessage: string,
-  checksAfterError?: () => void
+  checksAfterError?: () => void,
+  expectedErrorDetails?: Record<string, any>
 ) => {
   setupMocks();

   let caughtError: Error | null = null;
   try {
     await streamHLSHandler(context.mockRequest, context.mockResponse);
   } catch (error) {
     caughtError = error as Error;
   }

   expect(caughtError).toBeTruthy();
   expect(caughtError?.message).toBe(expectedErrorMessage);
+  expect(caughtError?.stack).toBeTruthy();

   if (expectedErrorCode !== VIDEO_ERRORS.CONVERSION_FAILED) {
     expect(context.mockTransaction.rollback).toHaveBeenCalled();
   }

   // Verify CustomError.critical was called
   const criticalCalls = vi.mocked(CustomError.critical).mock.calls;
   expect(criticalCalls.length).toBeGreaterThan(0);

   // Find the call that matches our error
   const matchingCall = criticalCalls.find(
     call => call[0] === expectedErrorMessage && call[1]?.errorCode === expectedErrorCode
   );
   expect(matchingCall).toBeTruthy();
+  if (expectedErrorDetails) {
+    expect(matchingCall?.[1]).toMatchObject(expectedErrorDetails);
+  }

   expect(context.mockResponse.json).not.toHaveBeenCalled();

   checksAfterError?.();
 };

204-267: Add test coverage for edge cases.

Consider adding test cases for the following scenarios:

it('should handle transaction commit failure', async () => {
  await testErrorScenario(
    () => {
      const expectedPlayableUrl = 'https://example.com/video.m3u8';
      vi.mocked(streamM3U8).mockResolvedValueOnce({
        playlistUrl: expectedPlayableUrl,
        duration: 100,
        thumbnailUrl: 'cloud-storage-url',
      });
      vi.mocked(finalizeVideo).mockResolvedValueOnce(1);
      vi.mocked(completeTask).mockResolvedValueOnce(undefined);
      context.mockTransaction.commit.mockRejectedValueOnce(
        CustomError.critical('Transaction commit failed', {
          errorCode: DATABASE_ERRORS.DB_ERROR,
          shouldRetry: true,
        })
      );
    },
    DATABASE_ERRORS.DB_ERROR,
    'Transaction commit failed'
  );
});

it('should handle concurrent transaction rollbacks', async () => {
  await testErrorScenario(
    () => {
      context.mockTransaction.rollback.mockRejectedValueOnce(new Error('Already rolled back'));
      vi.mocked(finalizeVideo).mockRejectedValueOnce(
        CustomError.critical('Failed to save to database', {
          errorCode: DATABASE_ERRORS.DB_ERROR,
          shouldRetry: true,
        })
      );
    },
    DATABASE_ERRORS.DB_ERROR,
    'Failed to save to database'
  );
});
src/services/videos/helpers/m3u8/helpers.test.ts (1)

48-56: Enhance fetch error test coverage.

Add test cases for different types of fetch errors:

test('should handle network errors', async () => {
  vi.mocked(fetchWithError).mockRejectedValue(new Error('Network error'));
  await expect(parseM3U8Content(baseUrl, excludePatterns)).rejects.toThrow('Network error');
});

test('should handle timeout errors', async () => {
  vi.mocked(fetchWithError).mockRejectedValue(new Error('Request timeout'));
  await expect(parseM3U8Content(baseUrl, excludePatterns)).rejects.toThrow('Request timeout');
});

test('should handle invalid content errors', async () => {
  vi.mocked(fetchWithError).mockResolvedValue({
    text: async () => 'Invalid M3U8 content',
  } as any);
  await expect(parseM3U8Content(baseUrl, excludePatterns)).rejects.toThrow('Invalid playlist format');
});
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7fb2bda and aaa7491.

📒 Files selected for processing (11)
  • src/apps/io/videos/routes/stream-hls/index.test.ts (8 hunks)
  • src/apps/io/videos/routes/stream-hls/index.ts (2 hunks)
  • src/services/videos/helpers/m3u8/helpers.test.ts (12 hunks)
  • src/services/videos/helpers/m3u8/helpers.ts (3 hunks)
  • src/services/videos/helpers/m3u8/index.test.ts (4 hunks)
  • src/services/videos/helpers/m3u8/index.ts (2 hunks)
  • src/utils/custom-error/index.ts (1 hunks)
  • src/utils/error-codes/index.test.ts (1 hunks)
  • src/utils/error-codes/index.ts (2 hunks)
  • src/utils/fetch/index.test.ts (1 hunks)
  • src/utils/fetch/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/services/videos/helpers/m3u8/index.test.ts (1)
Learnt from: shinaBR2
PR: shinaBR2/sworld-backend#54
File: src/services/videos/helpers/thumbnail/index.test.ts:78-85
Timestamp: 2025-02-16T07:46:43.827Z
Learning: In the video processing service tests, error handling verification should focus on core functionality (e.g., operation sequence, error propagation) rather than cleanup operations, letting errors throw naturally.
🔇 Additional comments (22)
src/services/videos/helpers/m3u8/index.ts (4)

50-58: Looks good.

Parsation of the M3U8 content and the accompanying logging statements provide helpful insights for debugging and validation.


60-66: No issues here.

Throwing an error for empty HLS content is an appropriate approach to indicate a malformed or unexpected playlist.


68-85: Implementation looks correct.

Capturing a screenshot from the first included segment and gracefully failing if it cannot be generated aligns with the overall error propagation strategy.


109-115: All set.

Using STORAGE_UPLOAD_FAILED and marking the error as retryable is consistent with the intended error-handling flow.

src/utils/error-codes/index.ts (5)

1-5: Constants defined correctly.

The HTTP_ERRORS object is well-structured for handling different HTTP-related error categories.


7-9: Database error code introduced properly.

Defining DB_ERROR as a dedicated code is a straightforward way to categorize database-related failures.


16-16: Appropriate addition of VIDEO_TAKE_SCREENSHOT_FAILED.

Including a dedicated error code improves clarity around screenshot-related failures.


33-34: Correct typing strategy.

Using mapped types for HttpErrorCode and DbErrorCode leverages the constants while avoiding manual string enumeration.


37-39: Union type export is well-handled.

Extending ErrorCode to include HttpErrorCode and DbErrorCode unifies error messaging across different domains.

src/utils/fetch/index.ts (3)

1-2: Imports look correct.

All necessary types and functions for node-fetch are imported without redundancy.


6-8: Clear type definition.

FetchWrapperOptions properly extends RequestInit while allowing a custom timeout?.


57-57: Correct export.

Exporting fetchWithError provides a concise entry point for the improved fetch mechanism.

src/apps/io/videos/routes/stream-hls/index.ts (3)

16-16: LGTM! Improved logging placement.

Moving the logging statement outside the try-catch block ensures that the event start is always logged, regardless of subsequent errors.


25-26: LGTM! Added transaction management.

Good addition of transaction management to ensure database consistency. The transaction is properly committed on success and rolled back on failure.

Also applies to: 38-38, 42-43


44-48:

✅ Verification successful

Verify error retry strategy.

The error handling now sets shouldRetry: true for database errors. While this is generally a good practice for transient database issues, we should verify if all database errors are truly retryable.


🏁 Script executed:

#!/bin/bash
# Search for other database error handling patterns in the codebase
rg -A 5 "DATABASE_ERRORS.DB_ERROR" --type ts

Length of output: 2098


Database Error Retry Strategy Confirmed

The shouldRetry: true flag for DATABASE_ERRORS.DB_ERROR in src/apps/io/videos/routes/stream-hls/index.ts is consistent with similar handling in the corresponding test file. The tests explicitly expect a retry flag, which confirms the intended error behavior for transient database issues. Please ensure that only truly transient database errors trigger retries and that permanent errors are managed appropriately.

src/utils/error-codes/index.test.ts (3)

5-13: LGTM! Comprehensive HTTP error codes test.

Good test coverage for HTTP error codes, ensuring all expected codes are present.


15-23: LGTM! Database error codes test.

Good test coverage for database error codes, ensuring the expected code is present.


30-30: LGTM! Updated video error codes.

Good addition of VIDEO_TAKE_SCREENSHOT_FAILED to the expected video error codes.

src/utils/fetch/index.test.ts (3)

7-14: LGTM! Well-structured mock setup.

Good mock implementation for node-fetch, providing control over response status and behavior.


46-59: LGTM! Comprehensive error handling tests.

Excellent test coverage for different error scenarios:

  • Network failures (retryable)
  • Server errors (retryable)
  • Client errors (non-retryable)

Each test properly verifies error type, code, and retry flag.

Also applies to: 61-77, 79-95


97-113: LGTM! Edge case handling tests.

Good coverage of edge cases:

  • Preserving existing CustomErrors
  • Handling non-Error objects

Tests ensure consistent error wrapping behavior.

Also applies to: 115-127

src/utils/custom-error/index.ts (1)

23-23:

✅ Verification successful

Verify impact of broadened error type.

The originalError type has been expanded to include unknown. While this provides more flexibility, we should verify that all consumers properly handle unknown error types.


🏁 Script executed:

#!/bin/bash
# Search for CustomError instantiations with originalError
ast-grep --pattern 'new CustomError($_, { $$$, originalError: $_, $$$})'

Length of output: 2607


Expanded originalError type – No immediate issues detected

After verifying the codebase, all current instantiations of CustomError continue to cast or provide a known error type (typically using error as Error, error as CustomError, or instances created via new Error(…)). There are no instances where an error of type unknown is passed directly. While the updated union type increases flexibility, no consumers are presently affected. Please remain mindful to add type-checks or additional handling if, in the future, errors of truly unknown origin are introduced.

  • Location Reviewed:
    • src/utils/fetch/index.ts (usage with error as Error)
    • src/utils/error-handler.test.ts (usage with new Error('Original error'))
    • src/utils/custom-error/index.test.ts (usage with casts to Error or CustomError)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (9)
src/utils/error-codes/index.ts (1)

9-11: Consider expanding database error codes.

While DB_ERROR is a good start, consider adding more specific database error codes like DB_CONNECTION_ERROR, DB_TRANSACTION_ERROR, etc., to provide more granular error handling for database operations.

src/utils/error-codes/index.test.ts (1)

15-23: Consider expanding database error code tests.

While the test is correct, it could be expanded when more specific database error codes are added as suggested in the previous review comment.

src/utils/fetch/index.ts (1)

4-53: Add JSDoc documentation for better maintainability.

The implementation is robust with good error handling, but would benefit from JSDoc documentation to explain the behavior, parameters, return type, and possible errors.

Add this documentation above the function:

/**
 * Fetches a resource with enhanced error handling and timeout support.
 * 
 * @param url - The URL to fetch
 * @param options - Fetch options with optional timeout
 * @param options.timeout - Timeout in milliseconds (default: 5000)
 * @returns Promise resolving to the fetch Response
 * @throws {CustomError} with appropriate error codes:
 *  - HTTP_ERRORS.SERVER_ERROR for 5xx responses (retryable)
 *  - HTTP_ERRORS.CLIENT_ERROR for 4xx responses (non-retryable)
 *  - HTTP_ERRORS.NETWORK_ERROR for network failures (retryable)
 */
src/utils/fetch/index.test.ts (2)

54-55: Improve test readability by removing redundant assertion.

The expect(true).toBeFalsy() assertion is a confusing way to indicate that the code shouldn't reach this point.

Replace with a more descriptive approach:

-      expect(true).toBeFalsy(); // Should not reach here
+      throw new Error('Expected function to throw an error');

138-139: Use a more descriptive error message.

The error message could be more descriptive about the expected behavior.

Replace with a more descriptive message:

-      expect.fail('Should have thrown an error');
+      expect.fail('Expected timeout to trigger a CustomError');
src/services/videos/helpers/m3u8/helpers.ts (2)

220-230: Enhance error context for better debugging.

The error context could include more details about the failure.

Add response status code to the error context:

     throw new CustomError('Failed to fetch segment', {
       errorCode: HTTP_ERRORS.EMPTY_RESPONSE,
       shouldRetry: false,
-      context: { segmentUrl, storagePath, responseStatus: response.statusText },
+      context: { 
+        segmentUrl, 
+        storagePath, 
+        responseStatus: response.statusText,
+        statusCode: response.status 
+      },
     });

252-269: Improve error propagation in batch processing.

The streamSegments function should provide context about which batch failed.

Add batch information to help with debugging:

   for (let i = 0; i < segmentUrls.length; i += concurrencyLimit) {
     const batch = segmentUrls.slice(i, i + concurrencyLimit);
+    const batchNumber = Math.floor(i / concurrencyLimit) + 1;
 
     await Promise.all(
       batch.map(async segmentUrl => {
         const segmentFileName = segmentUrl.split('/').pop();
         const segmentStoragePath = path.join(baseStoragePath, segmentFileName as string);
 
-        await streamSegmentFile(segmentUrl, segmentStoragePath);
+        try {
+          await streamSegmentFile(segmentUrl, segmentStoragePath);
+        } catch (error) {
+          if (error instanceof CustomError) {
+            error.context = { ...error.context, batchNumber, batchSize: batch.length };
+          }
+          throw error;
+        }
       })
     );
   }
src/services/videos/helpers/m3u8/helpers.test.ts (2)

591-595: Use more realistic mock data for better test coverage.

The mock response could better represent real-world scenarios.

Use a more realistic mock:

-    const mockBody = new ReadableStream();
+    const mockBody = new ReadableStream({
+      start(controller) {
+        controller.enqueue(new Uint8Array([0x47, 0x40, 0x00])); // MPEG-TS sync byte and headers
+        controller.close();
+      }
+    });
     (fetchWithError as Mock).mockResolvedValue({
       body: mockBody,
       statusText: 'OK',
+      status: 200,
+      headers: new Headers({
+        'content-type': 'video/MP2T',
+        'content-length': '3'
+      })
     } as Response);

774-804: Improve test description for clarity.

The test description "should log error and throw when segment streaming fails" could be more specific about the failure scenario.

Rename the test for better clarity:

-  test('should log error and throw when segment streaming fails', async () => {
+  test('should stop processing remaining segments when a segment fetch fails with empty response body', async () => {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between aaa7491 and b017484.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • package.json (0 hunks)
  • src/services/videos/helpers/gcp-cloud-storage/index.test.ts (0 hunks)
  • src/services/videos/helpers/m3u8/helpers.test.ts (15 hunks)
  • src/services/videos/helpers/m3u8/helpers.ts (4 hunks)
  • src/utils/error-codes/index.test.ts (1 hunks)
  • src/utils/error-codes/index.ts (2 hunks)
  • src/utils/fetch/index.test.ts (1 hunks)
  • src/utils/fetch/index.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • src/services/videos/helpers/gcp-cloud-storage/index.test.ts
  • package.json
🔇 Additional comments (9)
src/utils/error-codes/index.ts (4)

1-7: LGTM! Comprehensive HTTP error categorization.

The HTTP error codes cover essential network and server-related error scenarios, which will be valuable for the streaming HLS video flow.


18-18: LGTM! Added screenshot error handling.

The new VIDEO_TAKE_SCREENSHOT_FAILED error code aligns well with video processing requirements.


35-39: LGTM! Type-safe error code definitions.

The type definitions using TypeScript's keyof operator ensure type safety and maintainability.


41-41: LGTM! Comprehensive exports.

All necessary types and constants are properly exported.

src/utils/error-codes/index.test.ts (3)

2-2: LGTM! Updated imports.

All necessary error code types and constants are properly imported.


5-13: LGTM! Comprehensive HTTP error code testing.

The test thoroughly validates all HTTP error codes.


30-30: LGTM! Updated video error code test.

The test now includes the new VIDEO_TAKE_SCREENSHOT_FAILED error code.

src/utils/fetch/index.ts (1)

4-11:

✅ Verification successful

Verify the timeout implementation.

The timeout implementation using AbortSignal.timeout() is clean, but we should verify browser compatibility.


🌐 Web query:

Is AbortSignal.timeout() supported in all major browsers? What's the minimum browser version required?

💡 Result:

The AbortSignal.timeout() method is supported in modern versions of major browsers but requires specific minimum versions:

Browser Compatibility Overview

Browser Minimum Supported Version Notes
Chrome 103+ Not supported in Chrome ≤102
Firefox 100+ Not supported in Firefox ≤99
Safari 16.0+ Requires macOS Monterey/iOS 16+
Edge 103+ Not supported in Edge ≤102
Opera 89+ Not supported in Opera ≤88
iOS Safari 16.0+ Requires iPhone/iPad running iOS 16+

Key Details:

  • Mobile Browsers:

    • Chrome for Android and Firefox for Android fully support it[3][4].
    • Samsung Internet requires v20+[3][4].
  • Older Browser Workaround:
    Use AbortController + setTimeout() for unsupported environments[2][5]:

const controller = new AbortController();
setTimeout(() => controller.abort(), 6000);
fetch(url, { signal: controller.signal });
  • Timeout Behavior:
    Automatically triggers a TimeoutError DOMException[1][7], distinct from manual aborts (AbortError)[1][5].

Implementation Example:

// Modern approach (supported in listed browsers)
try {
  const response = await fetch(url, {
    signal: AbortSignal.timeout(5000) // Timeout after 5s
  });
} catch (err) {
  if (err.name === 'TimeoutError') {
    console.error('Request timed out');
  }
}

Browser compatibility data sourced from MDN[1], CanIUse[4], and Chrome release notes[2].

Citations:


Timeout Implementation & Browser Compatibility Verified

The current implementation leveraging AbortSignal.timeout() is valid for modern browsers. Our findings confirm that this method is supported in:

  • Chrome: v103+
  • Firefox: v100+
  • Safari / iOS Safari: v16.0+
  • Edge: v103+
  • Opera: v89+

If you expect users on older browser versions, please consider a fallback (e.g., using AbortController with setTimeout()) to ensure broader compatibility.

src/services/videos/helpers/m3u8/helpers.ts (1)

125-180:

❌ Incorrect review comment

Remove unused code or implement the missing functionality.

The downloadSegments function and its helper chunks are marked as potentially unused with TODO comments.

Let's verify if these functions are used:

Would you like me to help remove this code or implement the missing functionality?


🏁 Script executed:

#!/bin/bash
# Search for usage of downloadSegments function
rg -A 5 "downloadSegments\(" --type ts

Length of output: 2588


Usage verified; update TODO comments if needed.

The downloadSegments function and its helper chunks are actively used—not only in the inline example but also in the tests (see src/services/videos/helpers/m3u8/helpers.test.ts). There’s no indication that this code is unused. Please consider revising or removing the TODO comments to avoid confusion about their role.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/utils/fetch/index.ts (1)

16-65: Consider adding request URL to error context for non-CustomError cases.

The implementation is solid with comprehensive error handling. However, for consistency, consider adding the request URL to the error context when rethrowing CustomError instances.

 if (error instanceof CustomError) {
+  error.context = { ...error.context, url };
   throw error;
 }
src/services/videos/helpers/m3u8/helpers.test.ts (2)

600-668: Consider adding test for network timeout scenario.

The tests cover most error scenarios but are missing a test for network timeout, which is a key feature of the new fetchWithError implementation.

Add this test case:

test('should handle network timeout', async () => {
  const timeoutError = new Error('The operation was aborted');
  timeoutError.name = 'AbortError';
  (fetchWithError as Mock).mockRejectedValue(timeoutError);

  await expect(streamSegmentFile(testUrl, testPath)).rejects.toThrow();

  expect(CustomError).toHaveBeenCalledWith('Request timed out', {
    errorCode: HTTP_ERRORS.NETWORK_ERROR,
    shouldRetry: true,
    context: {
      segmentUrl: testUrl,
      storagePath: testPath,
    },
  });
  expect(streamFile).not.toHaveBeenCalled();
});

758-837: Consider testing retry behavior for server errors.

The tests verify basic error handling but don't test the retry behavior for server errors (500+).

Add this test case:

test('should handle server errors as retryable', async () => {
  const segmentUrls = ['http://example.com/seg-1.ts'];
  const baseStoragePath = 'videos/test';

  (fetchWithError as Mock).mockRejectedValueOnce(
    new CustomError('Server error', {
      errorCode: HTTP_ERRORS.SERVER_ERROR,
      shouldRetry: true,
      context: { url: segmentUrls[0] }
    })
  );

  await expect(
    streamSegments({
      segmentUrls,
      baseStoragePath,
      options: { concurrencyLimit: 1 },
    })
  ).rejects.toMatchObject({
    shouldRetry: true,
    errorCode: HTTP_ERRORS.SERVER_ERROR
  });

  expect(streamFile).not.toHaveBeenCalled();
});
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b017484 and 8e5b377.

📒 Files selected for processing (4)
  • src/services/videos/helpers/m3u8/helpers.test.ts (15 hunks)
  • src/services/videos/helpers/m3u8/helpers.ts (4 hunks)
  • src/utils/fetch/index.test.ts (1 hunks)
  • src/utils/fetch/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/fetch/index.test.ts
  • src/services/videos/helpers/m3u8/helpers.ts
🔇 Additional comments (2)
src/utils/fetch/index.ts (1)

1-15: LGTM! Well-documented function with clear error handling specifications.

The JSDoc documentation clearly outlines the function's behavior, error handling, and return types.

src/services/videos/helpers/m3u8/helpers.test.ts (1)

1-27: LGTM! Well-structured mock implementations.

The mock implementations are comprehensive and correctly set up for testing error scenarios.

@shinaBR2 shinaBR2 merged commit cca534e into main Feb 17, 2025
3 checks passed
@shinaBR2 shinaBR2 deleted the swo-98-backend-determine-retry-for-streaming-hls-video-flow branch February 17, 2025 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant