-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from narenaryan/test/add-unit-tests
tests: add unit tests for vaults
- Loading branch information
Showing
10 changed files
with
312 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
name: Run unit tests | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
branches: | ||
- "*" | ||
env: | ||
PYTHONPATH: ./src # Needed for tests to discover whispr package | ||
jobs: | ||
test: | ||
runs-on: ubuntu-22.04 | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v3 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: "3.10" | ||
|
||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install -r requirements_test.txt | ||
pip install coveralls | ||
- name: Run Test | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
run: | | ||
pytest --cov=whispr tests | ||
coveralls |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,3 +28,4 @@ lib64/ | |
# Secrets | ||
.env | ||
*.creds | ||
.coverage* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
-r requirements.txt | ||
coverage==7.6.4 | ||
pytest==8.3.3 | ||
pytest-cov==5.0.0 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
"""Tests for AWS module""" | ||
|
||
import unittest | ||
from unittest.mock import Mock, MagicMock, patch | ||
|
||
import botocore.exceptions | ||
import structlog | ||
|
||
from whispr.vault import SimpleVault | ||
from whispr.aws import AWSVault | ||
|
||
|
||
class AWSVaultTestCase(unittest.TestCase): | ||
"""Unit tests for AWSVault class, which fetches secrets from AWS Secrets Manager.""" | ||
|
||
def setUp(self): | ||
"""Set up mocks for logger and AWS client before each test.""" | ||
self.mock_logger = MagicMock() | ||
self.mock_client = MagicMock() | ||
self.vault = AWSVault(logger=self.mock_logger, client=self.mock_client) | ||
|
||
def test_initialization(self): | ||
"""Test that AWSVault initializes with logger and client correctly.""" | ||
self.assertEqual(self.vault.logger, self.mock_logger) | ||
self.assertEqual(self.vault.client, self.mock_client) | ||
|
||
def test_fetch_secrets_success(self): | ||
"""Test successful fetch of secrets from AWS Secrets Manager.""" | ||
self.mock_client.get_secret_value.return_value = { | ||
"SecretString": '{"key": "value"}' | ||
} | ||
result = self.vault.fetch_secrets("test_secret") | ||
self.assertEqual(result, '{"key": "value"}') | ||
self.mock_client.get_secret_value.assert_called_with(SecretId="test_secret") | ||
|
||
def test_fetch_secrets_resource_not_found(self): | ||
"""Test fetch_secrets handles ResourceNotFoundException gracefully.""" | ||
# Set up the client to raise ResourceNotFoundException | ||
self.mock_client.get_secret_value.side_effect = botocore.exceptions.ClientError( | ||
{"Error": {"Code": "ResourceNotFoundException"}}, "get_secret_value" | ||
) | ||
|
||
result = self.vault.fetch_secrets("non_existent_secret") | ||
self.assertEqual(result, "") | ||
self.mock_logger.error.assert_called_with( | ||
"The secret is not found on AWS. Did you set the right AWS_DEFAULT_REGION ?", | ||
secret_name="non_existent_secret", | ||
) | ||
|
||
@patch("whispr.aws.AWSVault.fetch_secrets") | ||
def test_fetch_secrets_unrecognized_client_exception(self, mock_fetch_secrets): | ||
"""Test fetch_secrets handles UnrecognizedClientException gracefully.""" | ||
mock_fetch_secrets.side_effect = botocore.exceptions.ClientError( | ||
{"Error": {"Code": "UnrecognizedClientException"}}, "get_secret_value" | ||
) | ||
|
||
with self.assertRaises(botocore.exceptions.ClientError): | ||
result = self.vault.fetch_secrets("incorrect_credentials_secret") | ||
self.assertEqual(result, "") | ||
self.mock_logger.error.assert_called_with( | ||
"Incorrect AWS credentials set for operation. Please verify them and retry." | ||
) | ||
|
||
def test_fetch_secrets_generic_exception(self): | ||
"""Test fetch_secrets raises exception and logs an error for generic exceptions.""" | ||
# Set up the client to raise a generic exception | ||
exception_message = "Some generic error" | ||
self.mock_client.get_secret_value.side_effect = Exception(exception_message) | ||
|
||
with self.assertRaises(Exception) as context: | ||
self.vault.fetch_secrets("generic_error_secret") | ||
self.assertEqual(str(context.exception), exception_message) | ||
|
||
# Extract the actual call to the logger and check its arguments | ||
self.assertTrue(self.mock_logger.error.called) | ||
error_call = self.mock_logger.error.call_args | ||
self.assertEqual(error_call[0][0], "Error fetching secret") | ||
self.assertIsInstance(error_call[1]["error"], Exception) | ||
self.assertEqual(str(error_call[1]["error"]), exception_message) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
"""Tests for Azure module""" | ||
|
||
import unittest | ||
from unittest.mock import Mock, MagicMock | ||
|
||
import structlog | ||
from azure.core.exceptions import ResourceNotFoundError | ||
|
||
from whispr.vault import SimpleVault | ||
from whispr.azure import AzureVault | ||
|
||
|
||
class AzureVaultTestCase(unittest.TestCase): | ||
"""Unit tests for AzureVault class, which fetches secrets from Azure Key Vault.""" | ||
|
||
def setUp(self): | ||
"""Set up mocks for logger, Azure client, and vault URL before each test.""" | ||
self.mock_logger = MagicMock() | ||
self.mock_client = MagicMock() | ||
self.vault_url = "https://example-vault.vault.azure.net/" | ||
self.vault = AzureVault( | ||
logger=self.mock_logger, client=self.mock_client, vault_url=self.vault_url | ||
) | ||
|
||
def test_initialization(self): | ||
"""Test that AzureVault initializes with logger, client, and vault_url correctly.""" | ||
self.assertEqual(self.vault.logger, self.mock_logger) | ||
self.assertEqual(self.vault.client, self.mock_client) | ||
self.assertEqual(self.vault.vault_url, self.vault_url) | ||
|
||
def test_fetch_secrets_success(self): | ||
"""Test successful fetch of secrets from Azure Key Vault.""" | ||
# Mock the client response | ||
mock_secret = MagicMock() | ||
mock_secret.value = '{"key": "value"}' | ||
self.mock_client.get_secret.return_value = mock_secret | ||
|
||
result = self.vault.fetch_secrets("test_secret") | ||
self.assertEqual(result, '{"key": "value"}') | ||
self.mock_logger.info.assert_called_with( | ||
"Successfully fetched secret: test_secret" | ||
) | ||
self.mock_client.get_secret.assert_called_with("test_secret") | ||
|
||
def test_fetch_secrets_resource_not_found(self): | ||
"""Test fetch_secrets handles ResourceNotFoundError gracefully.""" | ||
# Set up the client to raise ResourceNotFoundError | ||
self.mock_client.get_secret.side_effect = ResourceNotFoundError( | ||
"Secret not found" | ||
) | ||
|
||
result = self.vault.fetch_secrets("non_existent_secret") | ||
self.assertEqual(result, "") | ||
self.mock_logger.error.assert_called_with( | ||
"The given secret: non_existent_secret is not found on azure vault. Please check the secret name, vault name or subscription ID." | ||
) | ||
|
||
def test_fetch_secrets_generic_exception(self): | ||
"""Test fetch_secrets raises exception and logs an error for generic exceptions.""" | ||
# Set up the client to raise a generic exception | ||
exception_message = "Some generic error" | ||
self.mock_client.get_secret.side_effect = Exception(exception_message) | ||
|
||
with self.assertRaises(Exception) as context: | ||
self.vault.fetch_secrets("generic_error_secret") | ||
self.assertEqual(str(context.exception), exception_message) | ||
|
||
# Extract the actual call to the logger and check its arguments | ||
self.assertTrue(self.mock_logger.error.called) | ||
error_call = self.mock_logger.error.call_args | ||
self.assertEqual( | ||
error_call[0][0], | ||
f"Error fetching secret: generic_error_secret, Error: {exception_message}", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
"""Tests for GCP module""" | ||
|
||
import unittest | ||
from unittest.mock import Mock, patch, MagicMock | ||
|
||
import google.api_core.exceptions | ||
import structlog | ||
|
||
from whispr.vault import SimpleVault | ||
from whispr.gcp import GCPVault | ||
|
||
|
||
class GCPVaultTestCase(unittest.TestCase): | ||
"""Unit tests for GCPVault class, which fetches secrets from GCP Secrets Manager.""" | ||
|
||
def setUp(self): | ||
"""Set up mocks for logger, GCP client, and project_id before each test.""" | ||
self.mock_logger = MagicMock() | ||
self.mock_client = MagicMock() | ||
self.project_id = "test_project_id" | ||
self.vault = GCPVault( | ||
logger=self.mock_logger, client=self.mock_client, project_id=self.project_id | ||
) | ||
|
||
def test_initialization(self): | ||
"""Test that GCPVault initializes with logger, client, and project_id correctly.""" | ||
self.assertEqual(self.vault.logger, self.mock_logger) | ||
self.assertEqual(self.vault.client, self.mock_client) | ||
self.assertEqual(self.vault.project_id, self.project_id) | ||
|
||
def test_fetch_secrets_success(self): | ||
"""Test successful fetch of secrets from GCP Secrets Manager.""" | ||
# Mock the client response | ||
mock_response = MagicMock() | ||
mock_response.payload.data.decode.return_value = '{"key": "value"}' | ||
self.mock_client.access_secret_version.return_value = mock_response | ||
|
||
result = self.vault.fetch_secrets("test_secret") | ||
self.assertEqual(result, '{"key": "value"}') | ||
self.mock_logger.info.assert_called_with( | ||
"Successfully fetched gcp secret: projects/test_project_id/secrets/test_secret/versions/latest" | ||
) | ||
self.mock_client.access_secret_version.assert_called_with( | ||
name="projects/test_project_id/secrets/test_secret/versions/latest" | ||
) | ||
|
||
def test_fetch_secrets_not_found(self): | ||
"""Test fetch_secrets handles NotFound exception gracefully.""" | ||
# Set up the client to raise NotFound exception | ||
self.mock_client.access_secret_version.side_effect = ( | ||
google.api_core.exceptions.NotFound("Secret not found") | ||
) | ||
|
||
result = self.vault.fetch_secrets("non_existent_secret") | ||
self.assertEqual(result, "") | ||
self.mock_logger.error.assert_called_with( | ||
"The given secret: projects/test_project_id/secrets/non_existent_secret/versions/latest is not found on gcp vault." | ||
) | ||
|
||
def test_fetch_secrets_generic_exception(self): | ||
"""Test fetch_secrets handles generic exceptions gracefully.""" | ||
# Set up the client to raise a generic exception | ||
exception_message = "Some generic error" | ||
self.mock_client.access_secret_version.side_effect = Exception( | ||
exception_message | ||
) | ||
|
||
result = self.vault.fetch_secrets("generic_error_secret") | ||
self.assertEqual(result, "") | ||
self.mock_logger.error.assert_called_with( | ||
f"Error encountered while fetching secret: projects/test_project_id/secrets/generic_error_secret/versions/latest, Error: {exception_message}" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
"""Tests for Vault module""" | ||
|
||
import unittest | ||
from unittest.mock import Mock, patch | ||
|
||
import structlog | ||
|
||
from whispr.vault import SimpleVault | ||
|
||
|
||
class SimpleVaultTestCase(unittest.TestCase): | ||
"""Tests for Vault""" | ||
|
||
def setUp(self): | ||
# Mock logger and client to use in tests | ||
self.mock_logger = Mock(spec=structlog.BoundLogger) | ||
self.mock_client = Mock() | ||
|
||
# Subclass SimpleVault since it's abstract, only for testing | ||
class TestVault(SimpleVault): | ||
def fetch_secrets(self, secret_name: str) -> str: | ||
# Provide a simple implementation for the abstract method | ||
return "test_secret" | ||
|
||
self.vault = TestVault(logger=self.mock_logger, client=self.mock_client) | ||
|
||
@patch.object( | ||
SimpleVault, "__abstractmethods__", set() | ||
) # This allows instantiation of SimpleVault directly if needed | ||
def test_initialization(self): | ||
"""Test if the SimpleVault initializes with logger and client.""" | ||
self.assertEqual(self.vault.logger, self.mock_logger) | ||
self.assertEqual(self.vault.client, self.mock_client) | ||
|
||
def test_fetch_secrets(self): | ||
"""Test the fetch_secrets method to ensure it returns the expected result.""" | ||
secret_name = "my_secret" | ||
result = self.vault.fetch_secrets(secret_name) | ||
self.assertEqual(result, "test_secret") |