← Files Dataiku HeadlessARCHIVED FILE

tests/test_plugin_futures.py

4.73 KB · Nov 11, 2026 · 12:58 UTC

↓ Download file

See the change to this file →

# Copyright 2026 Dataiku SAS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Plugin future follow-up must not expose settings or Git error output."""

import asyncio
import json
from unittest.mock import patch

import pytest
from dataikuapi import DSSClient
from dataikuapi.utils import DataikuException

from dataiku_mcp.tools import jobs
from tests.utils.fakes import FakeContext


@pytest.mark.parametrize("fetch_result", [False, True])
@pytest.mark.parametrize("failure", [False, True])
def test_plugin_future_redacts_logs_errors_and_settings(fetch_result, failure):
    client = DSSClient("https://synthetic.example.com", "synthetic-key")

    def response(*args, **kwargs):
        return {
            "jobId": "F",
            "alive": False,
            "hasResult": True,
            "payload": {"action": "plugin_install", "targets": ["SYNTHETIC_SECRET"]},
            "log": "SYNTHETIC_SECRET",
            "result": {
                "success": not failure,
                "needsReload": True,
                "pluginDesc": {"desc": {"id": "fixture", "version": "1.0"}},
                "settings": {"token": "SYNTHETIC_SECRET"},
                **(
                    {
                        "installationError": {
                            "message": "https://user:SYNTHETIC_SECRET@example.com/repo"
                        }
                    }
                    if failure
                    else {}
                ),
            },
        }

    with (
        patch.object(client, "_perform_json", side_effect=response),
        patch.object(jobs, "get_dss_client", return_value=client),
    ):
        result = json.loads(
            asyncio.run(
                jobs.get_future_status("F", FakeContext(), fetch_result=fetch_result)
            )
        )
    assert "SYNTHETIC_SECRET" not in json.dumps(result)
    receipt = result["state"]["result"]
    assert receipt["success"] is not failure
    assert receipt["pluginDesc"]["desc"]["id"] == "fixture"
    assert ("error" in receipt) is failure


@pytest.mark.parametrize("stage", ["peek", "get"])
def test_future_transport_error_cannot_expose_git_output(stage):
    client = DSSClient("https://synthetic.example.com", "synthetic-key")

    def response(*args, **kwargs):
        if stage == "peek" or not kwargs["params"]["peek"]:
            raise DataikuException("Git failed: SYNTHETIC_SECRET")
        return {"payload": {"action": "plugin_install"}}

    with (
        patch.object(client, "_perform_json", side_effect=response),
        patch.object(jobs, "get_dss_client", return_value=client),
        pytest.raises(RuntimeError) as error,
    ):
        asyncio.run(jobs.get_future_status("F", FakeContext(), fetch_result=True))
    assert "SYNTHETIC_SECRET" not in str(error.value)


def test_other_future_results_are_preserved():
    client = DSSClient("https://synthetic.example.com", "synthetic-key")
    state = {"payload": {"action": "other"}, "result": {"value": 42}}
    with (
        patch.object(client, "_perform_json", return_value=state),
        patch.object(jobs, "get_dss_client", return_value=client),
    ):
        result = json.loads(
            asyncio.run(jobs.get_future_status("F", FakeContext(), fetch_result=True))
        )
    assert result["state"] == state


def test_plugin_future_preserves_progress_without_exposing_other_details():
    client = DSSClient("https://synthetic.example.com", "synthetic-key")
    progress = {"name": "Building CDE image", "unit": "images", "cur": 2, "target": 5}
    state = {
        "alive": True,
        "hasResult": False,
        "runningTime": 600000,
        "payload": {"action": "plugin_install", "targets": ["SYNTHETIC_SECRET"]},
        "progress": {
            "states": [{**progress, "details": "SYNTHETIC_SECRET"}],
            "extra": "SYNTHETIC_SECRET",
        },
        "log": "SYNTHETIC_SECRET",
    }
    with (
        patch.object(client, "_perform_json", return_value=state),
        patch.object(jobs, "get_dss_client", return_value=client),
    ):
        result = json.loads(asyncio.run(jobs.get_future_status("F", FakeContext())))
    assert result["state"]["runningTime"] == 600000
    assert result["state"]["progress"] == {"states": [progress]}
    assert result["state"]["alive"] is True
    assert "SYNTHETIC_SECRET" not in json.dumps(result)

SHA-256: 75d9ed23b3eb280ddc31bde4819479b5ec190989ac09f35ec7e52b23ef2bf59b