| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- import json
- import lzma
- from pathlib import Path
- from unittest.mock import Mock, patch
- from metadata_manager import ManifestClient, ManifestIndex, ManifestJSON
- from metadata_manager.firmware_server.client import _CacheMeta
- from metadata_manager.firmware_server.index import latest_features_txt_url
- FIXTURES_DIR = Path(__file__).parent / "fixtures"
- SAMPLE_MANIFEST = json.loads((FIXTURES_DIR / "manifest_sample.json").read_text())
- SAMPLE_JSON_BYTES = json.dumps(SAMPLE_MANIFEST).encode("utf-8")
- MANIFEST_XZ_URL = "https://firmware.ardupilot.org/manifest.json.xz"
- class TestManifestIndex:
- def test_builds_releases_for_copter_heli_and_tracker(self):
- index = ManifestIndex.build(SAMPLE_MANIFEST)
- copter = index.get_releases("copter")
- stable = [r for r in copter if r.release_type == "stable" and r.version_number == "4.6.3"]
- assert len(stable) == 1
- assert stable[0].commit_reference.startswith("aaaa")
- heli = index.get_releases("heli")
- heli_stable = [r for r in heli if r.release_type == "stable" and r.version_number == "4.6.3"]
- assert len(heli_stable) == 1
- assert heli_stable[0].commit_reference.startswith("bbbb")
- tracker = index.get_releases("tracker")
- assert any(r.version_number == "4.6.3" for r in tracker)
- latest = [r for r in copter if r.release_type == "latest"]
- assert len(latest) == 1
- assert latest[0].commit_reference.startswith("eeee")
- assert latest[0].version_number == "NA"
- def test_indexes_board_artifacts_for_vehicle_version_board(self):
- index = ManifestIndex.build(SAMPLE_MANIFEST)
- copter_artifacts = index.get_board_artifacts(
- "copter", "stable", "4.6.3", "CubeOrange"
- )
- assert len(copter_artifacts) == 1
- assert copter_artifacts[0].name == "arducopter.apj"
- assert copter_artifacts[0].format == "apj"
- assert copter_artifacts[0].url.endswith("/Copter/stable-4.6.3/CubeOrange/arducopter.apj")
- heli_artifacts = index.get_board_artifacts(
- "heli", "stable", "4.6.3", "CubeOrange"
- )
- assert len(heli_artifacts) == 1
- assert heli_artifacts[0].name == "arducopter-heli.apj"
- latest_artifacts = index.get_board_artifacts(
- "copter", "latest", "NA", "CubeOrange"
- )
- assert len(latest_artifacts) == 1
- assert latest_artifacts[0].url.endswith("/Copter/latest/CubeOrange/arducopter.apj")
- def test_board_artifacts_dedupe_generic_stable_alias(self):
- index = ManifestIndex.build(SAMPLE_MANIFEST)
- copter_artifacts = index.get_board_artifacts(
- "copter", "stable", "4.6.3", "CubeOrange"
- )
- assert len(copter_artifacts) == 1
- assert copter_artifacts[0].url.endswith("/Copter/stable-4.6.3/CubeOrange/arducopter.apj")
- heli_artifacts = index.get_board_artifacts(
- "heli", "stable", "4.6.3", "CubeOrange"
- )
- assert len(heli_artifacts) == 1
- assert heli_artifacts[0].name == "arducopter-heli.apj"
- latest_artifacts = index.get_board_artifacts(
- "copter", "latest", "NA", "CubeOrange"
- )
- assert len(latest_artifacts) == 1
- assert latest_artifacts[0].url.endswith("/Copter/latest/CubeOrange/arducopter.apj")
- def test_board_artifacts_missing_returns_empty_list(self):
- index = ManifestIndex.build(SAMPLE_MANIFEST)
- assert index.get_board_artifacts("copter", "stable", "4.6.3", "UnknownBoard") == []
- def test_get_features_txt_url_from_manifest_artifact(self):
- index = ManifestIndex.build(SAMPLE_MANIFEST)
- url = index.get_features_txt_url("copter", "stable", "4.6.3", "CubeOrange")
- assert url == (
- "https://firmware.ardupilot.org/Copter/stable-4.6.3/"
- "CubeOrange/features.txt"
- )
- heli_url = index.get_features_txt_url("heli", "stable", "4.6.3", "CubeOrange")
- assert heli_url == (
- "https://firmware.ardupilot.org/Copter/stable-4.6.3/"
- "CubeOrange-heli/features.txt"
- )
- def test_get_features_txt_url_missing_board_returns_none(self):
- index = ManifestIndex.build(SAMPLE_MANIFEST)
- assert index.get_features_txt_url(
- "copter", "stable", "4.6.3", "UnknownBoard"
- ) is None
- class TestLatestFeaturesTxtUrl:
- def test_copter_board(self):
- assert latest_features_txt_url("copter", "CubeOrange") == (
- "https://firmware.ardupilot.org/Copter/latest/CubeOrange/features.txt"
- )
- def test_heli_board(self):
- assert latest_features_txt_url("heli", "CubeOrange") == (
- "https://firmware.ardupilot.org/Copter/latest/CubeOrange-heli/features.txt"
- )
- class TestManifestJSONFeaturesUrl:
- def test_tag_release_uses_latest_url(self):
- manifest_json = ManifestJSON(url="https://example.com/manifest.json", cache_dir="/tmp")
- assert manifest_json.get_features_txt_url(
- "copter", "tag", "my-feature", "CubeOrange"
- ) == latest_features_txt_url("copter", "CubeOrange")
- def test_stable_release_uses_manifest_index(self):
- manifest_json = ManifestJSON(url="https://example.com/manifest.json", cache_dir="/tmp")
- manifest_json._index = ManifestIndex.build(SAMPLE_MANIFEST)
- assert manifest_json.get_features_txt_url(
- "copter", "stable", "4.6.3", "CubeOrange"
- ) == (
- "https://firmware.ardupilot.org/Copter/stable-4.6.3/"
- "CubeOrange/features.txt"
- )
- def test_unavailable_manifest_returns_none_for_stable(self):
- manifest_json = ManifestJSON(url="https://example.com/manifest.json", cache_dir="/tmp")
- assert manifest_json.get_features_txt_url(
- "copter", "stable", "4.6.3", "CubeOrange"
- ) is None
- class TestManifestClientCache:
- def test_uses_cache_on_304(self, tmp_path):
- client = ManifestClient(
- url=MANIFEST_XZ_URL,
- cache_dir=str(tmp_path),
- )
- client._write_cache(
- b'{"format-version":"1.0.0","firmware":[]}',
- _CacheMeta(etag='"abc"', last_modified="Mon, 01 Jan 2024 00:00:00 GMT"),
- )
- response = Mock(status_code=304, headers={}, content=b"")
- with patch(
- "metadata_manager.firmware_server.client.requests.get",
- return_value=response,
- ) as mock_get:
- raw = client.fetch_raw()
- assert raw == b'{"format-version":"1.0.0","firmware":[]}'
- mock_get.assert_called_once_with(
- MANIFEST_XZ_URL,
- headers={
- "User-Agent": "CustomBuild/1.0",
- "If-None-Match": '"abc"',
- "If-Modified-Since": "Mon, 01 Jan 2024 00:00:00 GMT",
- },
- timeout=120,
- )
- def test_download_decompresses_xz_manifest(self, tmp_path):
- client = ManifestClient(
- url=MANIFEST_XZ_URL,
- cache_dir=str(tmp_path),
- )
- compressed = lzma.compress(SAMPLE_JSON_BYTES)
- response = Mock(
- status_code=200,
- content=compressed,
- headers={
- "ETag": '"etag123"',
- "Last-Modified": "Mon, 01 Jan 2024 00:00:00 GMT",
- },
- )
- response.raise_for_status = Mock()
- with patch(
- "metadata_manager.firmware_server.client.requests.get",
- return_value=response,
- ):
- result = client.fetch()
- assert result == SAMPLE_MANIFEST
- assert client.cache_path.read_bytes() == SAMPLE_JSON_BYTES
- meta = _CacheMeta.from_dict(
- json.loads(client.meta_path.read_text(encoding="utf-8"))
- )
- assert meta.etag == '"etag123"'
- assert meta.last_modified == "Mon, 01 Jan 2024 00:00:00 GMT"
|