feat: add NetBox plugin store
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from support import FakeTransport, make_config, plugin_json, release_json, wheel_bytes
|
||||
|
||||
from netbox_store_agent.catalog import CatalogError, SecureHTTPTransport, StoreClient
|
||||
from netbox_store_agent.config import StoreSettings
|
||||
|
||||
|
||||
class CatalogTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.temporary.name)
|
||||
self.config = make_config(self.root)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.temporary.cleanup()
|
||||
|
||||
def test_trailing_slash_fallback_and_valid_wheel(self) -> None:
|
||||
artifact = wheel_bytes(self.root)
|
||||
release = release_json(
|
||||
sha256=hashlib.sha256(artifact).hexdigest(), artifact_size=len(artifact)
|
||||
)
|
||||
responses = {
|
||||
"http://store.test/api/v1/plugins/demo-plugin/": plugin_json(),
|
||||
"http://store.test/api/v1/plugins/demo-plugin/releases/1.2.3/": release,
|
||||
}
|
||||
transport = FakeTransport(responses, artifact)
|
||||
client = StoreClient(self.config, transport)
|
||||
plan = client.get_release("demo-plugin", "1.2.3")
|
||||
# Destination directory is caller-owned; use an existing operation directory.
|
||||
operation_dir = self.root / "operation"
|
||||
operation_dir.mkdir()
|
||||
downloaded = client.download_release(plan, operation_dir)
|
||||
self.assertTrue(downloaded.is_file())
|
||||
self.assertEqual(transport.json_urls[0][-1], "n")
|
||||
self.assertEqual(transport.json_urls[1][-1], "/")
|
||||
|
||||
def test_release_falls_back_to_plugin_releases_array(self) -> None:
|
||||
release = release_json()
|
||||
transport = FakeTransport(
|
||||
{"http://store.test/api/v1/plugins/demo-plugin": plugin_json([release])}
|
||||
)
|
||||
plan = StoreClient(self.config, transport).get_release("demo-plugin", "1.2.3")
|
||||
self.assertEqual(plan.version, "1.2.3")
|
||||
|
||||
def test_unapproved_or_mutable_release_rejected(self) -> None:
|
||||
for change in ({"approved": False}, {"immutable": False}, {"status": "pending"}):
|
||||
with self.subTest(change=change):
|
||||
transport = FakeTransport(
|
||||
{
|
||||
"http://store.test/api/v1/plugins/demo-plugin": plugin_json(),
|
||||
"http://store.test/api/v1/plugins/demo-plugin/releases/1.2.3": release_json(
|
||||
**change
|
||||
),
|
||||
}
|
||||
)
|
||||
with self.assertRaises(CatalogError):
|
||||
StoreClient(self.config, transport).get_release("demo-plugin", "1.2.3")
|
||||
|
||||
def test_unknown_catalog_field_fails_closed(self) -> None:
|
||||
payload = plugin_json()
|
||||
payload["internal_id"] = 7
|
||||
transport = FakeTransport({"http://store.test/api/v1/plugins/demo-plugin": payload})
|
||||
with self.assertRaises(CatalogError):
|
||||
StoreClient(self.config, transport).get_plugin("demo-plugin")
|
||||
|
||||
def test_netbox_incompatibility_rejected(self) -> None:
|
||||
transport = FakeTransport(
|
||||
{
|
||||
"http://store.test/api/v1/plugins/demo-plugin": plugin_json(
|
||||
min_netbox_version="4.7.0"
|
||||
)
|
||||
}
|
||||
)
|
||||
with self.assertRaises(CatalogError):
|
||||
StoreClient(self.config, transport).get_plugin("demo-plugin")
|
||||
|
||||
def test_wheel_distribution_must_match(self) -> None:
|
||||
artifact = wheel_bytes(self.root)
|
||||
release = release_json(
|
||||
sha256=hashlib.sha256(artifact).hexdigest(), artifact_size=len(artifact)
|
||||
)
|
||||
payload = plugin_json(package_name="another-package")
|
||||
transport = FakeTransport(
|
||||
{
|
||||
"http://store.test/api/v1/plugins/demo-plugin": payload,
|
||||
"http://store.test/api/v1/plugins/demo-plugin/releases/1.2.3": release,
|
||||
},
|
||||
artifact,
|
||||
)
|
||||
client = StoreClient(self.config, transport)
|
||||
plan = client.get_release("demo-plugin", "1.2.3")
|
||||
directory = self.root / "mismatch"
|
||||
directory.mkdir()
|
||||
with self.assertRaises(CatalogError):
|
||||
client.download_release(plan, directory)
|
||||
|
||||
def test_bearer_token_is_not_sent_to_artifact_origin(self) -> None:
|
||||
settings = StoreSettings(
|
||||
**{
|
||||
**self.config.store.__dict__,
|
||||
"base_url": "https://store.test:8443",
|
||||
"allow_http_for_testing": False,
|
||||
}
|
||||
)
|
||||
transport = SecureHTTPTransport(settings)
|
||||
transport._token = lambda: "secret" # type: ignore[method-assign]
|
||||
self.assertEqual(
|
||||
transport._headers("https://store.test:8443/api")["Authorization"],
|
||||
"Bearer secret",
|
||||
)
|
||||
self.assertNotIn(
|
||||
"Authorization", transport._headers("https://artifacts.test/release.whl")
|
||||
)
|
||||
self.assertNotIn("Authorization", transport._headers("https://store.test/api"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user