Files
yunohost/tests/test_app_catalog.py
machine-a-tsoins edb4c397df
Some checks failed
Check for new n releases / updater (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
🍽 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
2026-07-03 17:20:06 +00:00

268 lines
8.6 KiB
Python

#!/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