diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..3636711 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,34 @@ +name: ci + +on: + pull_request: + push: + branches: + - refactor_tests + +env: + API_KEY: ${{ secrets.ApiKey }} + +concurrency: + # Cancels pending runs when a PR gets updated. + group: ${{ github.head_ref || github.run_id }}-${{ github.actor }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements_dev.txt + - name: Run pytest + run: pytest diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..449fb2c --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +pytest>=7.1.2 +python-dotenv>=0.20.0 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_pandas.py b/tests/test_pandas.py new file mode 100644 index 0000000..239ad4b --- /dev/null +++ b/tests/test_pandas.py @@ -0,0 +1,160 @@ +from itertools import product +import os + +from dotenv import load_dotenv +from entsoe import EntsoePandasClient +import pandas as pd +import pytest + +load_dotenv() + +API_KEY = os.getenv("API_KEY") + + +@pytest.fixture +def client(): + yield EntsoePandasClient(api_key=API_KEY) + + +@pytest.fixture +def start(): + return pd.Timestamp("20171201", tz="Europe/Brussels") + + +@pytest.fixture +def end(): + return pd.Timestamp("20180101", tz="Europe/Brussels") + + +@pytest.fixture +def country_code(): + return "BE" # Belgium + + +@pytest.fixture +def country_code_from(): + return "FR" # France + + +@pytest.fixture +def country_code_to(): + return "DE_LU" # Germany-Luxembourg + + +STARTS = [pd.Timestamp("20171201", tz="Europe/Brussels")] +ENDS = [pd.Timestamp("20180101", tz="Europe/Brussels")] +COUNTRY_CODES = ["BE"] # Belgium +COUNTRY_CODES_FROM = ["FR"] # France +COUNTRY_CODES_TO = ["DE_LU"] # Germany-Luxembourg + +BASIC_QUERIES_SERIES = [ + "query_day_ahead_prices", + "query_net_position", + "query_aggregate_water_reservoirs_and_hydro_storage", +] + +BASIC_QUERIES_DATAFRAME = [ + "query_load", + "query_load_forecast", + "query_wind_and_solar_forecast", + "query_generation_forecast", + "query_generation", + "query_generation_per_plant", + "query_installed_generation_capacity", + "query_installed_generation_capacity_per_unit", + "query_imbalance_prices", + "query_withdrawn_unavailability_of_generation_units", + "query_unavailability_of_generation_units", + "query_unavailability_of_production_units", + "query_import", + "query_generation_import", +] + +CROSSBORDER_QUERIES = [ + "query_crossborder_flows", + "query_scheduled_exchanges", + "query_net_transfer_capacity_dayahead", + "query_net_transfer_capacity_weekahead", + "query_net_transfer_capacity_monthahead", + "query_net_transfer_capacity_yearahead", + "query_intraday_offered_capacity", +] + +# pandas.Series + +@pytest.mark.parametrize( + "country_code, start, end, query", + product(COUNTRY_CODES, STARTS, ENDS, BASIC_QUERIES_SERIES), +) +def test_basic_queries_series(client, query, country_code, start, end): + result = getattr(client, query)(country_code, start=start, end=end) + assert not result.empty + + +@pytest.mark.parametrize( + "country_code_from, country_code_to, start, end, query", + product(COUNTRY_CODES_FROM, COUNTRY_CODES_TO, STARTS, ENDS, CROSSBORDER_QUERIES), +) +def test_crossborder_queries( + client, query, country_code_from, country_code_to, start, end +): + result = getattr(client, query)(country_code_from, country_code_to, start=start, end=end) + assert not result.empty + + +def test_query_offered_capacity(client, country_code_from, country_code_to, start, end): + contract_marketagreement_type = "A01" + result = client.query_offered_capacity( + country_code_from, country_code_to, contract_marketagreement_type, start=start, end=end, implicit=True + ) + assert not result.empty + + +# pandas.DataFrames + +@pytest.mark.parametrize( + "country_code, start, end, query", + product(COUNTRY_CODES, STARTS, ENDS, BASIC_QUERIES_DATAFRAME), +) +def test_basic_queries_dataframe(client, query, country_code, start, end): + result = getattr(client, query)(country_code, start=start, end=end) + assert not result.empty + + +def test_query_contracted_reserve_prices(client, country_code, start, end): + type_marketagreement_type = "A01" + result = client.query_contracted_reserve_prices( + country_code, start=start, end=end, type_marketagreement_type=type_marketagreement_type, psr_type=None + ) + assert not result.empty + + +def test_query_contracted_reserve_amount(client, country_code, start, end): + type_marketagreement_type = "A01" + result = client.query_contracted_reserve_amount( + country_code, start=start, end=end, type_marketagreement_type=type_marketagreement_type, psr_type=None + ) + assert not result.empty + + +def test_query_unavailability_transmission(client, country_code_from, country_code_to, start, end): + result = client.query_unavailability_transmission( + country_code_from, country_code_to, start=start, end=end + ) + assert not result.empty + + +def test_query_procured_balancing_capacity_process_type_not_allowed(client, country_code, start, end): + process_type = "A01" + with pytest.raises(ValueError): + client.query_procured_balancing_capacity( + country_code, start=start, end=end, process_type=process_type + ) + + +def test_query_procured_balancing_capacity(client, country_code, start, end): + process_type = "A47" + result = client.query_procured_balancing_capacity( + country_code, start=start, end=end, process_type=process_type, type_marketagreement_type=None + ) + assert not result.empty diff --git a/tests/test_raw.py b/tests/test_raw.py new file mode 100644 index 0000000..8300036 --- /dev/null +++ b/tests/test_raw.py @@ -0,0 +1,191 @@ +from itertools import product +import os + +from bs4 import BeautifulSoup +from dotenv import load_dotenv +from entsoe import EntsoeRawClient +from entsoe.exceptions import PaginationError +import pandas as pd +import pytest + +load_dotenv() + +API_KEY = os.getenv("API_KEY") + + +@pytest.fixture +def client(): + yield EntsoeRawClient(api_key=API_KEY) + + +def valid_xml(s: str) -> bool: + try: + BeautifulSoup(s, "html.parser") + return True + except Exception: + return False + + +@pytest.fixture +def start(): + return pd.Timestamp("20171201", tz="Europe/Brussels") + + +@pytest.fixture +def end(): + return pd.Timestamp("20180101", tz="Europe/Brussels") + + +@pytest.fixture +def country_code(): + return "BE" # Belgium + + +@pytest.fixture +def country_code_from(): + return "FR" # France + + +@pytest.fixture +def country_code_to(): + return "DE_LU" # Germany-Luxembourg + + +STARTS = [pd.Timestamp("20171201", tz="Europe/Brussels")] +ENDS = [pd.Timestamp("20180101", tz="Europe/Brussels")] +COUNTRY_CODES = ["BE"] # Belgium +COUNTRY_CODES_FROM = ["FR"] # France +COUNTRY_CODES_TO = ["DE_LU"] # Germany-Luxembourg + +BASIC_QUERIES = [ + "query_day_ahead_prices", + "query_net_position", + "query_load", + "query_load_forecast", + "query_wind_and_solar_forecast", + "query_generation_forecast", + "query_generation", + "query_generation_per_plant", + "query_installed_generation_capacity", + "query_installed_generation_capacity_per_unit", + "query_aggregate_water_reservoirs_and_hydro_storage", +] + +CROSSBORDER_QUERIES = [ + "query_crossborder_flows", + "query_scheduled_exchanges", + "query_net_transfer_capacity_dayahead", + "query_net_transfer_capacity_weekahead", + "query_net_transfer_capacity_monthahead", + "query_net_transfer_capacity_yearahead", +] + +# XML + +@pytest.mark.parametrize( + "country_code, start, end, query", + product(COUNTRY_CODES, STARTS, ENDS, BASIC_QUERIES), +) +def test_basic_queries(client, query, country_code, start, end): + result = getattr(client, query)(country_code, start, end) + assert isinstance(result, str) + assert valid_xml(result) + + +@pytest.mark.parametrize( + "country_code_from, country_code_to, start, end, query", + product(COUNTRY_CODES_FROM, COUNTRY_CODES_TO, STARTS, ENDS, CROSSBORDER_QUERIES), +) +def test_crossborder_queries( + client, query, country_code_from, country_code_to, start, end +): + result = getattr(client, query)(country_code_from, country_code_to, start, end) + assert isinstance(result, str) + assert valid_xml(result) + + +def test_query_intraday_offered_capacity(client, country_code_from, country_code_to, start, end): + result = client.query_intraday_offered_capacity( + country_code_from, country_code_to, start, end, implicit=True + ) + assert isinstance(result, str) + assert valid_xml(result) + + +def test_query_offered_capacity(client, country_code_from, country_code_to, start, end): + contract_marketagreement_type = "A01" + result = client.query_offered_capacity( + country_code_from, country_code_to, start, end, contract_marketagreement_type, implicit=True + ) + assert isinstance(result, str) + assert valid_xml(result) + + +def test_query_contracted_reserve_prices(client, country_code, start, end): + type_marketagreement_type = "A01" + result = client.query_contracted_reserve_prices( + country_code, start, end, type_marketagreement_type, psr_type=None + ) + assert isinstance(result, str) + assert valid_xml(result) + + +def test_query_contracted_reserve_amount(client, country_code, start, end): + type_marketagreement_type = "A01" + result = client.query_contracted_reserve_amount( + country_code, start, end, type_marketagreement_type, psr_type=None + ) + assert isinstance(result, str) + assert valid_xml(result) + + +def test_query_procured_balancing_capacity_bytearray(client, country_code, start, end): + process_type = "A47" + result = client.query_procured_balancing_capacity( + country_code, start, end, process_type, type_marketagreement_type=None + ) + assert isinstance(result, str) + assert valid_xml(result) + + +def test_query_procured_balancing_capacity_process_type_not_allowed(client): + type_marketagreement_type = "A01" + process_type = "A01" + with pytest.raises(ValueError): + client.query_procured_balancing_capacity( + country_code, start, end, process_type, type_marketagreement_type + ) + +# ZIP + +def test_query_imbalance_prices(client, country_code, start, end): + result = client.query_imbalance_prices(country_code, start, end, psr_type=None) + assert isinstance(result, (bytes, bytearray)) + + +def test_query_unavailability_of_generation_units(client, country_code, start, end): + result = client.query_unavailability_of_generation_units(country_code, start, end, docstatus=None, periodstartupdate=None, periodendupdate=None) + assert isinstance(result, (bytes, bytearray)) + + +def test_query_unavailability_of_production_units(client, country_code, start, end): + result = client.query_unavailability_of_production_units(country_code, start, end, docstatus=None, periodstartupdate=None, periodendupdate=None) + assert isinstance(result, (bytes, bytearray)) + + +def test_query_unavailability_transmission( + client, country_code_from, country_code_to, start, end +): + result = client.query_unavailability_transmission( + country_code_from, country_code_to, start, end, docstatus=None, periodstartupdate=None, periodendupdate=None + ) + assert isinstance(result, (bytes, bytearray)) + + +def test_query_withdrawn_unavailability_of_generation_units( + client, country_code, start, end +): + result = client.query_withdrawn_unavailability_of_generation_units( + country_code, start, end, + ) + assert isinstance(result, (bytes, bytearray))