Skip to content

Commit

Permalink
Add core tests (#138)
Browse files Browse the repository at this point in the history
* 🤡 fakers: add base

* 🤡 fakers: add plants

* 🤡 fakers: add users

* 📑 interfaces: add users repository

* 📑 interfaces: add plants repository

* 🏭 factories: add update active plant

* 🏭 factories: add filters plants use case

* 🏭 factories: add create plant by form use case

* 🏭 factories: add delete plant use case

* 🏭 factories: add get plant by id factory use case

* 🏭 factories: add get plants page data use case

* 🏭 factories: add update plant use case

* 🤡 mocks: add plants repository

* 🤡 mocks: add users repositories

* 🧪 tests: update active plant

* 🧪 tests: create plant by form use case

* 🧪 tests: get plants page data use case

* 🧪 tests: delete plant use case

* 🧪 tests: update plant use case

* 🧪 tests: filter plants use case

* 🧪 tests: get plant by id use case

* 🚨 errors: pass ui message to super error

* ♻️ refactor: use factory pattern upon plants use cases

* 🚨 errors: add base error

* 🚨 errors: add email template not provided error

* 🚨 errors: incorrect admin user email error

* 🚨 errors: add sender password not provided error

* 🚨 errors: add user email not provided error

* 📑 providers: add email provider

* 📑 interfaces: add auth

* 🏭 factories: add auth

* 🤡 mocks: add auth

* 🤡 mocks: add email provider

* 🧪 tests: login user

* 🧪 tests: reset password use case

* 🧪 tests: request password reset use case

* ♻️ refactor: all custom errors implamentations and usage

* 🧪 tests: weekday common

* 🧪 tests: csv file common

* 🧪 tests: date common

* 🧪 tests: days range common

* 🧪 tests: weekday common

* 🚨 errors: add datime not valid error

* 🚨 errors: add date not valid error

* 🧪 tests: ordered plants common

* 🧪 tests: records filters common

* 🧪 tests: datetime common

* 📑 interfaces: add data analyser provider

* 🚨 errors: add csv file validation

* 👥 entities: add line chart record

* 🥸 fakers: add sensors records

* 🥸 fakers: add line chart records

* 🧪 tests: line chart common

* 📦 deps: install dependencies for tests

* ⚙️ config: set env vars for test enviroment

* 🧩 commons: enhance date and datime validation

* 🚨 errors: add for sensors records

* 🏷️ types: add hints for each attribute of error classes

* 🧩 commons: use date value on set records filters

* 💾 database: prevent mysql from starting under test enviroment

* 🤡 mocks: add sensors records repository

* 📑 interfaces: add sensors records repository

* ⚡perf: remove useless logs from app

* 🧪 tests: create sensors record by api use case

* 🧪 tests: create sensors record by csv file use case

* 🧪 tests: create sensors records by form use case

* 🧪 tests: get sensors records dashboard page data use case

* 🧪 tests: delete sensors records use case

* 🧪 tests: get sensors records csv file use case

* 🧪 tests: get last record page data use case

* 🧪 tests: ge sensors records csv file use case

* 🏭 factories: add create sensors record by api use case

* 🏭 factories: add all sensors records use cases

* 🚨 errors: add page number

* 🧪 tests: pagination common

* 🐛 fix: sensors records use cases exportations

* 🧩 commons: get date on get records filters date values

* 🥸 fakers: add checklist records

* 🚨 errors: add checklist record not valid

* 🚨 errors: add hour not valid

* 🚨 errors: add checklist record not found

* 📑 interfaces: add checklist records repository

* 🧪 tests: create checklist record by csv file use case

* 🧪 tests: create checklist record by csv form use case

* 🧪 tests: create delete checklist record use case

* 🧪 tests: create get checklist records csv file use case

* 🧪 tests: get checklist records table page data use case

* 🧪 tests: update checklist record use case

* 🤡 mocks: add checklist records repository

* 🏭 factories: checklist records use cases

* 🧪 tests: fix line chart common

* 🏭 factories: sensors records use cases

* 🤡 mocks: add csv_file_attribute to data analyser provider

* 🚄 ci: run core tests on every push or pull request

* 🐛 fix: sensors records counting with filters

* 🐛 fix: delete many records using mysql conector

* ♻️ refactor: sensors records and checklist records views making them to use use cases from factories

* 🤡 mocks: fix get sensors records count params of sensors records repository
  • Loading branch information
JohnPetros authored Jul 4, 2024
1 parent 946ce10 commit 8594e30
Show file tree
Hide file tree
Showing 226 changed files with 6,109 additions and 1,141 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Continuous Integration

on: [push, pull_request]

jobs:
CI:
name: Run core tests
runs-on: ubuntu-latest

steps:
- name: Init job
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: 3.10.12
cache: 'pip'

- name: Install Python dependencies
run: pip install -r requirements.txt

- name: Run core tests
run: python -m pytest src/app/core


5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
[tool.ruff]
ignore = [
"F403" # undefined-names,
]

[tool.pytest.ini_options]
env = [
"ENVIRONMENT=test"
]
8 changes: 8 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ cowsay==6.1
dnspython==2.6.1
email_validator==2.1.1
et-xmlfile==1.1.0
exceptiongroup==1.2.1
Faker==26.0.0
Flask==3.0.2
Flask-APScheduler==1.13.1
Flask-Bcrypt==1.0.1
Expand All @@ -24,6 +26,7 @@ greenlet==3.0.3
gunicorn==22.0.0
httplib2==0.22.0
idna==3.7
iniconfig==2.0.0
itsdangerous==2.1.2
Jinja2==3.1.3
MarkupSafe==2.1.5
Expand All @@ -32,17 +35,22 @@ numpy==1.26.4
openpyxl==3.1.2
packaging==24.0
pandas==2.2.1
pluggy==1.5.0
proto-plus==1.23.0
protobuf==4.25.3
pyasn1==0.6.0
pyasn1_modules==0.4.0
pyparsing==3.1.2
pytest==8.2.2
pytest-describe==2.2.0
pytest-env==1.1.3
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
pytz==2024.1
requests==2.32.3
rsa==4.9
six==1.16.0
tomli==2.0.1
tzdata==2024.1
tzlocal==5.2
uritemplate==4.1.1
Expand Down
Empty file added src/app/core/__init__.py
Empty file.
38 changes: 22 additions & 16 deletions src/app/core/commons/csv_file.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,50 @@
from typing import Dict, List
from werkzeug.datastructures import FileStorage

from core.commons import Error

from infra.providers.data_analyser_provider import DataAnalyserProvider
from core.interfaces.providers import DataAnalyserProviderInterface
from core.errors.validation import CSVFileNotValidError, CSVColumnsNotValidError


class CsvFile:
def __init__(self, csv_file: FileStorage) -> None:
self.csv_file = csv_file
self.data_analyser_provider = DataAnalyserProvider()
def __init__(
self,
file: FileStorage,
data_analyser_provider: DataAnalyserProviderInterface,
):
if not isinstance(file, FileStorage):
raise CSVFileNotValidError()

self._file = file
self._data_analyser_provider = data_analyser_provider

def read(self):
extension = self.get_extension()
extension = self.__get_extension()

if extension in ["csv", "txt"]:
self.data_analyser_provider.read_csv(self.csv_file)
self._data_analyser_provider.read_csv(self._file)
elif extension == "xlsx":
self.data_analyser_provider.read_excel(self.csv_file)
self._data_analyser_provider.read_excel(self._file)
else:
raise Error("Arquivo CSV inválido")
raise CSVFileNotValidError()

def get_records(self) -> List[Dict]:
records = self.data_analyser_provider.convert_to_list_of_records()
records = self._data_analyser_provider.convert_to_list_of_records()

records_list = []
for record in records:
records_list.append({key.lower(): value for key, value in record.items()})

return records_list

def validate_columns(self, columns: List[str]) -> bool:
csv_columns = self.data_analyser_provider.get_columns()
def validate_columns(self, columns: List[str]):
csv_columns = self._data_analyser_provider.get_columns()

has_valid_columns = set(map(lambda x: x.lower(), csv_columns)) == set(
map(lambda x: x.lower(), columns)
)

if not has_valid_columns:
raise Error("As colunas do arquivo CSV não estão corretas")
raise CSVColumnsNotValidError()

def get_extension(self) -> str:
return self.csv_file.filename.split(".")[1]
def __get_extension(self) -> str:
return self._file.filename.split(".")[1]
8 changes: 5 additions & 3 deletions src/app/core/commons/date.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import date, datetime

from core.commons import Error
from core.errors.validation import DateNotValidError


class Date:
Expand All @@ -14,10 +14,12 @@ def __init__(self, value: date):

self.value = value
except Exception:
raise Error("Valor de data inválido")
raise DateNotValidError

def format_value(self):
self.value = self.value.strftime("%d/%m/%Y")
if isinstance(self.value, date):
self.value = self.value.strftime("%d/%m/%Y")

return self

def get_value(self, is_date: bool = False) -> str:
Expand Down
16 changes: 13 additions & 3 deletions src/app/core/commons/datetime.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
from datetime import datetime, time

from core.errors.validation import DatetimeNotValidError


class Datetime:
value: datetime

def __init__(self, value: datetime):
if not isinstance(value, datetime):
raise DatetimeNotValidError()

self.value = value

def format_value(self):
self.value = self.value.strftime("%d/%m/%Y %H:%M")
if isinstance(self.value, datetime):
self.value = self.value.strftime("%d/%m/%Y %H:%M")

return self

def get_time(self) -> time:
def get_time(self):
if isinstance(self.value, str):
return datetime.strptime(self.value, "%d/%m/%Y %H:%M").time()

return time(
hour=self.value.hour,
minute=self.value.minute,
)

def get_value(self, is_datetime: bool = False) -> str:
def get_value(self, is_datetime: bool = False):
if is_datetime:
return self.value

Expand Down
2 changes: 2 additions & 0 deletions src/app/core/commons/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ def __init__(
self.internal_message = internal_message
self.status_code = status_code

super().__init__(ui_message)

cow_say(ui_message)
cow_say(internal_message)
67 changes: 31 additions & 36 deletions src/app/core/commons/line_chart.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,14 @@
from datetime import timedelta
from dataclasses import asdict

from core.commons.error import Error
from core.entities import Plant
from core.entities.plant import Plant
from core.entities.line_chart_record import LineChartRecord
from core.constants import DAYS_RANGES


class LineChart:
def __init__(self, records: list[dict], attribute: str):
self.records = []

for record in records:
for required_attribute in ["date", "plant_id", attribute]:
if required_attribute not in record:
raise Error("Required attribute not found in record")

self.records.append(
{
"date": record["date"],
"plant_id": record["plant_id"],
"value": record[attribute],
}
)

def filter_records_by_range_of_days(self, days_range: int, plant_id: str):
last_date = self.__get_last_record_date_by_plant(plant_id)
data = []

for day in range(days_range - 1, -1, -1):
current_date = last_date - timedelta(days=day)

for record in self.records:
if record["date"] == current_date:
data.append(
{
**record,
"date": record["date"].strftime("%d/%m/%Y"),
}
)

return data
def __init__(self, records: list[LineChartRecord]):
self.records = [asdict(record) for record in records]

def get_data(self, plants: list[Plant]):
chart_data = {
Expand All @@ -51,10 +21,13 @@ def get_data(self, plants: list[Plant]):

for days_range in DAYS_RANGES:
for plant in plants:
days_range_records = self.filter_records_by_range_of_days(
days_range_records = self.__filter_records_by_range_of_days(
days_range, plant.id
)

if not len(days_range_records):
continue

values = []
dates = []

Expand All @@ -75,6 +48,28 @@ def get_data(self, plants: list[Plant]):

return chart_data

def __filter_records_by_range_of_days(self, days_range: int, plant_id: str):
last_date = self.__get_last_record_date_by_plant(plant_id)

if last_date is None:
return []

data = []

for day in range(days_range - 1, -1, -1):
current_date = last_date - timedelta(days=day)

for record in self.records:
if record["date"] == current_date:
data.append(
{
**record,
"date": record["date"].strftime("%d/%m/%Y"),
}
)

return data

def __get_last_record_date_by_plant(self, plant_id: str):
for index in range(1, len(self.records) + 1):
record = self.records[-index]
Expand Down
9 changes: 5 additions & 4 deletions src/app/core/commons/ordered_plants.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from core.entities import Plant
from core.entities.plant import Plant


class OrderedPlants:
value: list[Plant]
value: list[Plant] = []

def __init__(self, plants: list[Plant], active_plant_id: str) -> None:
def __init__(self, plants: list[Plant], active_plant_id: str):
self.value = self.__order(plants, active_plant_id)

def __order(self, plants: list[Plant], active_plant_id: str):
if len(plants) == 1:
plants_count = len(plants)
if plants_count == 0 or plants_count == 1:
return plants

filtered_plants_by_id = filter(
Expand Down
6 changes: 5 additions & 1 deletion src/app/core/commons/pagination.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from math import ceil

from core.constants import PAGINATION
from core.errors.validation import PageNumberNotValidError


class Pagination:
def __init__(self, page_number: int, records_count: int) -> None:
def __init__(self, page_number: int, records_count: int):
if not isinstance(page_number, int) or not isinstance(records_count, int):
raise PageNumberNotValidError()

self.page_number = page_number
self.records_count = records_count

Expand Down
12 changes: 8 additions & 4 deletions src/app/core/commons/records_filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from core.commons import Date
from core.errors.validation import DateNotValidError


class RecordsFilters:
Expand All @@ -18,11 +19,14 @@ def __handle_filters(self):
if self.plant_id == "all":
self.plant_id = None

if self.start_date != "" and isinstance(self.start_date, str):
self.start_date = Date(self.start_date).get_value()
try:
if self.start_date != "" and isinstance(self.start_date, str):
self.start_date = Date(self.start_date).get_value(is_date=True)

if self.end_date != "" and isinstance(self.end_date, str):
self.end_date = Date(self.end_date).get_value()
if self.end_date != "" and isinstance(self.end_date, str):
self.end_date = Date(self.end_date).get_value(is_date=True)
except Exception:
raise DateNotValidError()

if self.start_date and (self.end_date is None or self.end_date == ""):
self.end_date = self.start_date
Empty file.
Loading

0 comments on commit 8594e30

Please sign in to comment.