feat: add NetBox plugin store
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import hashlib
|
||||
import io
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import _bootstrap # noqa: F401
|
||||
|
||||
from netbox_plugin_store.client import CatalogPlugin, StoreClient, StoreClientError, URLPolicy
|
||||
|
||||
|
||||
def plugin_payload():
|
||||
artifact = b"verified artifact"
|
||||
return {
|
||||
"slug": "example-plugin",
|
||||
"name": "Example",
|
||||
"summary": "Example plugin",
|
||||
"description": "README",
|
||||
"repository_url": "https://git.mrblake.cc/team/example",
|
||||
"latest_version": "1.2.0",
|
||||
"package_name": "netbox-example",
|
||||
"import_name": "netbox_example",
|
||||
"min_netbox_version": "4.6.5",
|
||||
"max_netbox_version": "4.6.8",
|
||||
"approved": True,
|
||||
"releases": [
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"download_url": "https://store.example/artifacts/example.whl",
|
||||
"sha256": hashlib.sha256(artifact).hexdigest(),
|
||||
"approved_payload_sha256": "a" * 64,
|
||||
"approved": True,
|
||||
"immutable": True,
|
||||
"min_netbox_version": "4.6.5",
|
||||
"max_netbox_version": "4.6.8",
|
||||
}
|
||||
],
|
||||
}, artifact
|
||||
|
||||
|
||||
class URLPolicyTests(unittest.TestCase):
|
||||
def test_exact_origin_and_path_prefix(self):
|
||||
policy = URLPolicy(["https://store.example/internal"])
|
||||
self.assertEqual(
|
||||
policy.check("https://store.example/internal/api/v1/plugins/"),
|
||||
"https://store.example/internal/api/v1/plugins/",
|
||||
)
|
||||
with self.assertRaises(StoreClientError):
|
||||
policy.check("https://store.example.evil/internal")
|
||||
with self.assertRaises(StoreClientError):
|
||||
policy.check("https://store.example/other")
|
||||
|
||||
def test_release_selection_checks_netbox_version(self):
|
||||
payload, _ = plugin_payload()
|
||||
plugin = CatalogPlugin.from_mapping(payload)
|
||||
self.assertEqual(plugin.select_release("4.6.8").version, "1.2.0")
|
||||
with self.assertRaises(StoreClientError):
|
||||
plugin.select_release("4.6.9")
|
||||
|
||||
|
||||
class _Response(io.BytesIO):
|
||||
def __init__(self, body):
|
||||
super().__init__(body)
|
||||
self.headers = {"Content-Length": str(len(body))}
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.close()
|
||||
|
||||
|
||||
class _Opener:
|
||||
def __init__(self, body):
|
||||
self.body = body
|
||||
|
||||
def open(self, request, timeout):
|
||||
return _Response(self.body)
|
||||
|
||||
|
||||
class DownloadTests(unittest.TestCase):
|
||||
def test_download_is_hashed_before_use(self):
|
||||
_, artifact = plugin_payload()
|
||||
client = StoreClient(
|
||||
"https://store.example", ("https://store.example",), ("https://store.example",)
|
||||
)
|
||||
with tempfile.TemporaryDirectory() as temp_name:
|
||||
target = Path(temp_name) / "plugin.whl"
|
||||
with patch("netbox_plugin_store.client.build_opener", return_value=_Opener(artifact)):
|
||||
actual, size = client.download_artifact(
|
||||
"https://store.example/artifacts/example.whl",
|
||||
target,
|
||||
expected_sha256=hashlib.sha256(artifact).hexdigest(),
|
||||
timeout=10,
|
||||
max_bytes=1_000,
|
||||
)
|
||||
self.assertEqual(actual, hashlib.sha256(artifact).hexdigest())
|
||||
self.assertEqual(size, len(artifact))
|
||||
self.assertEqual(target.read_bytes(), artifact)
|
||||
|
||||
def test_mismatched_hash_removes_download(self):
|
||||
_, artifact = plugin_payload()
|
||||
client = StoreClient(
|
||||
"https://store.example", ("https://store.example",), ("https://store.example",)
|
||||
)
|
||||
with tempfile.TemporaryDirectory() as temp_name:
|
||||
target = Path(temp_name) / "plugin.whl"
|
||||
with patch("netbox_plugin_store.client.build_opener", return_value=_Opener(artifact)):
|
||||
with self.assertRaises(StoreClientError):
|
||||
client.download_artifact(
|
||||
"https://store.example/artifacts/example.whl",
|
||||
target,
|
||||
expected_sha256="0" * 64,
|
||||
timeout=10,
|
||||
max_bytes=1_000,
|
||||
)
|
||||
self.assertFalse(target.exists())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user