🍽 Fork YunoHost — snapshot mangé par la machine à tsoins
Upstream: https://github.com/YunoHost/yunohost @ 3a5f8bac8301c450897b96cbd43a4c7d3ba750fb But (José) : transformer tout le code en bions + ploxions du xerboxion. La carte de digestion vit au labo : /yunohost-digest.json
This commit is contained in:
267
tests/test_app_catalog.py
Normal file
267
tests/test_app_catalog.py
Normal file
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2024 YunoHost Contributors
|
||||
#
|
||||
# This file is part of YunoHost (see https://yunohost.org)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
import requests_mock
|
||||
from moulinette import m18n
|
||||
from yunohost import app_catalog as app_catalog_module
|
||||
from yunohost.app_catalog import (
|
||||
APPS_CATALOG_API_VERSION,
|
||||
APPS_CATALOG_CACHE,
|
||||
APPS_CATALOG_CONF,
|
||||
APPS_CATALOG_DEFAULT_URL,
|
||||
_actual_apps_catalog_api_url,
|
||||
_load_apps_catalog,
|
||||
_read_apps_catalog_list,
|
||||
_update_apps_catalog,
|
||||
app_catalog,
|
||||
logger,
|
||||
)
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.utils.file_utils import read_json, write_to_json, write_to_yaml
|
||||
|
||||
from .conftest import message
|
||||
|
||||
APPS_CATALOG_DEFAULT_URL_FULL = _actual_apps_catalog_api_url(APPS_CATALOG_DEFAULT_URL)
|
||||
|
||||
DUMMY_APP_CATALOG = """{
|
||||
"apps": {
|
||||
"foo": {"id": "foo", "level": 4, "category": "yolo", "manifest":{"description": "Foo"}},
|
||||
"bar": {"id": "bar", "level": 7, "category": "swag", "manifest":{"description": "Bar"}}
|
||||
},
|
||||
"categories": [
|
||||
{"id": "yolo", "description": "YoLo", "title": {"en": "Yolo"}},
|
||||
{"id": "swag", "description": "sWaG", "title": {"en": "Swag"}}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class AnyStringWith(str):
|
||||
def __eq__(self, other):
|
||||
return self in other
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
# Clear apps catalog cache
|
||||
shutil.rmtree(APPS_CATALOG_CACHE, ignore_errors=True)
|
||||
|
||||
# Clear apps_catalog conf
|
||||
if os.path.exists(APPS_CATALOG_CONF):
|
||||
os.remove(APPS_CATALOG_CONF)
|
||||
|
||||
app_catalog_module._apps_catalog_cache_timestamp = 0
|
||||
app_catalog_module._apps_catalog_cache = None
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
# Clear apps catalog cache
|
||||
# Otherwise when using apps stuff after running the test,
|
||||
# we'll still have the dummy unusable list
|
||||
shutil.rmtree(APPS_CATALOG_CACHE, ignore_errors=True)
|
||||
|
||||
|
||||
#
|
||||
# ################################################
|
||||
#
|
||||
|
||||
|
||||
def test_apps_catalog_emptylist():
|
||||
# Let's imagine somebody removed the default apps catalog because uh idk they dont want to use our default apps catalog
|
||||
os.system("rm %s" % APPS_CATALOG_CONF)
|
||||
os.system("touch %s" % APPS_CATALOG_CONF)
|
||||
|
||||
apps_catalog_list = _read_apps_catalog_list()
|
||||
assert len(apps_catalog_list) == 0
|
||||
|
||||
|
||||
def test_apps_catalog_update_nominal(mocker):
|
||||
# Cache is empty
|
||||
assert not glob.glob(APPS_CATALOG_CACHE + "/*")
|
||||
|
||||
# Update
|
||||
with requests_mock.Mocker() as m:
|
||||
(_actual_apps_catalog_api_url,)
|
||||
# Mock the server response with a dummy apps catalog
|
||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
_update_apps_catalog()
|
||||
m18n.n.assert_any_call("apps_catalog_updating")
|
||||
m18n.n.assert_any_call("apps_catalog_update_success")
|
||||
|
||||
# Cache shouldn't be empty anymore empty
|
||||
assert glob.glob(APPS_CATALOG_CACHE + "/*")
|
||||
|
||||
# And if we load the catalog, we sould find
|
||||
# - foo and bar as apps (unordered),
|
||||
# - yolo and swag as categories (ordered)
|
||||
catalog = app_catalog(with_categories=True)
|
||||
|
||||
assert "apps" in catalog
|
||||
assert set(catalog["apps"].keys()) == {"foo", "bar"}
|
||||
|
||||
assert "categories" in catalog
|
||||
assert [c["id"] for c in catalog["categories"]] == ["yolo", "swag"]
|
||||
|
||||
|
||||
def test_apps_catalog_update_404(mocker):
|
||||
with requests_mock.Mocker() as m:
|
||||
# 404 error
|
||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, status_code=404)
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
mocker.spy(m18n, "n")
|
||||
_update_apps_catalog()
|
||||
m18n.n.assert_any_call("apps_catalog_failed_to_download")
|
||||
|
||||
|
||||
def test_apps_catalog_update_timeout(mocker):
|
||||
with requests_mock.Mocker() as m:
|
||||
# Timeout
|
||||
m.register_uri(
|
||||
"GET", APPS_CATALOG_DEFAULT_URL_FULL, exc=requests.exceptions.ConnectTimeout
|
||||
)
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
mocker.spy(m18n, "n")
|
||||
_update_apps_catalog()
|
||||
m18n.n.assert_any_call("apps_catalog_failed_to_download")
|
||||
|
||||
|
||||
def test_apps_catalog_update_sslerror(mocker):
|
||||
with requests_mock.Mocker() as m:
|
||||
# SSL error
|
||||
m.register_uri(
|
||||
"GET", APPS_CATALOG_DEFAULT_URL_FULL, exc=requests.exceptions.SSLError
|
||||
)
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
mocker.spy(m18n, "n")
|
||||
_update_apps_catalog()
|
||||
m18n.n.assert_any_call("apps_catalog_failed_to_download")
|
||||
|
||||
|
||||
def test_apps_catalog_update_corrupted(mocker):
|
||||
with requests_mock.Mocker() as m:
|
||||
# Corrupted json
|
||||
m.register_uri(
|
||||
"GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG[:-2]
|
||||
)
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
mocker.spy(m18n, "n")
|
||||
_update_apps_catalog()
|
||||
m18n.n.assert_any_call("apps_catalog_failed_to_download")
|
||||
|
||||
|
||||
def test_apps_catalog_load_with_empty_cache(mocker):
|
||||
# Cache is empty
|
||||
assert not glob.glob(APPS_CATALOG_CACHE + "/*")
|
||||
|
||||
# Update
|
||||
with requests_mock.Mocker() as m:
|
||||
# Mock the server response with a dummy apps catalog
|
||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||
|
||||
# Try to load the apps catalog
|
||||
# This should implicitly trigger an update in the background
|
||||
mocker.spy(m18n, "n")
|
||||
app_dict = _load_apps_catalog()["apps"]
|
||||
m18n.n.assert_any_call("apps_catalog_obsolete_cache")
|
||||
m18n.n.assert_any_call("apps_catalog_update_success")
|
||||
|
||||
# Cache shouldn't be empty anymore empty
|
||||
assert glob.glob(APPS_CATALOG_CACHE + "/*")
|
||||
|
||||
assert "foo" in app_dict.keys()
|
||||
assert "bar" in app_dict.keys()
|
||||
|
||||
|
||||
def test_apps_catalog_load_with_conflicts_between_lists(mocker):
|
||||
conf = [
|
||||
{"id": "default", "url": APPS_CATALOG_DEFAULT_URL},
|
||||
{
|
||||
"id": "default2",
|
||||
"url": APPS_CATALOG_DEFAULT_URL.replace("yunohost.org", "yolohost.org"),
|
||||
},
|
||||
]
|
||||
|
||||
write_to_yaml(APPS_CATALOG_CONF, conf)
|
||||
|
||||
# Update
|
||||
with requests_mock.Mocker() as m:
|
||||
# Mock the server response with a dummy apps catalog
|
||||
# + the same apps catalog for the second list
|
||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||
m.register_uri(
|
||||
"GET",
|
||||
APPS_CATALOG_DEFAULT_URL_FULL.replace("yunohost.org", "yolohost.org"),
|
||||
text=DUMMY_APP_CATALOG,
|
||||
)
|
||||
|
||||
# Try to load the apps catalog
|
||||
# This should implicitly trigger an update in the background
|
||||
mocker.spy(logger, "warning")
|
||||
app_dict = _load_apps_catalog()["apps"]
|
||||
logger.warning.assert_any_call(AnyStringWith("Duplicate"))
|
||||
|
||||
# Cache shouldn't be empty anymore empty
|
||||
assert glob.glob(APPS_CATALOG_CACHE + "/*")
|
||||
|
||||
assert "foo" in app_dict.keys()
|
||||
assert "bar" in app_dict.keys()
|
||||
|
||||
|
||||
def test_apps_catalog_load_with_outdated_api_version():
|
||||
# Update
|
||||
with requests_mock.Mocker() as m:
|
||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||
_update_apps_catalog()
|
||||
|
||||
# Cache shouldn't be empty anymore empty
|
||||
assert glob.glob(APPS_CATALOG_CACHE + "/*")
|
||||
|
||||
# Tweak the cache to replace the from_api_version with a different one
|
||||
for cache_file in glob.glob(APPS_CATALOG_CACHE + "/*"):
|
||||
cache_json = read_json(cache_file)
|
||||
assert cache_json["from_api_version"] == APPS_CATALOG_API_VERSION
|
||||
cache_json["from_api_version"] = 0
|
||||
write_to_json(cache_file, cache_json)
|
||||
|
||||
# Update
|
||||
with requests_mock.Mocker() as m:
|
||||
# Mock the server response with a dummy apps catalog
|
||||
m.register_uri("GET", APPS_CATALOG_DEFAULT_URL_FULL, text=DUMMY_APP_CATALOG)
|
||||
with message("apps_catalog_update_success"):
|
||||
app_dict = _load_apps_catalog()["apps"]
|
||||
|
||||
assert "foo" in app_dict.keys()
|
||||
assert "bar" in app_dict.keys()
|
||||
|
||||
# Check that we indeed have the new api number in cache
|
||||
for cache_file in glob.glob(APPS_CATALOG_CACHE + "/*"):
|
||||
cache_json = read_json(cache_file)
|
||||
assert cache_json["from_api_version"] == APPS_CATALOG_API_VERSION
|
||||
Reference in New Issue
Block a user