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

[OTE-881] propagate bazooka failure in auxo #2537

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jerryfan01234
Copy link
Contributor

@jerryfan01234 jerryfan01234 commented Oct 23, 2024

Changelist

refactored support functions to helper.ts for mocking
added unit test
THE ONLY CHANGE to the functions in helper.ts is runDbAndKafkaMigration where function runtime exceptions now throw error

Test Plan

[Describe how this PR was tested (if applicable)]

Author/Reviewer Checklist

  • If this PR has changes that result in a different app state given the same prior state and transaction list, manually add the state-breaking label.
  • If the PR has breaking postgres changes to the indexer add the indexer-postgres-breaking label.
  • If this PR isn't state-breaking but has changes that modify behavior in PrepareProposal or ProcessProposal, manually add the label proposal-breaking.
  • If this PR is one of many that implement a specific feature, manually label them all feature:[feature-name].
  • If you wish to for mergify-bot to automatically create a PR to backport your change to a release branch, manually add the label backport/[branch-name].
  • Manually add any of the following labels: refactor, chore, bug.

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive test suite for the Auxo Handler, enhancing error handling verification.
    • Added several asynchronous functions for managing AWS ECS services and Lambda functions, improving operational capabilities.
  • Bug Fixes

    • Enhanced error handling in the upgrade process for Lambda functions and ECS services.
  • Refactor

    • Streamlined the handler function, reducing complexity by consolidating logic and removing unnecessary helper functions.

@jerryfan01234 jerryfan01234 requested a review from a team as a code owner October 23, 2024 15:03
Copy link

linear bot commented Oct 23, 2024

Copy link
Contributor

coderabbitai bot commented Oct 23, 2024

Walkthrough

The changes in this pull request include the introduction of a new test suite for the Auxo Handler, the addition of a helpers.ts file with multiple asynchronous functions for managing AWS ECS services and Lambda functions, and significant modifications to the index.ts file, including the removal of several AWS SDK imports and functions. The new test suite enhances error handling verification, while the helpers.ts file provides structured management capabilities. The index.ts file has been simplified to streamline the upgrade process by consolidating logic within the handler function.

Changes

File Path Change Summary
indexer/services/auxo/tests/index.test.ts Added a new test suite for the Auxo Handler, removed a previous placeholder test, and added tests for error handling with mocks.
indexer/services/auxo/src/helpers.ts Introduced a new file with multiple asynchronous functions for AWS ECS and Lambda management, including error handling.
indexer/services/auxo/src/index.ts Removed several AWS SDK imports and functions, simplified the handler function, and retained error handling logic.

Suggested reviewers

  • adamfraser

Poem

In the garden where code does bloom,
New tests and helpers dispel the gloom.
With functions to manage, and errors to catch,
The Auxo Handler's ready, a perfect match!
Hops of joy in the code we write,
A rabbit's delight, all feels just right! 🐇✨


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 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
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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (6)
indexer/services/auxo/__tests__/index.test.ts (5)

11-20: Consider adding type safety to the Lambda client mock.

While the mock setup is functionally correct, it could benefit from explicit typing to prevent potential type-related issues.

 jest.mock('@aws-sdk/client-lambda', () => {
   const originalModule = jest.requireActual('@aws-sdk/client-lambda');
   return {
     ...originalModule,
-    LambdaClient: jest.fn(() => ({
+    LambdaClient: jest.fn().mockImplementation(() => ({
       send: mockClientSend,
-    })),
+    })) as jest.MockedClass<typeof originalModule.LambdaClient>,
     InvokeCommand: originalModule.InvokeCommand,
   };
 });

23-34: Enhance logger mock coverage.

The logger mock is missing some common methods, and there's no verification of mock calls in the test.

 jest.mock('@dydxprotocol-indexer/base', () => {
   const actualModule = jest.requireActual('@dydxprotocol-indexer/base');
   return {
     ...actualModule,
     logger: {
       info: jest.fn(),
       error: jest.fn(),
       crit: jest.fn(),
+      warn: jest.fn(),
+      debug: jest.fn(),
     },
     startBugsnag: jest.fn(),
   };
 });

Consider adding expectations to verify logger calls in your test case:

expect(logger.error).toHaveBeenCalledWith(
  expect.stringContaining('Bazooka Lambda error'),
  expect.any(Error)
);

37-40: Improve helper mock implementation.

The current mock setup for upgradeBazooka could be enhanced for better test coverage and type safety.

 jest.mock('../src/helpers', () => ({
   ...jest.requireActual('../src/helpers'),
-  upgradeBazooka: jest.fn().mockResolvedValue(undefined),
+  upgradeBazooka: jest.fn().mockImplementation(async () => {
+    // Mock realistic behavior
+    return Promise.resolve({
+      status: 'SUCCESS',
+      message: 'Bazooka upgrade completed',
+    });
+  }),
 }));

42-46: Enhance test suite setup with proper cleanup.

Consider adding afterAll cleanup and importing types for better maintainability.

+import { upgradeBazooka } from '../src/helpers';
+import { logger } from '@dydxprotocol-indexer/base';

 describe('Auxo Handler', () => {
   beforeEach(() => {
     jest.clearAllMocks();
   });
+  
+  afterAll(() => {
+    jest.restoreAllMocks();
+  });
 });

1-87: Consider architectural improvements for better testability.

While the test implementation is functional, consider these architectural improvements:

  1. The tight coupling with AWS services makes testing more complex. Consider introducing an abstraction layer for AWS services.
  2. The test doesn't fully verify the integration with the newly refactored helper.ts.

Consider introducing a service interface:

interface CloudServiceProvider {
  invokeLambda(functionName: string, payload: any): Promise<any>;
  // Add other AWS service methods as needed
}

class AWSCloudService implements CloudServiceProvider {
  constructor(private lambdaClient: LambdaClient) {}
  
  async invokeLambda(functionName: string, payload: any): Promise<any> {
    // Implementation using AWS SDK
  }
}

This would make the handler more testable and the tests simpler:

class MockCloudService implements CloudServiceProvider {
  async invokeLambda(functionName: string, payload: any): Promise<any> {
    // Simple mock implementation
  }
}
indexer/services/auxo/src/helpers.ts (1)

95-96: Use existing sleep function for consistency

Instead of creating a new promise with setTimeout, utilize the already defined sleep function for better code reuse and readability.

Apply this diff:

-    await new Promise((resolve) => setTimeout(resolve, 5000));
+    await sleep(5000);
🛑 Comments failed to post (5)
indexer/services/auxo/__tests__/index.test.ts (1)

48-87: ⚠️ Potential issue

Improve test coverage and error handling verification.

The current test case could be enhanced in several ways:

  1. The mock context should include required properties

  2. The error assertion should be more specific

  3. Missing positive test cases

  4. Enhance the context mock:

-    const mockContext: Context = {} as any; // Mocked Context object
+    const mockContext: Context = {
+      awsRequestId: 'mock-request-id',
+      functionName: 'auxo-handler',
+      functionVersion: '1',
+      invokedFunctionArn: 'mock-arn',
+      memoryLimitInMB: '128',
+      callbackWaitsForEmptyEventLoop: true,
+      getRemainingTimeInMillis: () => 1000,
+      done: () => {},
+      fail: () => {},
+      succeed: () => {},
+    };
  1. Make the error assertion more specific:
-    await expect(handler(mockEvent, mockContext)).rejects.toThrow();
+    await expect(handler(mockEvent, mockContext)).rejects.toThrow(
+      'Bazooka Lambda execution failed: Some bazooka error'
+    );
  1. Add a positive test case:
  it('should successfully handle Bazooka Lambda execution', async () => {
    mockClientSend.mockResolvedValueOnce({
      StatusCode: 200,
      Payload: Buffer.from(JSON.stringify({ status: 'success' })),
      $metadata: {
        httpStatusCode: 200,
        requestId: 'mock-request-id-success',
      },
    } as InvokeCommandOutput);

    const result = await handler(mockEvent, mockContext);
    expect(result).toEqual(expect.objectContaining({
      statusCode: 200,
      body: expect.stringContaining('success'),
    }));
  });
indexer/services/auxo/src/helpers.ts (4)

65-68: ⚠️ Potential issue

Handle timeout in upgradeBazooka to ensure upgrade completion

If the sleep(120000) promise resolves before checkLambdaStatus(lambda), the upgradeBazooka function will complete without confirming whether the Lambda function was successfully updated. This may lead to an incomplete upgrade without any error notification.

Apply this diff to handle the timeout scenario:

 await Promise.race([
   checkLambdaStatus(lambda),
   sleep(120000),
 ]);
+throw new Error('Timed out waiting for Lambda function to update');

This modification ensures that an error is thrown if the Lambda update does not complete within the expected time frame.

Committable suggestion was skipped due to low confidence.


259-260: ⚠️ Potential issue

Use a robust method to construct the new image URI

The current approach _.split(originalImage, ':')[0] assumes there's only one colon in the image URI, which might not be the case if the image includes a registry URL with a port or digest. This can result in incorrect image URIs.

Consider parsing the image URI more reliably. Apply this diff:

-  const newImage: string = `${_.split(originalImage, ':')[0]}:${event.upgrade_tag}`;
+  const imageParts = originalImage.split('@');
+  const imageWithoutTag = imageParts[0].split(':')[0];
+  const newImage: string = `${imageWithoutTag}:${event.upgrade_tag}`;

This modification ensures that the new image URI is correctly constructed, even if the original image URI contains multiple colons or an image digest.

📝 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.

  const imageParts = originalImage.split('@');
  const imageWithoutTag = imageParts[0].split(':')[0];
  const newImage: string = `${imageWithoutTag}:${event.upgrade_tag}`;
  updatedContainerDefinitions[serviceContainerDefinitionIndex].image = newImage;

143-143: ⚠️ Potential issue

Correct the parameter type for lambda in runDbAndKafkaMigration

The lambda parameter is currently typed as ECRClient, but it should be LambdaClient since it invokes a Lambda function.

Apply this diff to fix the parameter type:

 export async function runDbAndKafkaMigration(
   createNewKafkaTopics: boolean,
-  lambda: ECRClient,
+  lambda: LambdaClient,
 ): Promise<void> {

Committable suggestion was skipped due to low confidence.


334-369: 🛠️ Refactor suggestion

Utilize AWS SDK waiters instead of custom polling

The waitForTaskDefinitionToRegister function implements custom polling logic to check if the task definition is registered. AWS SDK provides built-in waiters that can simplify this process and handle retries more efficiently.

Consider using the AWS SDK's waiter:

 async function waitForTaskDefinitionToRegister(
   ecs: ECSClient,
   registerResult: RegisterTaskDefinitionCommandOutput,
 ): Promise<void> {
-  const taskDefinition = `${registerResult.taskDefinition!.family}:${registerResult.taskDefinition!.revision}`;
-  for (let i = 0; i <= config.MAX_TASK_DEFINITION_WAIT_TIME_MS; i += config.SLEEP_TIME_MS) {
-    const describeResult = await ecs.send(
-      new DescribeTaskDefinitionCommand({
-        taskDefinition,
-      }),
-    );
-    if (describeResult.taskDefinition !== undefined) {
-      logger.info({
-        at: 'index#waitForTaskDefinitionToRegister',
-        message: 'Task definition registered',
-        taskDefinition,
-        describeResult,
-      });
-      return;
-    }
-    logger.info({
-      at: 'index#waitForTaskDefinitionToRegister',
-      message: `Task definition is undefined, sleeping ${config.SLEEP_TIME_MS}ms`,
-    });
-    await sleep(config.SLEEP_TIME_MS);
-  }
-  logger.error({
-    at: 'index#waitForTaskDefinitionToRegister',
-    message: 'Timed out waiting for task definition to register',
-    taskDefinition,
-  });
-  throw new Error('Timed out waiting for task definition to register');
+  await ecs.waitFor('tasksRegistered', {
+    taskDefinition: registerResult.taskDefinition!.taskDefinitionArn!,
+    $waiter: {
+      maxAttempts: config.MAX_TASK_DEFINITION_WAIT_TIME_MS / config.SLEEP_TIME_MS,
+      delay: config.SLEEP_TIME_MS / 1000,
+    },
+  });
+  logger.info({
+    at: 'index#waitForTaskDefinitionToRegister',
+    message: 'Task definition registered',
+  });
 }

This refactor leverages the SDK's built-in functionality for waiting on resource states, improving code clarity and reliability.

Committable suggestion was skipped due to low confidence.

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: 8

🧹 Outside diff range and nitpick comments (5)
indexer/services/auxo/__tests__/index.test.ts (1)

22-34: Consider adding type safety to logger mocks.

While the mocking implementation is functionally correct, consider adding TypeScript types to ensure type safety.

 jest.mock('@dydxprotocol-indexer/base', () => {
   const actualModule = jest.requireActual('@dydxprotocol-indexer/base');
+  type LoggerFn = (message: string, metadata?: object) => void;
   return {
     ...actualModule,
     logger: {
-      info: jest.fn(),
-      error: jest.fn(),
-      crit: jest.fn(),
+      info: jest.fn<void, Parameters<LoggerFn>>(),
+      error: jest.fn<void, Parameters<LoggerFn>>(),
+      crit: jest.fn<void, Parameters<LoggerFn>>(),
     },
     startBugsnag: jest.fn(),
   };
 });
indexer/services/auxo/src/helpers.ts (4)

146-148: Update the 'at' field in logging to reflect the correct function name

The logger.info statement uses 'at': 'index#runDbMigration', but the function is now named runDbAndKafkaMigration in helpers.ts. Updating the 'at' field improves log clarity and maintainability.

Apply this diff to update the 'at' field:

 logger.info({
-  at: 'index#runDbMigration',
+  at: 'helpers#runDbAndKafkaMigration',
   message: 'Running db migration',
 });

164-167: Update the 'at' field in error logging to reflect the correct function name

The logger.error statement references 'at': 'index#runDbMigration'. Update it to match the current function name and file for accurate logging.

Apply this diff to update the 'at' field:

 logger.error({
-  at: 'index#runDbMigration',
+  at: 'helpers#runDbAndKafkaMigration',
   message: `Failed to run db migration due to bazooka failure: ${response.FunctionError}`,
 });

85-89: Update the 'at' field in logging to reflect the new file and function name

The logger.info statement uses 'at': 'index#upgradeBazooka', but since this function is now in helpers.ts, updating the 'at' value enhances clarity.

Apply this diff to update the 'at' field:

 logger.info({
-  at: 'index#upgradeBazooka',
+  at: 'helpers#checkLambdaStatus',
   message: 'Successfully upgraded bazooka',
   response: statusResponse,
 });

51-54: Update the 'at' field in logging to reflect the new file and function name

The logger.info statement references 'at': 'index#upgradeBazooka'. Updating this to reflect the current file and function improves maintainability.

Apply this diff to update the 'at' field:

 logger.info({
-  at: 'index#upgradeBazooka',
+  at: 'helpers#upgradeBazooka',
   message: `Upgrading bazooka to ${imageUri}`,
 });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 3dd45d7 and 45b90f8.

📒 Files selected for processing (3)
  • indexer/services/auxo/tests/index.test.ts (1 hunks)
  • indexer/services/auxo/src/helpers.ts (1 hunks)
  • indexer/services/auxo/src/index.ts (1 hunks)
🧰 Additional context used
🔇 Additional comments (5)
indexer/services/auxo/__tests__/index.test.ts (2)

9-20: LGTM: AWS SDK mocking setup is well-structured.

The mock implementation correctly preserves the original module while allowing controlled testing of Lambda responses.


42-46: LGTM: Clean test setup.

Proper test isolation is maintained by clearing mocks before each test.

indexer/services/auxo/src/index.ts (2)

1-3: AWS SDK Clients Imported Correctly

The imports for ECRClient, ECSClient, and LambdaClient from the AWS SDK are appropriate and reflect the refactoring changes. Importing only the necessary clients improves code efficiency and maintainability.


8-10: Helper Functions Imported Properly from 'helpers.ts'

The functions upgradeBazooka, runDbAndKafkaMigration, createNewEcsTaskDefinitions, and upgradeEcsServices are correctly imported from ./helpers. This aligns with the refactoring objective to modularize support functions, enhancing code organization and testability.

indexer/services/auxo/src/helpers.ts (1)

259-259: Verify the correctness of the new image URI construction

The current method of constructing newImage may not handle images with complex tags or digests. Ensure that the new image URI correctly targets the desired image.

Run the following script to check all image URIs:

✅ Verification successful

Let me analyze the code further by examining the context around this image URI construction.

This will help us understand:

  1. The context around how image URIs are processed
  2. If there are test cases validating this behavior
  3. The patterns of Docker image URIs used in the codebase

Let me search for more specific context around this image handling code.

This will help us understand:

  1. The complete context of the image URI transformation
  2. The event structure that provides the upgrade_tag
  3. Any test cases that validate this behavior

Based on the gathered context, I can now provide a final response:

The image URI construction is correct for the intended use case

The code is part of the Auxo service which handles ECS task definition updates. The image URI construction is appropriate because:

  • The original image comes from an ECS container definition, which follows standard Docker image format
  • The code only replaces the tag portion after the colon, preserving the repository path
  • The upgrade_tag is a validated field from the AuxoEventJson interface
  • The implementation is used in a controlled deployment process where the image format is standardized

The current implementation is sufficient for handling AWS ECR image URIs which follow the pattern: [registry]/[repository]:[tag]

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the new image URIs are correctly formed.

# Extract and display the image URIs from the task definitions.
aws ecs describe-task-definitions --query 'taskDefinitionArns[*]' --output text | while read -r arn; do
  aws ecs describe-task-definition --task-definition "$arn" --query 'taskDefinition.containerDefinitions[*].image' --output text
done

Length of output: 166


Script:

#!/bin/bash
# Search for patterns of Docker image URI handling
rg -A 5 -B 5 "originalImage.*split.*:" 

# Look for test cases related to image URI handling
fd -e test.ts -e spec.ts -X rg -l "originalImage|newImage"

# Search for any Docker image URI patterns in the codebase
rg "docker.*:|registry.*:|gcr\.io|amazonaws\.com"

Length of output: 9758


Script:

#!/bin/bash
# Get more context around the image URI construction
rg -B 10 -A 10 "newImage.*split.*originalImage" indexer/

# Look for the event type and structure
ast-grep --pattern 'interface $_ {
  $$$
  upgrade_tag: $_
  $$$
}'

# Find any related test cases specifically in auxo service
fd -e test.ts -e spec.ts -p "auxo" -X cat

Length of output: 4967

Comment on lines 48 to 87
it('should return 500 when Bazooka Lambda errors', async () => {
mockClientSend.mockResolvedValueOnce({
StatusCode: 500,
FunctionError: 'Some bazooka error',
$metadata: {
httpStatusCode: 200, // api returns 200 even if lambda runtime error
requestId: 'mock-request-id-invoke',
extendedRequestId: 'mock-extended-request-id-invoke',
cfId: 'mock-cf-id-invoke',
attempts: 1,
totalRetryDelay: 0,
},
} as InvokeCommandOutput);

const mockEvent: APIGatewayEvent & AuxoEventJson = {
// APIGatewayEvent properties
body: null,
headers: {},
multiValueHeaders: {},
httpMethod: 'POST',
isBase64Encoded: false,
path: '/deploy',
pathParameters: null,
queryStringParameters: null,
multiValueQueryStringParameters: null,
stageVariables: null,
resource: '',
requestContext: {} as any,
// AuxoEventJson properties
upgrade_tag: 'some_tag',
prefix: 'some_prefix',
region: 'us-east-1',
regionAbbrev: 'us-east-1',
addNewKafkaTopics: false,
onlyRunDbMigrationAndCreateKafkaTopics: false,
};

const mockContext: Context = {} as any;

await expect(handler(mockEvent, mockContext)).rejects.toThrow();
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance test robustness and clarity.

While the test covers the error path, consider these improvements:

  1. Make the test name more specific about the failure scenario
  2. Verify the specific error message or type
  3. Provide a more realistic mock context
-  it('should return 500 when Bazooka Lambda errors', async () => {
+  it('should throw an error when Bazooka Lambda returns a runtime error with status 500', async () => {
     mockClientSend.mockResolvedValueOnce({
       StatusCode: 500,
       FunctionError: 'Some bazooka error',
       $metadata: {
         httpStatusCode: 200,
         requestId: 'mock-request-id-invoke',
         extendedRequestId: 'mock-extended-request-id-invoke',
         cfId: 'mock-cf-id-invoke',
         attempts: 1,
         totalRetryDelay: 0,
       },
     } as InvokeCommandOutput);

     const mockEvent: APIGatewayEvent & AuxoEventJson = {
       // ... event properties ...
     };

-    const mockContext: Context = {} as any;
+    const mockContext: Context = {
+      awsRequestId: 'mock-request-id',
+      functionName: 'auxo-handler',
+      functionVersion: '1',
+      invokedFunctionArn: 'arn:aws:lambda:us-east-1:mock',
+      memoryLimitInMB: '128',
+      logGroupName: '/aws/lambda/auxo-handler',
+      logStreamName: '2024/01/01/[$LATEST]mock',
+      callbackWaitsForEmptyEventLoop: true,
+      getRemainingTimeInMillis: () => 30000,
+      done: () => {},
+      fail: () => {},
+      succeed: () => {},
+    };

-    await expect(handler(mockEvent, mockContext)).rejects.toThrow();
+    await expect(handler(mockEvent, mockContext)).rejects.toThrow(
+      'Bazooka Lambda function failed with error: Some bazooka error'
+    );
   });

Also consider adding test cases for:

  • Other error scenarios (e.g., network errors)
  • Successful execution path
  • Edge cases with different event properties

Committable suggestion was skipped due to low confidence.

Comment on lines 1 to 87
// handler.test.ts

import { handler } from '../src';

import { InvokeCommandOutput } from '@aws-sdk/client-lambda';
import { APIGatewayEvent, Context } from 'aws-lambda';
import { AuxoEventJson } from 'src/types';

// Mock aws clients from AWS SDK
const mockClientSend = jest.fn();
jest.mock('@aws-sdk/client-lambda', () => {
const originalModule = jest.requireActual('@aws-sdk/client-lambda');
return {
...originalModule,
LambdaClient: jest.fn(() => ({
send: mockClientSend,
})),
InvokeCommand: originalModule.InvokeCommand,
};
});

// Mock logger and startBugsnag from @dydxprotocol-indexer/base
jest.mock('@dydxprotocol-indexer/base', () => {
const actualModule = jest.requireActual('@dydxprotocol-indexer/base');
return {
...actualModule, // Spread the actual module's exports
logger: {
info: jest.fn(),
error: jest.fn(),
crit: jest.fn(),
},
startBugsnag: jest.fn(),
};
});

// Mock upgradeBazooka to do nothing
jest.mock('../src/helpers', () => ({
...jest.requireActual('../src/helpers'),
upgradeBazooka: jest.fn().mockResolvedValue(undefined),
}));

describe('Auxo Handler', () => {
beforeEach(() => {
// Reset all mocks before each test
jest.clearAllMocks();
});

it('should return 500 when Bazooka Lambda errors', async () => {
mockClientSend.mockResolvedValueOnce({
StatusCode: 500,
FunctionError: 'Some bazooka error',
$metadata: {
httpStatusCode: 200, // api returns 200 even if lambda runtime error
requestId: 'mock-request-id-invoke',
extendedRequestId: 'mock-extended-request-id-invoke',
cfId: 'mock-cf-id-invoke',
attempts: 1,
totalRetryDelay: 0,
},
} as InvokeCommandOutput);

const mockEvent: APIGatewayEvent & AuxoEventJson = {
// APIGatewayEvent properties
body: null,
headers: {},
multiValueHeaders: {},
httpMethod: 'POST',
isBase64Encoded: false,
path: '/deploy',
pathParameters: null,
queryStringParameters: null,
multiValueQueryStringParameters: null,
stageVariables: null,
resource: '',
requestContext: {} as any,
// AuxoEventJson properties
upgrade_tag: 'some_tag',
prefix: 'some_prefix',
region: 'us-east-1',
regionAbbrev: 'us-east-1',
addNewKafkaTopics: false,
onlyRunDbMigrationAndCreateKafkaTopics: false,
};

const mockContext: Context = {} as any;

await expect(handler(mockEvent, mockContext)).rejects.toThrow();
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the findings, I can now generate a final response.

Test coverage needed for runDbAndKafkaMigration and early return path

The implementation shows that runDbAndKafkaMigration has important logic for handling both database migrations and Kafka topic creation, but there's no test coverage for:

  • The case where createNewKafkaTopics is true
  • The early return path when onlyRunDbMigrationAndCreateKafkaTopics is true

Suggested test cases to add:

  • Test successful database migration with Kafka topic creation (createNewKafkaTopics: true)
  • Test the early return path when onlyRunDbMigrationAndCreateKafkaTopics: true
  • Test error handling when Kafka topic creation fails
🔗 Analysis chain

Verify test coverage for runDbAndKafkaMigration changes.

The PR mentions changes to runDbAndKafkaMigration, but the current test file focuses only on Bazooka failure. Additional test coverage might be needed.


Let me try a different search strategy to verify the test coverage.


Let me check the implementation and test coverage more specifically.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing tests related to runDbAndKafkaMigration

echo "Searching for runDbAndKafkaMigration tests:"
rg "runDbAndKafkaMigration" --type ts --type test -C 3

echo -e "\nChecking implementation changes:"
ast-grep --pattern 'async function runDbAndKafkaMigration($_) {
  $$$
}'

Length of output: 336


Script:

#!/bin/bash
# Search for runDbAndKafkaMigration in all TypeScript files
echo "Searching for runDbAndKafkaMigration in TypeScript files:"
rg "runDbAndKafkaMigration" --type ts -C 3

# Search for test files in the auxo service
echo -e "\nListing test files in auxo service:"
fd "\.test\.ts$" indexer/services/auxo

Length of output: 1737


Script:

#!/bin/bash
# Check the implementation of runDbAndKafkaMigration
echo "Checking runDbAndKafkaMigration implementation:"
cat indexer/services/auxo/src/helpers.ts

# Check test coverage for the case where onlyRunDbMigrationAndCreateKafkaTopics is true
echo -e "\nChecking test coverage for onlyRunDbMigrationAndCreateKafkaTopics:"
rg "onlyRunDbMigrationAndCreateKafkaTopics.*true" indexer/services/auxo/__tests__/index.test.ts -C 5

Length of output: 15416

Comment on lines 36 to 40
// Mock upgradeBazooka to do nothing
jest.mock('../src/helpers', () => ({
...jest.requireActual('../src/helpers'),
upgradeBazooka: jest.fn().mockResolvedValue(undefined),
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Now I have enough information to generate the final response. The helpers.ts file shows several exported functions that need test coverage, and the current test file only mocks upgradeBazooka.

Additional test coverage needed for helper functions

The current test only mocks upgradeBazooka, but there are other critical exported functions that should be tested:

  • runDbAndKafkaMigration: Handles database migrations and Kafka topic creation
  • createNewEcsTaskDefinitions: Creates new ECS task definitions
  • upgradeEcsServices: Upgrades ECS services with new task definitions

These functions are crucial for the deployment process and should have proper test coverage to ensure reliable infrastructure updates.

🔗 Analysis chain

Verify test coverage for all helper functions.

Given that support functions were refactored into helper.ts, we should ensure adequate test coverage for all critical helper functions, not just upgradeBazooka.


Let me generate new shell scripts to find the correct helper file and its functions since the previous search found helpers from a different service (comlink).


Let me check the actual contents of the helpers.ts file to see all exported functions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for exported functions in helper.ts that might need test coverage

# Find all exported functions in helper.ts
echo "Exported functions in helper.ts:"
ast-grep --pattern 'export async function $_($_) {
  $$$
}'

# Check for existing test coverage
echo -e "\nExisting test coverage:"
rg "mock(Resolved|Rejected)Value.*helper" -A 2

Length of output: 5902


Script:

#!/bin/bash
# First, let's find the correct helper file
echo "Looking for helper file in auxo service:"
fd "helper" indexer/services/auxo/

# Then, let's examine its contents and exported functions
echo -e "\nExported functions in auxo helper file:"
ast-grep --pattern 'export $_ function $_($_) {
  $$$
}' --lang typescript

# Check test coverage specifically in auxo tests
echo -e "\nTest coverage in auxo tests:"
rg "mock\(" indexer/services/auxo/__tests__/ -A 2

Length of output: 1275


Script:

#!/bin/bash
# Get the contents of the helpers file
echo "Contents of helpers.ts:"
cat indexer/services/auxo/src/helpers.ts

# Also check for any other test files that might be testing helper functions
echo -e "\nOther test files testing helpers:"
rg "helpers" indexer/services/auxo/__tests__/ -l

Length of output: 15294

Comment on lines +222 to +227
logger.info({
at: 'index#createNewEcsTaskDefinition',
message: 'Got existing ECS Task Definition',
taskDefinitionName,
describeResult,
});
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

Avoid logging extensive task definition details

Logging the entire describeResult can be excessive. To enhance performance and security, log only necessary information.

Apply this diff to refine the logged data:

 logger.info({
   at: 'index#createNewEcsTaskDefinition',
   message: 'Got existing ECS Task Definition',
   taskDefinitionName,
-  describeResult,
+  taskDefinitionArn: describeResult.taskDefinition?.taskDefinitionArn,
 });
📝 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
logger.info({
at: 'index#createNewEcsTaskDefinition',
message: 'Got existing ECS Task Definition',
taskDefinitionName,
describeResult,
});
logger.info({
at: 'index#createNewEcsTaskDefinition',
message: 'Got existing ECS Task Definition',
taskDefinitionName,
taskDefinitionArn: describeResult.taskDefinition?.taskDefinitionArn,
});

Comment on lines +119 to +125
logger.info({
at: 'index#getImageDetail',
message: 'Successfully got ecr images',
images,
repositoryName,
event,
});
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

Limit logging of large data structures to prevent performance issues

Logging the entire images object can impact performance and may expose sensitive information. Consider logging only essential details.

Apply this diff to log relevant information:

 logger.info({
   at: 'index#getImageDetail',
   message: 'Successfully got ecr images',
-  images,
+  imageDigests: images.imageDetails?.map(detail => detail.imageDigest),
   repositoryName,
   event,
 });
📝 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
logger.info({
at: 'index#getImageDetail',
message: 'Successfully got ecr images',
images,
repositoryName,
event,
});
logger.info({
at: 'index#getImageDetail',
message: 'Successfully got ecr images',
imageDigests: images.imageDetails?.map(detail => detail.imageDigest),
repositoryName,
event,
});

indexer/services/auxo/src/helpers.ts Outdated Show resolved Hide resolved
}

// Wait for 5s before checking again
await new Promise((resolve) => setTimeout(resolve, 5000));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use the 'sleep' helper function instead of duplicating sleep logic

In checkLambdaStatus, you're manually implementing a sleep with setTimeout. Since a sleep helper function is already defined, consider using it for consistency and code reuse.

Apply this diff to use the sleep function:

-    await new Promise((resolve) => setTimeout(resolve, 5000));
+    await sleep(5000);

Committable suggestion was skipped due to low confidence.

Comment on lines +256 to +260
const updatedContainerDefinitions: ContainerDefinition[] = _.cloneDeep(
taskDefinition.containerDefinitions!,
);
const newImage: string = `${_.split(originalImage, ':')[0]}:${event.upgrade_tag}`;
updatedContainerDefinitions[serviceContainerDefinitionIndex].image = newImage;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure image tag parsing is robust

Splitting the image string using _.split(originalImage, ':')[0] may not handle all image URI formats, such as those with port numbers or digest references. Consider using a more reliable method to construct the new image URI.

Apply this diff to improve image URI handling:

 const originalImage: string = serviceContainerDefinition.image;
-const newImage: string = `${_.split(originalImage, ':')[0]}:${event.upgrade_tag}`;
+const imageParts = originalImage.split('/');
+const imageNameWithTag = imageParts.pop()?.split(':')[0];
+const newImage = `${imageParts.join('/')}/${imageNameWithTag}:${event.upgrade_tag}`;
 updatedContainerDefinitions[serviceContainerDefinitionIndex].image = newImage;

Committable suggestion was skipped due to low confidence.

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: 3

🧹 Outside diff range and nitpick comments (3)
indexer/services/auxo/__tests__/index.test.ts (1)

22-26: Enhance test suite structure.

Consider adding:

  1. afterEach/afterAll cleanup to restore mocks
  2. Nested describe blocks for different scenarios (e.g., "error cases", "success cases")
 describe('Auxo Handler', () => {
   beforeEach(() => {
     jest.clearAllMocks();
   });
+
+  afterEach(() => {
+    jest.restoreAllMocks();
+  });
+
+  describe('error cases', () => {
+    // Move error test cases here
+  });
+
+  describe('success cases', () => {
+    // Add success test cases here
+  });
 });
indexer/services/auxo/src/index.ts (2)

Line range hint 17-17: Remove unnecessary ESLint directive.

The // eslint-disable-next-line @typescript-eslint/require-await comment is unnecessary since the handler function contains await expressions.

Apply this diff to remove the unnecessary ESLint directive:

-// eslint-disable-next-line @typescript-eslint/require-await

Line range hint 28-30: Enhance error logging for better traceability.

Consider including additional context from the event object in the error log to facilitate debugging.

Modify the error logging as follows:

 logger.error({
   at: 'index#handler',
   message: 'Error upgrading services',
+  event,
   error,
 });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 45b90f8 and 004ffc5.

📒 Files selected for processing (3)
  • indexer/services/auxo/tests/index.test.ts (1 hunks)
  • indexer/services/auxo/src/helpers.ts (1 hunks)
  • indexer/services/auxo/src/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • indexer/services/auxo/src/helpers.ts
🧰 Additional context used
🔇 Additional comments (3)
indexer/services/auxo/src/index.ts (3)

1-3: AWS SDK Client imports are correctly added.

The imports for ECRClient, ECSClient, and LambdaClient from the AWS SDK are appropriately added to interact with AWS services.


10-10: Confirm 'AuxoEventJson' includes all required properties.

Ensure that the AuxoEventJson interface includes region, addNewKafkaTopics, and onlyRunDbMigrationAndCreateKafkaTopics as they are used in the handler function.

Run the following script to verify that AuxoEventJson includes the necessary properties:

✅ Verification successful

Let me try a different approach to find the interface definition.


AuxoEventJson interface has all the required properties

The interface definition in indexer/services/auxo/src/types.ts includes all the properties mentioned in the review comment:

  • region: string
  • addNewKafkaTopics: boolean
  • onlyRunDbMigrationAndCreateKafkaTopics: boolean

Additionally, it includes other properties:

  • upgrade_tag: string
  • prefix: string
  • regionAbbrev: string
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that 'AuxoEventJson' interface includes specific properties.

# Test: Search for 'AuxoEventJson' interface definition with required properties.
ast-grep --pattern $'interface AuxoEventJson {
  region: $_;
  addNewKafkaTopics: $_;
  onlyRunDbMigrationAndCreateKafkaTopics: $_;
  $$$
}' --lang ts

Length of output: 151


Script:

#!/bin/bash
# Search for AuxoEventJson interface definition
rg "interface AuxoEventJson" -A 10

# Also search for type definition as an alternative
rg "type AuxoEventJson" -A 10

Length of output: 768


8-9: Ensure all helper functions are properly exported from 'helpers.ts'.

Verify that helpers.ts exports upgradeBazooka, runDbAndKafkaMigration, createNewEcsTaskDefinitions, and upgradeEcsServices to prevent import errors.

Run the following script to confirm that all required functions are exported from helpers.ts:

✅ Verification successful

Let me try a different approach to verify these exports.

This will help us:

  1. Locate the helpers.ts file
  2. Search for export statements of these specific functions with some context

All required helper functions are properly exported

The verification results show that all four functions (upgradeBazooka, runDbAndKafkaMigration, createNewEcsTaskDefinitions, and upgradeEcsServices) are correctly exported as async functions in indexer/services/auxo/src/helpers.ts. The imports in index.ts will work as expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that 'helpers.ts' exports the necessary functions.

# Test: Search for export statements in 'helpers.ts' that include the required functions.
ast-grep --pattern $'export async function upgradeBazooka($$$) {
  $$$
}
export async function runDbAndKafkaMigration($$$) {
  $$$
}
export async function createNewEcsTaskDefinitions($$$) {
  $$$
}
export async function upgradeEcsServices($$$) {
  $$$
}' --lang ts

Length of output: 920


Script:

#!/bin/bash
# First, let's find the helpers.ts file
fd helpers.ts

# Then, let's examine its content focusing on export statements
rg "export.*function (upgradeBazooka|runDbAndKafkaMigration|createNewEcsTaskDefinitions|upgradeEcsServices)" -A 1

Length of output: 2802

Comment on lines +27 to +72
it('should return 500 when Bazooka Lambda errors', async () => {
// Mock upgradeBazooka to do nothing
jest.spyOn(helpers, 'upgradeBazooka').mockResolvedValue(undefined);

// Mock LambdaClient.send to return response with FunctionError
jest.spyOn(LambdaClient.prototype, 'send').mockImplementation(() => {
return {
StatusCode: 500,
FunctionError: 'Some bazooka error',
$metadata: {
httpStatusCode: 200, // api returns 200 even if lambda runtime error
requestId: 'mock-request-id-invoke',
extendedRequestId: 'mock-extended-request-id-invoke',
cfId: 'mock-cf-id-invoke',
attempts: 1,
totalRetryDelay: 0,
},
} as InvokeCommandOutput;
});

const mockEvent: APIGatewayEvent & AuxoEventJson = {
// APIGatewayEvent properties
body: null,
headers: {},
multiValueHeaders: {},
httpMethod: 'POST',
isBase64Encoded: false,
path: '/deploy',
pathParameters: null,
queryStringParameters: null,
multiValueQueryStringParameters: null,
stageVariables: null,
resource: '',
requestContext: {} as any,
// AuxoEventJson properties
upgrade_tag: 'some_tag',
prefix: 'some_prefix',
region: 'us-east-1',
regionAbbrev: 'us-east-1',
addNewKafkaTopics: false,
onlyRunDbMigrationAndCreateKafkaTopics: false,
};

const mockContext: Context = {} as any;

await expect(handler(mockEvent, mockContext)).rejects.toThrow('bazooka failure: Some bazooka error');
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

Add comprehensive test coverage.

The current test file only covers the Bazooka Lambda error case. Based on the PR objectives and changes, we need additional test coverage for:

  1. Success scenarios:

    • Successful deployment without Kafka topics
    • Successful deployment with new Kafka topics
    • Successful database migration only
  2. Error scenarios:

    • Database migration failures
    • ECS service upgrade failures
    • Network/timeout errors
  3. Edge cases:

    • Invalid event properties
    • Missing required fields
    • Unexpected Lambda responses

Here's a template for additional test cases:

// Success scenarios
it('should successfully complete deployment without Kafka topics', async () => {
  // Mock successful responses
  jest.spyOn(helpers, 'upgradeBazooka').mockResolvedValue(undefined);
  jest.spyOn(helpers, 'runDbAndKafkaMigration').mockResolvedValue(undefined);
  
  const mockEvent = {
    ...baseMockEvent,
    addNewKafkaTopics: false,
  };
  
  await expect(handler(mockEvent, mockContext)).resolves.not.toThrow();
  expect(helpers.upgradeBazooka).toHaveBeenCalledTimes(1);
  expect(helpers.runDbAndKafkaMigration).toHaveBeenCalledWith(
    expect.objectContaining({ createNewKafkaTopics: false })
  );
});

it('should only run database migration when onlyRunDbMigrationAndCreateKafkaTopics is true', async () => {
  // ... similar structure for other cases
});

import * as helpers from '../src/helpers';
import { InvokeCommandOutput, LambdaClient } from '@aws-sdk/client-lambda';
import { APIGatewayEvent, Context } from 'aws-lambda';
import { AuxoEventJson } from 'src/types';
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

Fix the import path for types.

The import path 'src/types' appears to be absolute. It should be relative to maintain consistency and avoid potential module resolution issues.

-import { AuxoEventJson } from 'src/types';
+import { AuxoEventJson } from '../src/types';

Committable suggestion was skipped due to low confidence.

onlyRunDbMigrationAndCreateKafkaTopics: false,
};

const mockContext: Context = {} as any;
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

Improve mock context implementation.

The current mock context is too minimal ({} as any), which could hide potential issues. Implement a proper mock context with all required properties.

-const mockContext: Context = {} as any;
+const mockContext: Context = {
+  awsRequestId: 'mock-request-id',
+  functionName: 'auxo-handler',
+  functionVersion: '1',
+  invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:auxo-handler',
+  memoryLimitInMB: '128',
+  logGroupName: '/aws/lambda/auxo-handler',
+  logStreamName: '2024/01/01/[$LATEST]mock',
+  callbackWaitsForEmptyEventLoop: true,
+  getRemainingTimeInMillis: () => 30000,
+  done: jest.fn(),
+  fail: jest.fn(),
+  succeed: jest.fn(),
+};
📝 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
const mockContext: Context = {} as any;
const mockContext: Context = {
awsRequestId: 'mock-request-id',
functionName: 'auxo-handler',
functionVersion: '1',
invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:auxo-handler',
memoryLimitInMB: '128',
logGroupName: '/aws/lambda/auxo-handler',
logStreamName: '2024/01/01/[$LATEST]mock',
callbackWaitsForEmptyEventLoop: true,
getRemainingTimeInMillis: () => 30000,
done: jest.fn(),
fail: jest.fn(),
succeed: jest.fn(),
};

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

Successfully merging this pull request may close these issues.

1 participant