-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds an extensive set of unit tests to ensure all flask endpoints are working as expected. We are mainly interested in the HTTP status code and in the returned response bodies. In any case the unit tests are checking also that the underlying objects and methods are instantiated and called as expected. Besides that, we also do the following: * Add unit tests for all utilitary functions * Add unit tests for all functions used by the CLI * Add unit tests for the CLI References: BAR-132. Signed-off-by: Israel Barth Rubio <israel.barth@enterprisedb.com>
- Loading branch information
1 parent
742bbbe
commit 83b7ab5
Showing
4 changed files
with
1,025 additions
and
0 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,156 @@ | ||
# -*- coding: utf-8 -*- | ||
# © Copyright EnterpriseDB UK Limited 2021-2023 | ||
# | ||
# This file is part of Postgres Backup API. | ||
# | ||
# Postgres Backup API is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# Postgres Backup API is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with Postgres Backup API. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
"""Unit tests for the CLI.""" | ||
from textwrap import dedent | ||
from unittest.mock import MagicMock, patch | ||
|
||
import pytest | ||
|
||
from pg_backup_api.__main__ import main | ||
|
||
|
||
_HELP_OUTPUT = { | ||
"pg-backup-api --help": dedent("""\ | ||
usage: pg-backup-api [-h] {serve,status,recovery} ... | ||
positional arguments: | ||
{serve,status,recovery} | ||
options: | ||
-h, --help show this help message and exit | ||
Postgres Backup API by EnterpriseDB (www.enterprisedb.com) | ||
\ | ||
"""), | ||
"pg-backup-api serve --help": dedent("""\ | ||
usage: pg-backup-api serve [-h] [--port PORT] | ||
Start the REST API server. Listen for requests on '127.0.0.1', on the given port. | ||
options: | ||
-h, --help show this help message and exit | ||
--port PORT Port to bind to. | ||
\ | ||
"""), # noqa: E501 | ||
"pg-backup-api status --help": dedent("""\ | ||
usage: pg-backup-api status [-h] [--port PORT] | ||
Check if the REST API server is up and running | ||
options: | ||
-h, --help show this help message and exit | ||
--port PORT Port to be checked. | ||
\ | ||
"""), # noqa: E501 | ||
"pg-backup-api recovery --help": dedent("""\ | ||
usage: pg-backup-api recovery [-h] --server-name SERVER_NAME --operation-id OPERATION_ID | ||
Perform a 'barman recover' through the 'pg-backup-api'. Can only be run if a recover operation has been previously registered. | ||
options: | ||
-h, --help show this help message and exit | ||
--server-name SERVER_NAME | ||
Name of the Barman server to be recovered. | ||
--operation-id OPERATION_ID | ||
ID of the operation in the 'pg-backup-api'. | ||
\ | ||
"""), # noqa: E501 | ||
} | ||
|
||
_COMMAND_FUNC = { | ||
"pg-backup-api serve": "serve", | ||
"pg-backup-api status": "status", | ||
"pg-backup-api recovery --server-name SOME_SERVER --operation-id SOME_OP_ID": "recovery_operation", # noqa: E501 | ||
} | ||
|
||
|
||
@pytest.mark.parametrize("command", _HELP_OUTPUT.keys()) | ||
def test_main_helper(command, capsys): | ||
"""Test :func:`main`. | ||
Ensure all the ``--help`` calls print the expected content to the console. | ||
""" | ||
with patch("sys.argv", command.split()), pytest.raises(SystemExit) as exc: | ||
main() | ||
|
||
assert str(exc.value) == "0" | ||
|
||
assert capsys.readouterr().out == _HELP_OUTPUT[command] | ||
|
||
|
||
@pytest.mark.parametrize("command", _COMMAND_FUNC.keys()) | ||
@pytest.mark.parametrize("output", [None, "SOME_OUTPUT"]) | ||
@pytest.mark.parametrize("success", [False, True]) | ||
def test_main_funcs(command, output, success, capsys): | ||
"""Test :func:`main`. | ||
Ensure :func:`main` executes the expected functions, print the expected | ||
messages, and exits with the expected codes. | ||
""" | ||
mock_controller = patch(f"pg_backup_api.__main__.{_COMMAND_FUNC[command]}") | ||
mock_func = mock_controller.start() | ||
|
||
mock_func.return_value = (output, success) | ||
|
||
with patch("sys.argv", command.split()), pytest.raises(SystemExit) as exc: | ||
main() | ||
|
||
mock_controller.stop() | ||
|
||
assert capsys.readouterr().out == (f"{output}\n" if output else "") | ||
assert str(exc.value) == ("0" if success else "-1") | ||
|
||
|
||
@patch("argparse.ArgumentParser.parse_args") | ||
def test_main_with_func(mock_parse_args, capsys): | ||
"""Test :func:`main`. | ||
Ensure :func:`main` calls the function with the expected arguments, if a | ||
command has a function associated with it. | ||
""" | ||
mock_parse_args.return_value.func = MagicMock() | ||
mock_func = mock_parse_args.return_value.func | ||
mock_func.return_value = ("SOME_OUTPUT", True) | ||
|
||
with pytest.raises(SystemExit) as exc: | ||
main() | ||
|
||
capsys.readouterr() # so we don't write to stdout during unit tests | ||
|
||
mock_func.assert_called_once_with(mock_parse_args.return_value) | ||
assert str(exc.value) == "0" | ||
|
||
|
||
@patch("argparse.ArgumentParser.print_help") | ||
@patch("argparse.ArgumentParser.parse_args") | ||
def test_main_without_func(mock_parse_args, mock_print_help, capsys): | ||
"""Test :func:`main`. | ||
Ensure :func:`main` prints a helper if a command has no function associated | ||
with it. | ||
""" | ||
delattr(mock_parse_args.return_value, "func") | ||
|
||
with pytest.raises(SystemExit) as exc: | ||
main() | ||
|
||
capsys.readouterr() # so we don't write to stdout during unit tests | ||
|
||
mock_print_help.assert_called_once_with() | ||
assert str(exc.value) == "0" |
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,123 @@ | ||
# -*- coding: utf-8 -*- | ||
# © Copyright EnterpriseDB UK Limited 2021-2023 | ||
# | ||
# This file is part of Postgres Backup API. | ||
# | ||
# Postgres Backup API is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# Postgres Backup API is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with Postgres Backup API. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
"""Unit tests for functions used by the CLI.""" | ||
|
||
import argparse | ||
from requests.exceptions import ConnectionError | ||
from unittest.mock import MagicMock, patch, call | ||
|
||
import pytest | ||
|
||
from pg_backup_api.run import serve, status, recovery_operation | ||
|
||
|
||
@pytest.mark.parametrize("port", [7480, 7481]) | ||
@patch("pg_backup_api.run.output") | ||
@patch("pg_backup_api.run.load_barman_config") | ||
@patch("pg_backup_api.run.app") | ||
def test_serve(mock_app, mock_load_config, mock_output, port): | ||
"""Test :func:`serve`. | ||
Ensure :func:`serve` performs the expected calls and return the expected | ||
values. | ||
""" | ||
mock_output.AVAILABLE_WRITERS.__getitem__.return_value = MagicMock() | ||
expected = mock_output.AVAILABLE_WRITERS.__getitem__.return_value | ||
expected.return_value = MagicMock() | ||
|
||
args = argparse.Namespace(port=port) | ||
|
||
assert serve(args) == (mock_app.run.return_value, True) | ||
|
||
mock_load_config.assert_called_once_with() | ||
mock_output.set_output_writer.assert_called_once_with( | ||
expected.return_value, | ||
) | ||
mock_app.run.assert_called_once_with(host="127.0.0.1", port=port) | ||
|
||
|
||
@pytest.mark.parametrize("port", [7480, 7481]) | ||
@patch("requests.get") | ||
def test_status_ok(mock_request, port): | ||
"""Test :func:`status`. | ||
Ensure the expected ``GET`` request is performed, and that :func:`status` | ||
returns `OK` when the API is available. | ||
""" | ||
args = argparse.Namespace(port=port) | ||
|
||
assert status(args) == ("OK", True) | ||
|
||
mock_request.assert_called_once_with(f"http://127.0.0.1:{port}/status") | ||
|
||
|
||
@pytest.mark.parametrize("port", [7480, 7481]) | ||
@patch("requests.get") | ||
def test_status_failed(mock_request, port): | ||
"""Test :func:`status`. | ||
Ensure the expected ``GET`` request is performed, and that :func:`status` | ||
returns an error message when the API is not available. | ||
""" | ||
args = argparse.Namespace(port=port) | ||
|
||
mock_request.side_effect = ConnectionError("Some Error") | ||
|
||
message = "The Postgres Backup API does not appear to be available." | ||
assert status(args) == (message, False) | ||
|
||
mock_request.assert_called_once_with(f"http://127.0.0.1:{port}/status") | ||
|
||
|
||
@pytest.mark.parametrize("server_name", ["SERVER_1", "SERVER_2"]) | ||
@pytest.mark.parametrize("operation_id", ["OPERATION_1", "OPERATION_2"]) | ||
@pytest.mark.parametrize("rc", [0, 1]) | ||
@patch("pg_backup_api.run.RecoveryOperation") | ||
def test_recovery_operation(mock_rec_op, server_name, operation_id, rc): | ||
"""Test :func:`recovery_operation`. | ||
Ensure the operation is created and executed, and that the expected values | ||
are returned depending on the return code. | ||
""" | ||
args = argparse.Namespace(server_name=server_name, | ||
operation_id=operation_id) | ||
|
||
mock_rec_op.return_value.run.return_value = ("SOME_OUTPUT", rc) | ||
mock_write_output = mock_rec_op.return_value.write_output_file | ||
mock_time_event = mock_rec_op.return_value.time_event_now | ||
mock_read_job = mock_rec_op.return_value.read_job_file | ||
|
||
assert recovery_operation(args) == (mock_write_output.return_value, | ||
rc == 0) | ||
|
||
mock_rec_op.assert_called_once_with(server_name, operation_id) | ||
mock_rec_op.return_value.run.assert_called_once_with() | ||
mock_time_event.assert_called_once_with() | ||
mock_read_job.assert_called_once_with() | ||
|
||
# Make sure the expected content was added to `read_job_file` output before | ||
# writing it to the output file. | ||
assert len(mock_read_job.return_value.__setitem__.mock_calls) == 3 | ||
mock_read_job.return_value.__setitem__.assert_has_calls([ | ||
call('success', rc == 0), | ||
call('end_time', mock_time_event.return_value), | ||
call('output', "SOME_OUTPUT"), | ||
]) | ||
|
||
mock_write_output.assert_called_once_with(mock_read_job.return_value) |
Oops, something went wrong.