301 lines
14 KiB
Python
301 lines
14 KiB
Python
#
|
|
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
#
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
from octavia_cli.apply import commands
|
|
|
|
|
|
@pytest.fixture
|
|
def patch_click(mocker):
|
|
mocker.patch.object(commands, "click")
|
|
|
|
|
|
@pytest.fixture
|
|
def context_object(mock_api_client, mock_telemetry_client):
|
|
return {
|
|
"PROJECT_IS_INITIALIZED": True,
|
|
"API_CLIENT": mock_api_client,
|
|
"WORKSPACE_ID": "workspace_id",
|
|
"TELEMETRY_CLIENT": mock_telemetry_client,
|
|
}
|
|
|
|
|
|
def test_apply_not_initialized():
|
|
runner = CliRunner()
|
|
result = runner.invoke(commands.apply, obj={"PROJECT_IS_INITIALIZED": False})
|
|
assert result.exit_code == 1
|
|
|
|
|
|
def test_apply_without_custom_configuration_file(mocker, context_object):
|
|
runner = CliRunner()
|
|
local_files = ["foo", "bar"]
|
|
mocker.patch.object(commands, "find_local_configuration_files", mocker.Mock(return_value=local_files))
|
|
mock_resources_to_apply = [mocker.Mock(), mocker.Mock()]
|
|
mocker.patch.object(commands, "get_resources_to_apply", mocker.Mock(return_value=mock_resources_to_apply))
|
|
mocker.patch.object(commands, "apply_single_resource")
|
|
result = runner.invoke(commands.apply, obj=context_object)
|
|
assert result.exit_code == 0
|
|
commands.find_local_configuration_files.assert_called_once()
|
|
commands.get_resources_to_apply.assert_called_once_with(local_files, context_object["API_CLIENT"], context_object["WORKSPACE_ID"])
|
|
commands.apply_single_resource([mocker.call(r, False) for r in commands.get_resources_to_apply.return_value])
|
|
|
|
|
|
def test_apply_with_custom_configuration_file(mocker, context_object):
|
|
runner = CliRunner()
|
|
mocker.patch.object(commands, "find_local_configuration_files")
|
|
mocker.patch.object(commands, "get_resources_to_apply")
|
|
mocker.patch.object(commands, "apply_single_resource")
|
|
result = runner.invoke(commands.apply, ["--file", "foo", "--file", "bar"], obj=context_object)
|
|
assert result.exit_code == 0
|
|
commands.find_local_configuration_files.assert_not_called()
|
|
commands.get_resources_to_apply.assert_called_with(("foo", "bar"), context_object["API_CLIENT"], context_object["WORKSPACE_ID"])
|
|
|
|
|
|
def test_apply_with_custom_configuration_file_force(mocker, context_object):
|
|
runner = CliRunner()
|
|
mocker.patch.object(commands, "find_local_configuration_files")
|
|
mocker.patch.object(commands, "get_resources_to_apply", mocker.Mock(return_value=[mocker.Mock()]))
|
|
mocker.patch.object(commands, "apply_single_resource")
|
|
result = runner.invoke(commands.apply, ["--file", "foo", "--file", "bar", "--force"], obj=context_object)
|
|
assert result.exit_code == 0
|
|
commands.apply_single_resource.assert_called_with(commands.get_resources_to_apply.return_value[0], True)
|
|
|
|
|
|
def test_get_resource_to_apply(mocker, mock_api_client):
|
|
local_files_priorities = [("foo", 2), ("bar", 1)]
|
|
mock_resource_factory = mocker.Mock()
|
|
mock_resource_factory.side_effect = [mocker.Mock(APPLY_PRIORITY=priority) for _, priority in local_files_priorities]
|
|
mocker.patch.object(commands, "resource_factory", mock_resource_factory)
|
|
|
|
resources_to_apply = commands.get_resources_to_apply([f[0] for f in local_files_priorities], mock_api_client, "workspace_id")
|
|
assert resources_to_apply == sorted(resources_to_apply, key=lambda r: r.APPLY_PRIORITY)
|
|
assert commands.resource_factory.call_count == len(local_files_priorities)
|
|
commands.resource_factory.assert_has_calls([mocker.call(mock_api_client, "workspace_id", path) for path, _ in local_files_priorities])
|
|
|
|
|
|
@pytest.mark.parametrize("resource_was_created", [True, False])
|
|
def test_apply_single_resource(patch_click, mocker, resource_was_created):
|
|
mocker.patch.object(commands, "update_resource", mocker.Mock(return_value=["updated"]))
|
|
mocker.patch.object(commands, "create_resource", mocker.Mock(return_value=["created"]))
|
|
resource = mocker.Mock(was_created=resource_was_created, resource_name="my_resource_name")
|
|
force = mocker.Mock()
|
|
commands.apply_single_resource(resource, force)
|
|
if resource_was_created:
|
|
commands.update_resource.assert_called_once_with(resource, force)
|
|
commands.create_resource.assert_not_called()
|
|
expected_message = (
|
|
"🐙 - my_resource_name exists on your Airbyte instance according to your state file, let's check if we need to update it!"
|
|
)
|
|
expected_message_color = "yellow"
|
|
expected_echo_calls = [mocker.call(commands.click.style.return_value), mocker.call("\n".join(["updated"]))]
|
|
else:
|
|
commands.update_resource.assert_not_called()
|
|
commands.create_resource.assert_called_once_with(resource)
|
|
expected_message = "🐙 - my_resource_name does not exists on your Airbyte instance, let's create it!"
|
|
expected_message_color = "green"
|
|
expected_echo_calls = [mocker.call(commands.click.style.return_value), mocker.call("\n".join(["created"]))]
|
|
commands.click.style.assert_called_with(expected_message, fg=expected_message_color)
|
|
commands.click.echo.assert_has_calls(expected_echo_calls)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"force,user_validation,local_file_changed,expect_update,expected_reason",
|
|
[
|
|
pytest.param(
|
|
True, True, True, True, "🚨 - Running update because the force mode is activated.", id="1 - Check if force has the top priority."
|
|
),
|
|
pytest.param(
|
|
True,
|
|
False,
|
|
True,
|
|
True,
|
|
"🚨 - Running update because the force mode is activated.",
|
|
id="2 - Check if force has the top priority.",
|
|
),
|
|
pytest.param(
|
|
True,
|
|
False,
|
|
False,
|
|
True,
|
|
"🚨 - Running update because the force mode is activated.",
|
|
id="3 - Check if force has the top priority.",
|
|
),
|
|
pytest.param(
|
|
True,
|
|
True,
|
|
False,
|
|
True,
|
|
"🚨 - Running update because the force mode is activated.",
|
|
id="4 - Check if force has the top priority.",
|
|
),
|
|
pytest.param(
|
|
False,
|
|
True,
|
|
True,
|
|
True,
|
|
"🟢 - Running update because you validated the changes.",
|
|
id="Check if user validation has priority over local file change.",
|
|
),
|
|
pytest.param(
|
|
False,
|
|
False,
|
|
True,
|
|
False,
|
|
"🔴 - Did not update because you refused the changes.",
|
|
id="Check if user validation has priority over local file change.",
|
|
),
|
|
pytest.param(
|
|
False,
|
|
None,
|
|
True,
|
|
True,
|
|
"🟡 - Running update because a local file change was detected and a secret field might have been edited.",
|
|
id="Check if local_file_changed runs even if user validation is None.",
|
|
),
|
|
pytest.param(
|
|
False,
|
|
None,
|
|
False,
|
|
False,
|
|
"😴 - Did not update because no change detected.",
|
|
id="Check no update if no local change and user validation is None.",
|
|
),
|
|
],
|
|
)
|
|
def test_should_update_resource(patch_click, mocker, force, user_validation, local_file_changed, expect_update, expected_reason):
|
|
should_update, update_reason = commands.should_update_resource(force, user_validation, local_file_changed)
|
|
assert should_update == expect_update
|
|
assert update_reason == commands.click.style.return_value
|
|
commands.click.style.assert_called_with(expected_reason, fg="green")
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"diff,expected_number_calls_to_display_diff_line",
|
|
[("", 0), ("First diff line", 1), ("First diff line\nSecond diff line", 2), ("First diff line\nSecond diff line\nThird diff line", 3)],
|
|
)
|
|
def test_prompt_for_diff_validation(patch_click, mocker, diff, expected_number_calls_to_display_diff_line):
|
|
mocker.patch.object(commands, "display_diff_line")
|
|
output = commands.prompt_for_diff_validation("my_resource", diff)
|
|
assert commands.display_diff_line.call_count == expected_number_calls_to_display_diff_line
|
|
if diff and expected_number_calls_to_display_diff_line > 0:
|
|
commands.display_diff_line.assert_has_calls([mocker.call(line) for line in diff.split("\n")])
|
|
commands.click.style.assert_has_calls(
|
|
[
|
|
mocker.call(
|
|
"👀 - Here's the computed diff (🚨 remind that diff on secret fields are not displayed):", fg="magenta", bold=True
|
|
),
|
|
mocker.call("❓ - Do you want to update my_resource?", bold=True),
|
|
]
|
|
)
|
|
commands.click.echo.assert_called_with(commands.click.style.return_value)
|
|
assert output == commands.click.confirm.return_value
|
|
else:
|
|
assert output is False
|
|
|
|
|
|
def test_create_resource(patch_click, mocker):
|
|
mock_created_resource = mocker.Mock()
|
|
mock_state = mocker.Mock()
|
|
mock_resource = mocker.Mock(create=mocker.Mock(return_value=(mock_created_resource, mock_state)))
|
|
output_messages = commands.create_resource(mock_resource)
|
|
mock_resource.create.assert_called_once()
|
|
assert output_messages == [commands.click.style.return_value, commands.click.style.return_value]
|
|
commands.click.style.assert_has_calls(
|
|
[
|
|
mocker.call(f"🎉 - Successfully created {mock_created_resource.name} on your Airbyte instance!", fg="green", bold=True),
|
|
mocker.call(f"💾 - New state for {mock_created_resource.name} saved at {mock_state.path}", fg="yellow"),
|
|
]
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"force,diff,local_file_changed,expect_prompt,user_validation,expect_update",
|
|
[
|
|
pytest.param(True, True, True, False, False, True, id="Force, diff, local file change -> no prompt, no validation, expect update."),
|
|
pytest.param(
|
|
True, True, False, False, False, True, id="Force, diff, no local file change -> no prompt, no validation, expect update."
|
|
),
|
|
pytest.param(
|
|
True, False, False, False, False, True, id="Force, no diff, no local file change -> no prompt, no validation, expect update."
|
|
),
|
|
pytest.param(
|
|
True, False, True, False, False, True, id="Force, no diff, local file change -> no prompt, no validation, expect update."
|
|
),
|
|
pytest.param(
|
|
False, True, True, True, True, True, id="No force, diff, local file change -> expect prompt, validation, expect update."
|
|
),
|
|
pytest.param(
|
|
False, True, True, True, False, False, id="No force, diff, local file change -> expect prompt, no validation, no update."
|
|
),
|
|
pytest.param(
|
|
False, True, False, True, True, True, id="No force, diff, no local file change -> expect prompt, validation, expect update."
|
|
),
|
|
pytest.param(
|
|
False, True, False, True, False, False, id="No force, diff, no local file change -> expect prompt, no validation, no update."
|
|
),
|
|
pytest.param(
|
|
False, False, True, False, False, True, id="No force, no diff, local file change -> no prompt, no validation, expect update."
|
|
),
|
|
pytest.param(
|
|
False, False, False, False, False, False, id="No force, no diff, no local file change -> no prompt, no validation, no update."
|
|
),
|
|
],
|
|
)
|
|
def test_update_resource(patch_click, mocker, force, diff, local_file_changed, expect_prompt, user_validation, expect_update):
|
|
mock_updated_resource = mocker.Mock()
|
|
mock_state = mocker.Mock()
|
|
mock_resource = mocker.Mock(
|
|
get_diff_with_remote_resource=mocker.Mock(return_value=diff),
|
|
resource_name="my_resource",
|
|
local_file_changed=local_file_changed,
|
|
update=mocker.Mock(return_value=(mock_updated_resource, mock_state)),
|
|
)
|
|
mocker.patch.object(commands, "prompt_for_diff_validation", mocker.Mock(return_value=user_validation))
|
|
|
|
output_messages = commands.update_resource(mock_resource, force)
|
|
commands.click.echo.assert_called_once()
|
|
|
|
if expect_prompt:
|
|
commands.prompt_for_diff_validation.assert_called_once_with("my_resource", diff)
|
|
else:
|
|
commands.prompt_for_diff_validation.assert_not_called()
|
|
if expect_update:
|
|
mock_resource.update.assert_called_once()
|
|
else:
|
|
mock_resource.update.assert_not_called()
|
|
|
|
if expect_update:
|
|
assert output_messages == [
|
|
commands.click.style.return_value,
|
|
commands.click.style.return_value,
|
|
]
|
|
commands.click.style.assert_has_calls(
|
|
[
|
|
mocker.call(f"🎉 - Successfully updated {mock_updated_resource.name} on your Airbyte instance!", fg="green", bold=True),
|
|
mocker.call(f"💾 - New state for {mock_updated_resource.name} stored at {mock_state.path}.", fg="yellow"),
|
|
]
|
|
)
|
|
else:
|
|
assert output_messages == []
|
|
|
|
|
|
def test_find_local_configuration_files(mocker):
|
|
project_directories = ["sources", "connections", "destinations"]
|
|
mocker.patch.object(commands, "REQUIRED_PROJECT_DIRECTORIES", project_directories)
|
|
mocker.patch.object(commands, "glob", mocker.Mock(return_value=["foo.yaml"]))
|
|
configuration_files = commands.find_local_configuration_files()
|
|
assert isinstance(configuration_files, list)
|
|
commands.glob.assert_has_calls([mocker.call(f"./{directory}/**/configuration.yaml") for directory in project_directories])
|
|
assert configuration_files == ["foo.yaml" for _ in range(len(project_directories))]
|
|
|
|
|
|
def test_find_local_configuration_files_no_file_found(patch_click, mocker):
|
|
project_directories = ["sources", "connections", "destinations"]
|
|
mocker.patch.object(commands, "REQUIRED_PROJECT_DIRECTORIES", project_directories)
|
|
mocker.patch.object(commands, "glob", mocker.Mock(return_value=[]))
|
|
configuration_files = commands.find_local_configuration_files()
|
|
assert not configuration_files
|
|
commands.click.style.assert_called_once_with("😒 - No YAML file found to run apply.", fg="red")
|