Repository: getindata/kedro-kubeflow
Pull request #33 (2021-01-12 06:45:30)

Added unittests for CLI
Unit tests for CLI 

em-pe: Docs are failing as expected as they're not merged yet
szczeles: Looks nice!

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d04dd09..7574dcd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## [Unreleased]
 
+### Changed
+
+- Increased test coverage for the CLI handling
+
 ## [0.1.10] - 2021-01-11
 
 ### Added
diff --git a/README.md b/README.md
index a3e26f9..4c786c9 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@
 
 [![Maintainability](https://api.codeclimate.com/v1/badges/fff07cbd2e5012a045a3/maintainability)](https://codeclimate.com/github/getindata/kedro-kubeflow/maintainability) 
 [![Test Coverage](https://api.codeclimate.com/v1/badges/fff07cbd2e5012a045a3/test_coverage)](https://codeclimate.com/github/getindata/kedro-kubeflow/test_coverage)
+[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fgetindata%2Fkedro-kubeflow.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fgetindata%2Fkedro-kubeflow?ref=badge_shield)
 ## About
 
 The main purpose of this plugin is to enable running kedro pipeline on Kubeflow Pipelines. It supports translation from 
diff --git a/kedro_kubeflow/plugin.py b/kedro_kubeflow/cli.py
similarity index 98%
rename from kedro_kubeflow/plugin.py
rename to kedro_kubeflow/cli.py
index 10ec139..7fbc5d8 100644
--- a/kedro_kubeflow/plugin.py
+++ b/kedro_kubeflow/cli.py
@@ -1,3 +1,4 @@
+import webbrowser
 from pathlib import Path
 
 import click
@@ -70,9 +71,7 @@ def run_once(ctx, image: str, pipeline: str):
 @click.pass_context
 def ui(ctx) -> None:
     """Open Kubeflow Pipelines UI in new browser tab"""
-    import webbrowser
-
-    host = ctx.obj["context_helper"].config["host"]
+    host = ctx.obj["context_helper"].config.host
     webbrowser.open_new_tab(host)
 
 
diff --git a/requirements.txt b/requirements.txt
index 97f1530..8f3e9a5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,4 @@ click
 kfp
 tabulate
 kedro-mlflow
+semver
diff --git a/setup.py b/setup.py
index 8536888..95ff8ba 100644
--- a/setup.py
+++ b/setup.py
@@ -38,9 +38,7 @@ setup(
     install_requires=INSTALL_REQUIRES,
     extras_require=EXTRA_REQUIRE,
     entry_points={
-        "kedro.project_commands": [
-            "kubeflow = kedro_kubeflow.plugin:commands"
-        ],
+        "kedro.project_commands": ["kubeflow = kedro_kubeflow.cli:commands"],
         "kedro.hooks": [
             "kubeflow_cfg_hook = kedro_kubeflow.hooks:register_templated_config_loader",
         ],


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
new file mode 100644
index 0000000..a14a3cf
--- /dev/null
+++ b/tests/test_cli.py
@@ -0,0 +1,150 @@
+import os
+import unittest
+from pathlib import Path
+from tempfile import TemporaryDirectory
+from unittest.mock import MagicMock, patch
+
+from click.testing import CliRunner
+
+from kedro_kubeflow.cli import (
+    compile,
+    init,
+    list_pipelines,
+    run_once,
+    schedule,
+    ui,
+    upload_pipeline,
+)
+from kedro_kubeflow.config import PluginConfig
+from kedro_kubeflow.context_helper import ContextHelper
+
+test_config = PluginConfig(
+    {
+        "host": "https://example.com",
+        "run_config": {
+            "image": "gcr.io/project-image/test",
+            "image_pull_policy": "Always",
+            "experiment_name": "Test Experiment",
+            "run_name": "test run",
+            "wait_for_completion": True,
+            "volume": {
+                "storageclass": "default",
+                "size": "3Gi",
+                "access_modes": "[ReadWriteOnce]",
+            },
+        },
+    }
+)
+
+
+class TestPluginCLI(unittest.TestCase):
+    def test_list_pipelines(self):
+        context_helper = MagicMock(ContextHelper)
+        config = dict(context_helper=context_helper)
+        runner = CliRunner()
+
+        result = runner.invoke(list_pipelines, [], obj=config)
+
+        assert result.exit_code == 0
+        context_helper.kfp_client.list_pipelines.assert_called_with()
+
+    def test_run_once(self):
+        context_helper = MagicMock(ContextHelper)
+        context_helper.config = test_config
+        config = dict(context_helper=context_helper)
+        runner = CliRunner()
+
+        result = runner.invoke(
+            run_once, ["-i", "new_img", "-p", "new_pipe"], obj=config
+        )
+
+        assert result.exit_code == 0
+        context_helper.kfp_client.run_once.assert_called_with(
+            experiment_name="Test Experiment",
+            image="new_img",
+            image_pull_policy="Always",
+            pipeline="new_pipe",
+            run_name="test run",
+            wait=True,
+        )
+
+    @patch("webbrowser.open_new_tab")
+    def test_ui(self, open_new_tab):
+        context_helper = MagicMock(ContextHelper)
+        context_helper.config = test_config
+        config = dict(context_helper=context_helper)
+        runner = CliRunner()
+
+        result = runner.invoke(ui, [], obj=config)
+
+        assert result.exit_code == 0
+        open_new_tab.assert_called_with("https://example.com")
+
+    def test_compile(self):
+        context_helper = MagicMock(ContextHelper)
+        context_helper.config = test_config
+        config = dict(context_helper=context_helper)
+        runner = CliRunner()
+
+        result = runner.invoke(
+            compile, ["-p", "pipe", "-i", "img", "-o", "output"], obj=config
+        )
+
+        assert result.exit_code == 0
+        context_helper.kfp_client.compile.assert_called_with(
+            image="img",
+            image_pull_policy="Always",
+            output="output",
+            pipeline="pipe",
+        )
+
+    def test_upload_pipeline(self):
+        context_helper = MagicMock(ContextHelper)
+        context_helper.config = test_config
+        config = dict(context_helper=context_helper)
+        runner = CliRunner()
+
+        result = runner.invoke(
+            upload_pipeline, ["-p", "pipe", "-i", "img"], obj=config
+        )
+
+        assert result.exit_code == 0
+        context_helper.kfp_client.upload.assert_called_with(
+            image="img", image_pull_policy="Always", pipeline="pipe"
+        )
+
+    def test_schedule(self):
+        context_helper = MagicMock(ContextHelper)
+        context_helper.config = test_config
+        config = dict(context_helper=context_helper)
+        runner = CliRunner()
+
+        result = runner.invoke(
+            schedule, ["-c", "* * *", "-x", "test_experiment"], obj=config
+        )
+
+        assert result.exit_code == 0
+        context_helper.kfp_client.schedule.assert_called_with(
+            "test_experiment", "* * *"
+        )
+
+    @patch.object(Path, "cwd")
+    def test_init(self, cwd):
+        context_helper = MagicMock(ContextHelper)
+        context_helper.config = test_config
+        context_helper.context.project_name = "Test Project"
+        context_helper.context.project_path.name = "test_project_path"
+        config = dict(context_helper=context_helper)
+        runner = CliRunner()
+
+        with TemporaryDirectory() as temp_dir:
+            path = Path(temp_dir)
+            cwd.return_value = path
+            os.makedirs(path.joinpath("conf/base"))
+            result = runner.invoke(init, ["http:/kubeflow"], obj=config)
+
+            assert result.exit_code == 0
+            assert result.output.startswith("Configuration generated in ")
+            with open(path.joinpath("conf/base/kubeflow.yaml"), "r") as f:
+                content = "\n".join(f.readlines())
+                assert "host: http:/kubeflow" in content