You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			438 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			438 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
# Copyright (c) Jupyter Development Team.
 | 
						|
# Distributed under the terms of the Modified BSD License.
 | 
						|
 | 
						|
import json
 | 
						|
from unittest.mock import Mock, patch
 | 
						|
 | 
						|
import pytest
 | 
						|
from traitlets.config import Config, Configurable
 | 
						|
 | 
						|
from jupyterlab.extensions import PyPIExtensionManager, ReadOnlyExtensionManager
 | 
						|
from jupyterlab.extensions.manager import ExtensionManager, ExtensionPackage, PluginManager
 | 
						|
 | 
						|
from . import fake_client_factory
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.parametrize(
 | 
						|
    "version, expected",
 | 
						|
    (
 | 
						|
        ("1", "1"),
 | 
						|
        ("1.0", "1.0"),
 | 
						|
        ("1.0.0", "1.0.0"),
 | 
						|
        ("1.0.0a52", "1.0.0-alpha.52"),
 | 
						|
        ("1.0.0b3", "1.0.0-beta.3"),
 | 
						|
        ("1.0.0rc22", "1.0.0-rc.22"),
 | 
						|
        ("1.0.0rc23.post2", "1.0.0-rc.23"),
 | 
						|
        ("1.0.0rc24.dev2", "1.0.0-rc.24"),
 | 
						|
        ("1.0.0rc25.post4.dev2", "1.0.0-rc.25"),
 | 
						|
    ),
 | 
						|
)
 | 
						|
def test_ExtensionManager_get_semver_version(version, expected):
 | 
						|
    assert ExtensionManager.get_semver_version(version) == expected
 | 
						|
 | 
						|
 | 
						|
async def test_ExtensionManager_list_extensions_installed(monkeypatch):
 | 
						|
    extension1 = ExtensionPackage("extension1", "Extension 1 description", "", "prebuilt")
 | 
						|
 | 
						|
    async def mock_installed(*args, **kwargs):
 | 
						|
        return {"extension1": extension1}
 | 
						|
 | 
						|
    monkeypatch.setattr(ReadOnlyExtensionManager, "_get_installed_extensions", mock_installed)
 | 
						|
 | 
						|
    manager = ReadOnlyExtensionManager()
 | 
						|
 | 
						|
    extensions = await manager.list_extensions()
 | 
						|
 | 
						|
    assert extensions == ([extension1], 1)
 | 
						|
 | 
						|
 | 
						|
async def test_ExtensionManager_list_extensions_query(monkeypatch):
 | 
						|
    extension1 = ExtensionPackage("extension1", "Extension 1 description", "", "prebuilt")
 | 
						|
    extension2 = ExtensionPackage("extension2", "Extension 2 description", "", "prebuilt")
 | 
						|
 | 
						|
    async def mock_list(*args, **kwargs):
 | 
						|
        return {"extension1": extension1, "extension2": extension2}, None
 | 
						|
 | 
						|
    monkeypatch.setattr(ReadOnlyExtensionManager, "list_packages", mock_list)
 | 
						|
 | 
						|
    manager = ReadOnlyExtensionManager()
 | 
						|
 | 
						|
    extensions = await manager.list_extensions("ext")
 | 
						|
 | 
						|
    assert extensions == ([extension1, extension2], 1)
 | 
						|
 | 
						|
 | 
						|
@patch("tornado.httpclient.AsyncHTTPClient", new_callable=fake_client_factory)
 | 
						|
async def test_ExtensionManager_list_extensions_query_allow(mock_client, monkeypatch):
 | 
						|
    extension1 = ExtensionPackage("extension1", "Extension 1 description", "", "prebuilt")
 | 
						|
    extension2 = ExtensionPackage("extension2", "Extension 2 description", "", "prebuilt")
 | 
						|
 | 
						|
    mock_client.body = json.dumps({"allowed_extensions": [{"name": "extension1"}]}).encode()
 | 
						|
 | 
						|
    async def mock_list(*args, **kwargs):
 | 
						|
        return {"extension1": extension1, "extension2": extension2}, None
 | 
						|
 | 
						|
    monkeypatch.setattr(ReadOnlyExtensionManager, "list_packages", mock_list)
 | 
						|
 | 
						|
    manager = ReadOnlyExtensionManager(
 | 
						|
        ext_options={"allowed_extensions_uris": {"http://dummy-allowed-extension"}},
 | 
						|
    )
 | 
						|
 | 
						|
    extensions = await manager.list_extensions("ext")
 | 
						|
 | 
						|
    assert extensions == ([extension1], 1)
 | 
						|
 | 
						|
 | 
						|
@patch("tornado.httpclient.AsyncHTTPClient", new_callable=fake_client_factory)
 | 
						|
async def test_ExtensionManager_list_extensions_query_block(mock_client, monkeypatch):
 | 
						|
    extension1 = ExtensionPackage("extension1", "Extension 1 description", "", "prebuilt")
 | 
						|
    extension2 = ExtensionPackage("extension2", "Extension 2 description", "", "prebuilt")
 | 
						|
 | 
						|
    mock_client.body = json.dumps({"blocked_extensions": [{"name": "extension1"}]}).encode()
 | 
						|
 | 
						|
    async def mock_list(*args, **kwargs):
 | 
						|
        return {"extension1": extension1, "extension2": extension2}, None
 | 
						|
 | 
						|
    monkeypatch.setattr(ReadOnlyExtensionManager, "list_packages", mock_list)
 | 
						|
 | 
						|
    manager = ReadOnlyExtensionManager(
 | 
						|
        ext_options={"blocked_extensions_uris": {"http://dummy-blocked-extension"}}
 | 
						|
    )
 | 
						|
 | 
						|
    extensions = await manager.list_extensions("ext")
 | 
						|
 | 
						|
    assert extensions == ([extension2], 1)
 | 
						|
 | 
						|
 | 
						|
@patch("tornado.httpclient.AsyncHTTPClient", new_callable=fake_client_factory)
 | 
						|
async def test_ExtensionManager_list_extensions_query_allow_block(mock_client, monkeypatch):
 | 
						|
    extension1 = ExtensionPackage("extension1", "Extension 1 description", "", "prebuilt")
 | 
						|
    extension2 = ExtensionPackage("extension2", "Extension 2 description", "", "prebuilt")
 | 
						|
 | 
						|
    mock_client.body = json.dumps(
 | 
						|
        {
 | 
						|
            "allowed_extensions": [{"name": "extension1"}],
 | 
						|
            "blocked_extensions": [{"name": "extension1"}],
 | 
						|
        }
 | 
						|
    ).encode()
 | 
						|
 | 
						|
    async def mock_list(*args, **kwargs):
 | 
						|
        return {"extension1": extension1, "extension2": extension2}, None
 | 
						|
 | 
						|
    monkeypatch.setattr(ReadOnlyExtensionManager, "list_packages", mock_list)
 | 
						|
 | 
						|
    manager = ReadOnlyExtensionManager(
 | 
						|
        ext_options={
 | 
						|
            "allowed_extensions_uris": {"http://dummy-allowed-extension"},
 | 
						|
            "blocked_extensions_uris": {"http://dummy-blocked-extension"},
 | 
						|
        }
 | 
						|
    )
 | 
						|
 | 
						|
    extensions = await manager.list_extensions("ext")
 | 
						|
 | 
						|
    assert extensions == ([extension1], 1)
 | 
						|
 | 
						|
 | 
						|
async def test_ExtensionManager_install():
 | 
						|
    manager = ReadOnlyExtensionManager()
 | 
						|
 | 
						|
    result = await manager.install("extension1")
 | 
						|
 | 
						|
    assert result.status == "error"
 | 
						|
    assert result.message == "Extension installation not supported."
 | 
						|
 | 
						|
 | 
						|
async def test_ExtensionManager_uninstall():
 | 
						|
    manager = ReadOnlyExtensionManager()
 | 
						|
 | 
						|
    result = await manager.uninstall("extension1")
 | 
						|
 | 
						|
    assert result.status == "error"
 | 
						|
    assert result.message == "Extension removal not supported."
 | 
						|
 | 
						|
 | 
						|
@patch("jupyterlab.extensions.pypi.xmlrpc.client")
 | 
						|
async def test_ExtensionManager_list_extensions_query_sort(mocked_rpcclient):
 | 
						|
    extension_data = [
 | 
						|
        {
 | 
						|
            "name": "jupyterlab-apod",
 | 
						|
            "project_urls": {
 | 
						|
                "Homepage": "https://github.com/jupyterlab/jupyterlab_apod",
 | 
						|
            },
 | 
						|
        },
 | 
						|
        {
 | 
						|
            "name": "jupyterlab-gitlab",
 | 
						|
            "project_urls": {
 | 
						|
                "Homepage": "https://github.com/jupyterlab-contrib/jupyterlab-gitlab/issues",
 | 
						|
            },
 | 
						|
        },
 | 
						|
        {
 | 
						|
            "name": "jupyterlab-git",
 | 
						|
            "project_url": "https://github.com/jupyterlab/jupyterlab-git",
 | 
						|
        },
 | 
						|
        {
 | 
						|
            "name": "jupyterlab-rainbow-brackets",
 | 
						|
            "project_url": "https://github.com/krassowski/jupyterlab-rainbow-brackets",
 | 
						|
        },
 | 
						|
        {"name": "nbdime", "home_page": "https://github.com/jupyter/nbdime"},
 | 
						|
        {
 | 
						|
            "name": "rise",
 | 
						|
            "project_urls": {
 | 
						|
                "Source Code": "https://github.com/jupyterlab-contrib/rise",
 | 
						|
            },
 | 
						|
        },
 | 
						|
    ]
 | 
						|
 | 
						|
    proxy = Mock(
 | 
						|
        browse=Mock(return_value=[[extension["name"], "1.0.0"] for extension in extension_data]),
 | 
						|
    )
 | 
						|
    mocked_rpcclient.ServerProxy = Mock(return_value=proxy)
 | 
						|
 | 
						|
    manager = PyPIExtensionManager()
 | 
						|
 | 
						|
    extensions = {
 | 
						|
        extension["name"]: {"version": "1.0.0", **extension} for extension in extension_data
 | 
						|
    }
 | 
						|
 | 
						|
    async def mock_pkg_metadata(name, l, b):  # noqa
 | 
						|
        return extensions[name]
 | 
						|
 | 
						|
    manager._fetch_package_metadata = mock_pkg_metadata
 | 
						|
 | 
						|
    first_page, pages_count = await manager.list_extensions("", per_page=3)
 | 
						|
    assert [extension.name for extension in first_page] == [
 | 
						|
        # jupyter/jupyterlab
 | 
						|
        "jupyterlab-git",
 | 
						|
        "nbdime",
 | 
						|
        # jupyterlab-contrib
 | 
						|
        "jupyterlab-gitlab",
 | 
						|
    ]
 | 
						|
    assert pages_count == 2
 | 
						|
    second_page, pages_count = await manager.list_extensions("", page=2, per_page=3)
 | 
						|
    assert [extension.name for extension in second_page] == [
 | 
						|
        # jupyterlab-contrib
 | 
						|
        "rise",
 | 
						|
        # other third-party
 | 
						|
        "jupyterlab-rainbow-brackets",
 | 
						|
        # example extensions
 | 
						|
        "jupyterlab-apod",
 | 
						|
    ]
 | 
						|
 | 
						|
 | 
						|
@patch("jupyterlab.extensions.pypi.xmlrpc.client")
 | 
						|
async def test_PyPiExtensionManager_list_extensions_query(mocked_rpcclient):
 | 
						|
    extension1 = ExtensionPackage(
 | 
						|
        name="jupyterlab-git",
 | 
						|
        description="A JupyterLab extension for version control using git",
 | 
						|
        homepage_url="https://github.com/jupyterlab/jupyterlab-git",
 | 
						|
        pkg_type="prebuilt",
 | 
						|
        latest_version="0.37.1",
 | 
						|
        author="Jupyter Development Team",
 | 
						|
        license="BSD-3-Clause",
 | 
						|
        package_manager_url="https://pypi.org/project/jupyterlab-git/",
 | 
						|
    )
 | 
						|
    extension2 = ExtensionPackage(
 | 
						|
        name="jupyterlab-github",
 | 
						|
        description="JupyterLab viewer for GitHub repositories",
 | 
						|
        homepage_url="https://github.com/jupyterlab/jupyterlab-github/blob/main/README.md",
 | 
						|
        pkg_type="prebuilt",
 | 
						|
        latest_version="3.0.1",
 | 
						|
        author="Ian Rose",
 | 
						|
        license="BSD-3-Clause",
 | 
						|
        bug_tracker_url="https://github.com/jupyterlab/jupyterlab-github/issues",
 | 
						|
        package_manager_url="https://pypi.org/project/jupyterlab-github/",
 | 
						|
        repository_url="https://github.com/jupyterlab/jupyterlab-github",
 | 
						|
    )
 | 
						|
 | 
						|
    proxy = Mock(
 | 
						|
        browse=Mock(
 | 
						|
            return_value=[
 | 
						|
                ["jupyterlab-git", "0.33.0"],
 | 
						|
                ["jupyterlab-git", "0.34.0"],
 | 
						|
                ["jupyterlab-git", "0.34.1"],
 | 
						|
                ["jupyterlab-git", "0.37.0"],
 | 
						|
                ["jupyterlab-git", "0.37.1"],
 | 
						|
                ["jupyterlab-github", "3.0.0"],
 | 
						|
                ["jupyterlab-github", "3.0.1"],
 | 
						|
            ]
 | 
						|
        ),
 | 
						|
    )
 | 
						|
    mocked_rpcclient.ServerProxy = Mock(return_value=proxy)
 | 
						|
 | 
						|
    manager = PyPIExtensionManager()
 | 
						|
 | 
						|
    async def mock_pkg_metadata(n, l, b):  # noqa
 | 
						|
        return (
 | 
						|
            {
 | 
						|
                "name": "jupyterlab-git",
 | 
						|
                "version": "0.37.1",
 | 
						|
                "stable_version": None,
 | 
						|
                "bugtrack_url": None,
 | 
						|
                "package_url": "https://pypi.org/project/jupyterlab-git/",
 | 
						|
                "release_url": "https://pypi.org/project/jupyterlab-git/0.37.1/",
 | 
						|
                "docs_url": None,
 | 
						|
                "home_page": "https://github.com/jupyterlab/jupyterlab-git",
 | 
						|
                "download_url": "",
 | 
						|
                "project_url": "",
 | 
						|
                "project_urls": {},
 | 
						|
                "author": "Jupyter Development Team",
 | 
						|
                "author_email": "",
 | 
						|
                "maintainer": "",
 | 
						|
                "maintainer_email": "",
 | 
						|
                "summary": "A JupyterLab extension for version control using git",
 | 
						|
                "license": "BSD-3-Clause",
 | 
						|
                "keywords": "Jupyter,JupyterLab,JupyterLab3,jupyterlab-extension,Git",
 | 
						|
                "platform": "Linux",
 | 
						|
                "classifiers": [
 | 
						|
                    "Framework :: Jupyter",
 | 
						|
                    "Framework :: Jupyter :: JupyterLab",
 | 
						|
                    "Framework :: Jupyter :: JupyterLab :: 3",
 | 
						|
                    "Framework :: Jupyter :: JupyterLab :: Extensions",
 | 
						|
                    "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
 | 
						|
                    "Intended Audience :: Developers",
 | 
						|
                    "Intended Audience :: Science/Research",
 | 
						|
                    "License :: OSI Approved :: BSD License",
 | 
						|
                    "Programming Language :: Python",
 | 
						|
                    "Programming Language :: Python :: 3",
 | 
						|
                    "Programming Language :: Python :: 3.10",
 | 
						|
                    "Programming Language :: Python :: 3.6",
 | 
						|
                    "Programming Language :: Python :: 3.7",
 | 
						|
                    "Programming Language :: Python :: 3.8",
 | 
						|
                    "Programming Language :: Python :: 3.9",
 | 
						|
                ],
 | 
						|
                "requires": [],
 | 
						|
                "requires_dist": [
 | 
						|
                    "jupyter-server",
 | 
						|
                    "nbdime (~=3.1)",
 | 
						|
                    "nbformat",
 | 
						|
                    "packaging",
 | 
						|
                    "pexpect",
 | 
						|
                    "coverage ; extra == 'dev'",
 | 
						|
                    "jupyterlab (~=3.0) ; extra == 'dev'",
 | 
						|
                    "pre-commit ; extra == 'dev'",
 | 
						|
                    "pytest ; extra == 'dev'",
 | 
						|
                    "pytest-asyncio ; extra == 'dev'",
 | 
						|
                    "pytest-cov ; extra == 'dev'",
 | 
						|
                    "pytest-tornasync ; extra == 'dev'",
 | 
						|
                    "coverage ; extra == 'tests'",
 | 
						|
                    "jupyterlab (~=3.0) ; extra == 'tests'",
 | 
						|
                    "pre-commit ; extra == 'tests'",
 | 
						|
                    "pytest ; extra == 'tests'",
 | 
						|
                    "pytest-asyncio ; extra == 'tests'",
 | 
						|
                    "pytest-cov ; extra == 'tests'",
 | 
						|
                    "pytest-tornasync ; extra == 'tests'",
 | 
						|
                    "hybridcontents ; extra == 'tests'",
 | 
						|
                    "jupytext ; extra == 'tests'",
 | 
						|
                ],
 | 
						|
                "provides": [],
 | 
						|
                "provides_dist": [],
 | 
						|
                "obsoletes": [],
 | 
						|
                "obsoletes_dist": [],
 | 
						|
                "requires_python": "<4,>=3.6",
 | 
						|
                "requires_external": [],
 | 
						|
                "_pypi_ordering": 55,
 | 
						|
                "downloads": {"last_day": -1, "last_week": -1, "last_month": -1},
 | 
						|
                "cheesecake_code_kwalitee_id": None,
 | 
						|
                "cheesecake_documentation_id": None,
 | 
						|
                "cheesecake_installability_id": None,
 | 
						|
            }
 | 
						|
            if n == "jupyterlab-git"
 | 
						|
            else {
 | 
						|
                "name": "jupyterlab-github",
 | 
						|
                "version": "3.0.1",
 | 
						|
                "stable_version": None,
 | 
						|
                "bugtrack_url": None,
 | 
						|
                "package_url": "https://pypi.org/project/jupyterlab-github/",
 | 
						|
                "release_url": "https://pypi.org/project/jupyterlab-github/3.0.1/",
 | 
						|
                "docs_url": None,
 | 
						|
                "home_page": "",
 | 
						|
                "download_url": "",
 | 
						|
                "project_url": "",
 | 
						|
                "project_urls": {
 | 
						|
                    "Homepage": "https://github.com/jupyterlab/jupyterlab-github/blob/main/README.md",
 | 
						|
                    "Bug Tracker": "https://github.com/jupyterlab/jupyterlab-github/issues",
 | 
						|
                    "Source Code": "https://github.com/jupyterlab/jupyterlab-github",
 | 
						|
                },
 | 
						|
                "author": "Ian Rose",
 | 
						|
                "author_email": "jupyter@googlegroups.com",
 | 
						|
                "maintainer": "",
 | 
						|
                "maintainer_email": "",
 | 
						|
                "summary": "JupyterLab viewer for GitHub repositories",
 | 
						|
                "license": "BSD-3-Clause",
 | 
						|
                "keywords": "Jupyter,JupyterLab,JupyterLab3",
 | 
						|
                "platform": "Linux",
 | 
						|
                "classifiers": [
 | 
						|
                    "Framework :: Jupyter",
 | 
						|
                    "Framework :: Jupyter :: JupyterLab",
 | 
						|
                    "Framework :: Jupyter :: JupyterLab :: 3",
 | 
						|
                    "Framework :: Jupyter :: JupyterLab :: Extensions",
 | 
						|
                    "Framework :: Jupyter :: JupyterLab :: Extensions :: Prebuilt",
 | 
						|
                    "License :: OSI Approved :: BSD License",
 | 
						|
                    "Programming Language :: Python",
 | 
						|
                    "Programming Language :: Python :: 3",
 | 
						|
                    "Programming Language :: Python :: 3.6",
 | 
						|
                    "Programming Language :: Python :: 3.7",
 | 
						|
                    "Programming Language :: Python :: 3.8",
 | 
						|
                    "Programming Language :: Python :: 3.9",
 | 
						|
                ],
 | 
						|
                "requires": [],
 | 
						|
                "requires_dist": ["jupyterlab (~=3.0)"],
 | 
						|
                "provides": [],
 | 
						|
                "provides_dist": [],
 | 
						|
                "obsoletes": [],
 | 
						|
                "obsoletes_dist": [],
 | 
						|
                "requires_python": ">=3.6",
 | 
						|
                "requires_external": [],
 | 
						|
                "_pypi_ordering": 12,
 | 
						|
                "downloads": {"last_day": -1, "last_week": -1, "last_month": -1},
 | 
						|
                "cheesecake_code_kwalitee_id": None,
 | 
						|
                "cheesecake_documentation_id": None,
 | 
						|
                "cheesecake_installability_id": None,
 | 
						|
            }
 | 
						|
        )
 | 
						|
 | 
						|
    manager._fetch_package_metadata = mock_pkg_metadata
 | 
						|
 | 
						|
    extensions = await manager.list_extensions("git")
 | 
						|
 | 
						|
    assert extensions == ([extension1, extension2], 1)
 | 
						|
 | 
						|
 | 
						|
async def test_PyPiExtensionManager_custom_server_url():
 | 
						|
    BASE_URL = "https://mylocal.pypi.server/pypi"  # noqa
 | 
						|
 | 
						|
    parent = Configurable(config=Config({"PyPIExtensionManager": {"base_url": BASE_URL}}))
 | 
						|
 | 
						|
    manager = PyPIExtensionManager(parent=parent)
 | 
						|
 | 
						|
    assert manager.base_url == BASE_URL
 | 
						|
 | 
						|
 | 
						|
LEVELS = ["user", "sys_prefix", "system"]
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.parametrize("level", LEVELS)
 | 
						|
async def test_PyPiExtensionManager_custom_level(level):
 | 
						|
    parent = Configurable(config=Config({"PyPIExtensionManager": {"level": level}}))
 | 
						|
    manager = PyPIExtensionManager(parent=parent)
 | 
						|
    assert manager.level == level
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.parametrize("level", LEVELS)
 | 
						|
async def test_PyPiExtensionManager_inherits_custom_level(level):
 | 
						|
    parent = Configurable(config=Config({"PluginManager": {"level": level}}))
 | 
						|
    manager = PyPIExtensionManager(parent=parent)
 | 
						|
    assert manager.level == level
 | 
						|
 | 
						|
 | 
						|
@pytest.mark.parametrize("level", LEVELS)
 | 
						|
async def test_PluginManager_custom_level(level):
 | 
						|
    parent = Configurable(config=Config({"PluginManager": {"level": level}}))
 | 
						|
    manager = PluginManager(parent=parent)
 | 
						|
    assert manager.level == level
 | 
						|
 | 
						|
 | 
						|
async def test_PluginManager_default_level():
 | 
						|
    manager = PluginManager()
 | 
						|
    assert manager.level == "sys_prefix"
 |