Skip to content

Commit

Permalink
Fix and improve error handling for missing or invalid configuration file
Browse files Browse the repository at this point in the history
  • Loading branch information
bhirsz committed Nov 10, 2023
1 parent c4f2a72 commit 29449a3
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 17 deletions.
59 changes: 59 additions & 0 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
# This workflow will install Python dependencies
# and run unit tests for given OSes

name: Unit tests

on: [push, pull_request]

jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: 'ubuntu-latest'
python-version: '3.7'
rf-version: '3.2.2'
- os: 'windows'
python-version: '3.8'
rf-version: '4.1.3'
- os: 'ubuntu-latest'
python-version: '3.9'
rf-version: '5.0.1'
- os: 'ubuntu-latest'
python-version: '3.10'
rf-version: '6.1.1'
- os: 'ubuntu-latest'
python-version: '3.11'
rf-version: '6.1.1'
- os: 'ubuntu-latest'
python-version: '3.12'
rf-version: '6.1.1'
runs-on: ${{ matrix.os }}

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install robotframework==${{ matrix.rf-version }}
pip install .
- name: Run unit tests with coverage
run:
coverage run -m pytest

- name: Codecov
uses: codecov/codecov-action@v3
with:
name: ${{ matrix.python-version }}-${{ matrix.os }}-${{ matrix.rf-version }}
51 changes: 34 additions & 17 deletions src/DatabaseLibrary/connection_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@
# limitations under the License.

import importlib
from configparser import ConfigParser, NoOptionError, NoSectionError
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, Optional

try:
import ConfigParser
except:
import configparser as ConfigParser

from robot.api import logger


Expand Down Expand Up @@ -81,6 +78,30 @@ def __iter__(self):
return iter(self._connections.values())


class ConfigReader:
def __init__(self, config_file: Optional[str], alias: str):
if config_file is None:
config_file = "./resources/db.cfg"
config_path = Path(config_file)
if config_path.exists():
config = ConfigParser()
config.read([config_file])
else:
config = None
self.alias = alias
self.config: Optional[ConfigParser] = config

def get(self, param: str) -> str:
if self.config is None:
raise ValueError(f"Required '{param}' parameter was not provided in keyword arguments.") from None
try:
return self.config.get(self.alias, param)
except NoSectionError:
raise ValueError(f"Configuration file does not have [{self.alias}] section.") from None
except NoOptionError:
raise ValueError(f"Required '{param}' parameter missing in both keyword arguments and configuration file.")


class ConnectionManager:
"""
Connection Manager handles the connection & disconnection to the database.
Expand Down Expand Up @@ -153,18 +174,14 @@ def connect_to_database(
| # uses explicit `dbapiModuleName` and `dbName` but uses the `dbUsername` and `dbPassword` in './resources/db.cfg' |
| Connect To Database | psycopg2 | my_db_test |
"""

if dbConfigFile is None:
dbConfigFile = "./resources/db.cfg"
config = ConfigParser.ConfigParser()
config.read([dbConfigFile])

dbapiModuleName = dbapiModuleName or config.get(alias, "dbapiModuleName")
dbName = dbName or config.get(alias, "dbName")
dbUsername = dbUsername or config.get(alias, "dbUsername")
dbPassword = dbPassword if dbPassword is not None else config.get(alias, "dbPassword")
dbHost = dbHost or config.get(alias, "dbHost") or "localhost"
dbPort = int(dbPort or config.get(alias, "dbPort"))
config = ConfigReader(dbConfigFile, alias)

dbapiModuleName = dbapiModuleName or config.get("dbapiModuleName")
dbName = dbName or config.get("dbName")
dbUsername = dbUsername or config.get("dbUsername")
dbPassword = dbPassword if dbPassword is not None else config.get("dbPassword")
dbHost = dbHost or config.get("dbHost") or "localhost"
dbPort = int(dbPort if dbPort is not None else config.get("dbPort"))

if dbapiModuleName == "excel" or dbapiModuleName == "excelrw":
db_api_module_name = "pyodbc"
Expand Down
Empty file added test/tests/__init__.py
Empty file.
Empty file added test/tests/utests/__init__.py
Empty file.
48 changes: 48 additions & 0 deletions test/tests/utests/test_connection_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import re
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from DatabaseLibrary.connection_manager import ConnectionManager

TEST_DATA = Path(__file__).parent / "test_data"


class TestConnectWithConfigFile:
def test_connect_with_empty_config(self):
conn_manager = ConnectionManager()
config_path = str(TEST_DATA / "empty.cfg")
with pytest.raises(ValueError, match=re.escape("Configuration file does not have [default] section.")):
conn_manager.connect_to_database("my_client", dbConfigFile=config_path)

def test_connect_no_params_no_config(self):
conn_manager = ConnectionManager()
with pytest.raises(ValueError, match="Required 'dbName' parameter was not provided in keyword arguments."):
conn_manager.connect_to_database("my_client")

def test_connect_missing_option(self):
conn_manager = ConnectionManager()
config_path = str(TEST_DATA / "no_option.cfg")
with pytest.raises(
ValueError,
match="Required 'dbPassword' parameter missing in both keyword arguments and configuration file.",
):
conn_manager.connect_to_database("my_client", dbConfigFile=config_path)

def test_aliased_section(self):
conn_manager = ConnectionManager()
config_path = str(TEST_DATA / "alias.cfg")
with patch("importlib.import_module", new=MagicMock()) as client:
conn_manager.connect_to_database(
"my_client",
dbUsername="name",
dbPassword="password",
dbHost="host",
dbPort=0,
dbConfigFile=config_path,
alias="alias2",
)
client.return_value.connect.assert_called_with(
database="example", user="name", password="password", host="host", port=0
)
2 changes: 2 additions & 0 deletions test/tests/utests/test_data/alias.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[alias2]
dbName = example
Empty file.
3 changes: 3 additions & 0 deletions test/tests/utests/test_data/no_option.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[default]
dbName = example
dbUsername = example

0 comments on commit 29449a3

Please sign in to comment.