yunohost/src/tests/test_questions.py
Alexandre Aubin 05a459d4df Fix tests
2022-02-01 15:01:11 +01:00

2090 lines
58 KiB
Python

import sys
import pytest
import os
from mock import patch
from io import StringIO
from moulinette import Moulinette
from yunohost import domain, user
from yunohost.utils.config import (
ask_questions_and_parse_answers,
PasswordQuestion,
DomainQuestion,
PathQuestion,
BooleanQuestion,
FileQuestion,
evaluate_simple_js_expression,
)
from yunohost.utils.error import YunohostError, YunohostValidationError
"""
Argument default format:
{
"the_name": {
"type": "one_of_the_available_type", // "sting" is not specified
"ask": {
"en": "the question in english",
"fr": "the question in french"
},
"help": {
"en": "some help text in english",
"fr": "some help text in french"
},
"example": "an example value", // optional
"default", "some stuff", // optional, not available for all types
"optional": true // optional, will skip if not answered
}
}
User answers:
{"the_name": "value", ...}
"""
def test_question_empty():
ask_questions_and_parse_answers({}, {}) == []
def test_question_string():
questions = {
"some_string": {
"type": "string",
}
}
answers = {"some_string": "some_value"}
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "some_value"
def test_question_string_from_query_string():
questions = {
"some_string": {
"type": "string",
}
}
answers = "foo=bar&some_string=some_value&lorem=ipsum"
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "some_value"
def test_question_string_default_type():
questions = {"some_string": {}}
answers = {"some_string": "some_value"}
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "some_value"
def test_question_string_no_input():
questions = {"some_string": {}}
answers = {}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
def test_question_string_input():
questions = {
"some_string": {
"ask": "some question",
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "some_value"
def test_question_string_input_no_ask():
questions = {"some_string": {}}
answers = {}
with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "some_value"
def test_question_string_no_input_optional():
questions = {"some_string": {"optional": True}}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == ""
def test_question_string_optional_with_input():
questions = {
"some_string": {
"ask": "some question",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "some_value"
def test_question_string_optional_with_empty_input():
questions = {
"some_string": {
"ask": "some question",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value=""), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == ""
def test_question_string_optional_with_input_without_ask():
questions = {
"some_string": {
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "some_value"
def test_question_string_no_input_default():
questions = {
"some_string": {
"ask": "some question",
"default": "some_value",
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "some_value"
def test_question_string_input_test_ask():
ask_text = "some question"
questions = {
"some_string": {
"ask": ask_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
prompt.assert_called_with(
message=ask_text,
is_password=False,
confirm=False,
prefill="",
is_multiline=False,
autocomplete=[],
help=None,
)
def test_question_string_input_test_ask_with_default():
ask_text = "some question"
default_text = "some example"
questions = {
"some_string": {
"ask": ask_text,
"default": default_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
prompt.assert_called_with(
message=ask_text,
is_password=False,
confirm=False,
prefill=default_text,
is_multiline=False,
autocomplete=[],
help=None,
)
@pytest.mark.skip # we should do something with this example
def test_question_string_input_test_ask_with_example():
ask_text = "some question"
example_text = "some example"
questions = {
"some_string": {
"ask": ask_text,
"example": example_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
assert ask_text in prompt.call_args[1]["message"]
assert example_text in prompt.call_args[1]["message"]
@pytest.mark.skip # we should do something with this help
def test_question_string_input_test_ask_with_help():
ask_text = "some question"
help_text = "some_help"
questions = {
"some_string": {
"ask": ask_text,
"help": help_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
assert ask_text in prompt.call_args[1]["message"]
assert help_text in prompt.call_args[1]["message"]
def test_question_string_with_choice():
questions = {"some_string": {"type": "string", "choices": ["fr", "en"]}}
answers = {"some_string": "fr"}
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "fr"
def test_question_string_with_choice_prompt():
questions = {"some_string": {"type": "string", "choices": ["fr", "en"]}}
answers = {"some_string": "fr"}
with patch.object(Moulinette, "prompt", return_value="fr"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "fr"
def test_question_string_with_choice_bad():
questions = {"some_string": {"type": "string", "choices": ["fr", "en"]}}
answers = {"some_string": "bad"}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
def test_question_string_with_choice_ask():
ask_text = "some question"
choices = ["fr", "en", "es", "it", "ru"]
questions = {
"some_string": {
"ask": ask_text,
"choices": choices,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="ru") as prompt, patch.object(
os, "isatty", return_value=True
):
ask_questions_and_parse_answers(questions, answers)
assert ask_text in prompt.call_args[1]["message"]
for choice in choices:
assert choice in prompt.call_args[1]["message"]
def test_question_string_with_choice_default():
questions = {
"some_string": {
"type": "string",
"choices": ["fr", "en"],
"default": "en",
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_string"
assert out.type == "string"
assert out.value == "en"
def test_question_password():
questions = {
"some_password": {
"type": "password",
}
}
answers = {"some_password": "some_value"}
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_password"
assert out.type == "password"
assert out.value == "some_value"
def test_question_password_no_input():
questions = {
"some_password": {
"type": "password",
}
}
answers = {}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
def test_question_password_input():
questions = {
"some_password": {
"type": "password",
"ask": "some question",
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_password"
assert out.type == "password"
assert out.value == "some_value"
def test_question_password_input_no_ask():
questions = {
"some_password": {
"type": "password",
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_password"
assert out.type == "password"
assert out.value == "some_value"
def test_question_password_no_input_optional():
questions = {
"some_password": {
"type": "password",
"optional": True,
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_password"
assert out.type == "password"
assert out.value == ""
questions = {"some_password": {"type": "password", "optional": True, "default": ""}}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_password"
assert out.type == "password"
assert out.value == ""
def test_question_password_optional_with_input():
questions = {
"some_password": {
"ask": "some question",
"type": "password",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_password"
assert out.type == "password"
assert out.value == "some_value"
def test_question_password_optional_with_empty_input():
questions = {
"some_password": {
"ask": "some question",
"type": "password",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value=""), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_password"
assert out.type == "password"
assert out.value == ""
def test_question_password_optional_with_input_without_ask():
questions = {
"some_password": {
"type": "password",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_password"
assert out.type == "password"
assert out.value == "some_value"
def test_question_password_no_input_default():
questions = {
"some_password": {
"type": "password",
"ask": "some question",
"default": "some_value",
}
}
answers = {}
# no default for password!
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
@pytest.mark.skip # this should raises
def test_question_password_no_input_example():
questions = {
"some_password": {
"type": "password",
"ask": "some question",
"example": "some_value",
}
}
answers = {"some_password": "some_value"}
# no example for password!
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
def test_question_password_input_test_ask():
ask_text = "some question"
questions = {
"some_password": {
"type": "password",
"ask": ask_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
prompt.assert_called_with(
message=ask_text,
is_password=True,
confirm=False,
prefill="",
is_multiline=False,
autocomplete=[],
help=None,
)
@pytest.mark.skip # we should do something with this example
def test_question_password_input_test_ask_with_example():
ask_text = "some question"
example_text = "some example"
questions = {
"some_password": {
"type": "password",
"ask": ask_text,
"example": example_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
assert ask_text in prompt.call_args[1]["message"]
assert example_text in prompt.call_args[1]["message"]
@pytest.mark.skip # we should do something with this help
def test_question_password_input_test_ask_with_help():
ask_text = "some question"
help_text = "some_help"
questions = {
"some_password": {
"type": "password",
"ask": ask_text,
"help": help_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
assert ask_text in prompt.call_args[1]["message"]
assert help_text in prompt.call_args[1]["message"]
def test_question_password_bad_chars():
questions = {
"some_password": {
"type": "password",
"ask": "some question",
"example": "some_value",
}
}
for i in PasswordQuestion.forbidden_chars:
with pytest.raises(YunohostError), patch.object(
os, "isatty", return_value=False
):
ask_questions_and_parse_answers(questions, {"some_password": i * 8})
def test_question_password_strong_enough():
questions = {
"some_password": {
"type": "password",
"ask": "some question",
"example": "some_value",
}
}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
# too short
ask_questions_and_parse_answers(questions, {"some_password": "a"})
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, {"some_password": "password"})
def test_question_password_optional_strong_enough():
questions = {
"some_password": {
"ask": "some question",
"type": "password",
"optional": True,
}
}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
# too short
ask_questions_and_parse_answers(questions, {"some_password": "a"})
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, {"some_password": "password"})
def test_question_path():
questions = {
"some_path": {
"type": "path",
}
}
answers = {"some_path": "/some_value"}
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_path"
assert out.type == "path"
assert out.value == "/some_value"
def test_question_path_no_input():
questions = {
"some_path": {
"type": "path",
}
}
answers = {}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
def test_question_path_input():
questions = {
"some_path": {
"type": "path",
"ask": "some question",
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_path"
assert out.type == "path"
assert out.value == "/some_value"
def test_question_path_input_no_ask():
questions = {
"some_path": {
"type": "path",
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_path"
assert out.type == "path"
assert out.value == "/some_value"
def test_question_path_no_input_optional():
questions = {
"some_path": {
"type": "path",
"optional": True,
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_path"
assert out.type == "path"
assert out.value == ""
def test_question_path_optional_with_input():
questions = {
"some_path": {
"ask": "some question",
"type": "path",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_path"
assert out.type == "path"
assert out.value == "/some_value"
def test_question_path_optional_with_empty_input():
questions = {
"some_path": {
"ask": "some question",
"type": "path",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value=""), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_path"
assert out.type == "path"
assert out.value == ""
def test_question_path_optional_with_input_without_ask():
questions = {
"some_path": {
"type": "path",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="/some_value"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_path"
assert out.type == "path"
assert out.value == "/some_value"
def test_question_path_no_input_default():
questions = {
"some_path": {
"ask": "some question",
"type": "path",
"default": "some_value",
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_path"
assert out.type == "path"
assert out.value == "/some_value"
def test_question_path_input_test_ask():
ask_text = "some question"
questions = {
"some_path": {
"type": "path",
"ask": ask_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
prompt.assert_called_with(
message=ask_text,
is_password=False,
confirm=False,
prefill="",
is_multiline=False,
autocomplete=[],
help=None,
)
def test_question_path_input_test_ask_with_default():
ask_text = "some question"
default_text = "someexample"
questions = {
"some_path": {
"type": "path",
"ask": ask_text,
"default": default_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
prompt.assert_called_with(
message=ask_text,
is_password=False,
confirm=False,
prefill=default_text,
is_multiline=False,
autocomplete=[],
help=None,
)
@pytest.mark.skip # we should do something with this example
def test_question_path_input_test_ask_with_example():
ask_text = "some question"
example_text = "some example"
questions = {
"some_path": {
"type": "path",
"ask": ask_text,
"example": example_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
assert ask_text in prompt.call_args[1]["message"]
assert example_text in prompt.call_args[1]["message"]
@pytest.mark.skip # we should do something with this help
def test_question_path_input_test_ask_with_help():
ask_text = "some question"
help_text = "some_help"
questions = {
"some_path": {
"type": "path",
"ask": ask_text,
"help": help_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="some_value"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
assert ask_text in prompt.call_args[1]["message"]
assert help_text in prompt.call_args[1]["message"]
def test_question_boolean():
questions = {
"some_boolean": {
"type": "boolean",
}
}
answers = {"some_boolean": "y"}
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_boolean"
assert out.type == "boolean"
assert out.value == 1
def test_question_boolean_all_yes():
questions = {
"some_boolean": {
"type": "boolean",
}
}
for value in ["Y", "yes", "Yes", "YES", "1", 1, True, "True", "TRUE", "true"]:
out = ask_questions_and_parse_answers(questions, {"some_boolean": value})[0]
assert out.name == "some_boolean"
assert out.type == "boolean"
assert out.value == 1
def test_question_boolean_all_no():
questions = {
"some_boolean": {
"type": "boolean",
}
}
for value in ["n", "N", "no", "No", "No", "0", 0, False, "False", "FALSE", "false"]:
out = ask_questions_and_parse_answers(questions, {"some_boolean": value})[0]
assert out.name == "some_boolean"
assert out.type == "boolean"
assert out.value == 0
# XXX apparently boolean are always False (0) by default, I'm not sure what to think about that
def test_question_boolean_no_input():
questions = {
"some_boolean": {
"type": "boolean",
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.value == 0
def test_question_boolean_bad_input():
questions = {
"some_boolean": {
"type": "boolean",
}
}
answers = {"some_boolean": "stuff"}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
def test_question_boolean_input():
questions = {
"some_boolean": {
"type": "boolean",
"ask": "some question",
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="y"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.value == 1
with patch.object(Moulinette, "prompt", return_value="n"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.value == 0
def test_question_boolean_input_no_ask():
questions = {
"some_boolean": {
"type": "boolean",
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="y"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.value == 1
def test_question_boolean_no_input_optional():
questions = {
"some_boolean": {
"type": "boolean",
"optional": True,
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.value == 0
def test_question_boolean_optional_with_input():
questions = {
"some_boolean": {
"ask": "some question",
"type": "boolean",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="y"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.value == 1
def test_question_boolean_optional_with_empty_input():
questions = {
"some_boolean": {
"ask": "some question",
"type": "boolean",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value=""), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.value == 0
def test_question_boolean_optional_with_input_without_ask():
questions = {
"some_boolean": {
"type": "boolean",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="n"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.value == 0
def test_question_boolean_no_input_default():
questions = {
"some_boolean": {
"ask": "some question",
"type": "boolean",
"default": 0,
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.value == 0
def test_question_boolean_bad_default():
questions = {
"some_boolean": {
"ask": "some question",
"type": "boolean",
"default": "bad default",
}
}
answers = {}
with pytest.raises(YunohostError):
ask_questions_and_parse_answers(questions, answers)
def test_question_boolean_input_test_ask():
ask_text = "some question"
questions = {
"some_boolean": {
"type": "boolean",
"ask": ask_text,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value=0) as prompt, patch.object(
os, "isatty", return_value=True
):
ask_questions_and_parse_answers(questions, answers)
prompt.assert_called_with(
message=ask_text + " [yes | no]",
is_password=False,
confirm=False,
prefill="no",
is_multiline=False,
autocomplete=[],
help=None,
)
def test_question_boolean_input_test_ask_with_default():
ask_text = "some question"
default_text = 1
questions = {
"some_boolean": {
"type": "boolean",
"ask": ask_text,
"default": default_text,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value=1) as prompt, patch.object(
os, "isatty", return_value=True
):
ask_questions_and_parse_answers(questions, answers)
prompt.assert_called_with(
message=ask_text + " [yes | no]",
is_password=False,
confirm=False,
prefill="yes",
is_multiline=False,
autocomplete=[],
help=None,
)
def test_question_domain_empty():
questions = {
"some_domain": {
"type": "domain",
}
}
main_domain = "my_main_domain.com"
answers = {}
with patch.object(
domain, "_get_maindomain", return_value="my_main_domain.com"
), patch.object(
domain, "domain_list", return_value={"domains": [main_domain]}
), patch.object(
os, "isatty", return_value=False
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_domain"
assert out.type == "domain"
assert out.value == main_domain
def test_question_domain():
main_domain = "my_main_domain.com"
domains = [main_domain]
questions = {
"some_domain": {
"type": "domain",
}
}
answers = {"some_domain": main_domain}
with patch.object(
domain, "_get_maindomain", return_value=main_domain
), patch.object(domain, "domain_list", return_value={"domains": domains}):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_domain"
assert out.type == "domain"
assert out.value == main_domain
def test_question_domain_two_domains():
main_domain = "my_main_domain.com"
other_domain = "some_other_domain.tld"
domains = [main_domain, other_domain]
questions = {
"some_domain": {
"type": "domain",
}
}
answers = {"some_domain": other_domain}
with patch.object(
domain, "_get_maindomain", return_value=main_domain
), patch.object(domain, "domain_list", return_value={"domains": domains}):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_domain"
assert out.type == "domain"
assert out.value == other_domain
answers = {"some_domain": main_domain}
with patch.object(
domain, "_get_maindomain", return_value=main_domain
), patch.object(domain, "domain_list", return_value={"domains": domains}):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_domain"
assert out.type == "domain"
assert out.value == main_domain
def test_question_domain_two_domains_wrong_answer():
main_domain = "my_main_domain.com"
other_domain = "some_other_domain.tld"
domains = [main_domain, other_domain]
questions = {
"some_domain": {
"type": "domain",
}
}
answers = {"some_domain": "doesnt_exist.pouet"}
with patch.object(
domain, "_get_maindomain", return_value=main_domain
), patch.object(domain, "domain_list", return_value={"domains": domains}):
with pytest.raises(YunohostError), patch.object(
os, "isatty", return_value=False
):
ask_questions_and_parse_answers(questions, answers)
def test_question_domain_two_domains_default_no_ask():
main_domain = "my_main_domain.com"
other_domain = "some_other_domain.tld"
domains = [main_domain, other_domain]
questions = {
"some_domain": {
"type": "domain",
}
}
answers = {}
with patch.object(
domain, "_get_maindomain", return_value=main_domain
), patch.object(
domain, "domain_list", return_value={"domains": domains}
), patch.object(
os, "isatty", return_value=False
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_domain"
assert out.type == "domain"
assert out.value == main_domain
def test_question_domain_two_domains_default():
main_domain = "my_main_domain.com"
other_domain = "some_other_domain.tld"
domains = [main_domain, other_domain]
questions = {"some_domain": {"type": "domain", "ask": "choose a domain"}}
answers = {}
with patch.object(
domain, "_get_maindomain", return_value=main_domain
), patch.object(
domain, "domain_list", return_value={"domains": domains}
), patch.object(
os, "isatty", return_value=False
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_domain"
assert out.type == "domain"
assert out.value == main_domain
def test_question_domain_two_domains_default_input():
main_domain = "my_main_domain.com"
other_domain = "some_other_domain.tld"
domains = [main_domain, other_domain]
questions = {"some_domain": {"type": "domain", "ask": "choose a domain"}}
answers = {}
with patch.object(
domain, "_get_maindomain", return_value=main_domain
), patch.object(
domain, "domain_list", return_value={"domains": domains}
), patch.object(
os, "isatty", return_value=True
):
with patch.object(Moulinette, "prompt", return_value=main_domain):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_domain"
assert out.type == "domain"
assert out.value == main_domain
with patch.object(Moulinette, "prompt", return_value=other_domain):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_domain"
assert out.type == "domain"
assert out.value == other_domain
def test_question_user_empty():
users = {
"some_user": {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "p@ynh.local",
"fullname": "the first name the last name",
}
}
questions = {
"some_user": {
"type": "user",
}
}
answers = {}
with patch.object(user, "user_list", return_value={"users": users}):
with pytest.raises(YunohostError), patch.object(
os, "isatty", return_value=False
):
ask_questions_and_parse_answers(questions, answers)
def test_question_user():
username = "some_user"
users = {
username: {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "p@ynh.local",
"fullname": "the first name the last name",
}
}
questions = {
"some_user": {
"type": "user",
}
}
answers = {"some_user": username}
with patch.object(user, "user_list", return_value={"users": users}), patch.object(
user, "user_info", return_value={}
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_user"
assert out.type == "user"
assert out.value == username
def test_question_user_two_users():
username = "some_user"
other_user = "some_other_user"
users = {
username: {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "p@ynh.local",
"fullname": "the first name the last name",
},
other_user: {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "z@ynh.local",
"fullname": "john doe",
},
}
questions = {
"some_user": {
"type": "user",
}
}
answers = {"some_user": other_user}
with patch.object(user, "user_list", return_value={"users": users}), patch.object(
user, "user_info", return_value={}
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_user"
assert out.type == "user"
assert out.value == other_user
answers = {"some_user": username}
with patch.object(user, "user_list", return_value={"users": users}), patch.object(
user, "user_info", return_value={}
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_user"
assert out.type == "user"
assert out.value == username
def test_question_user_two_users_wrong_answer():
username = "my_username.com"
other_user = "some_other_user"
users = {
username: {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "p@ynh.local",
"fullname": "the first name the last name",
},
other_user: {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "z@ynh.local",
"fullname": "john doe",
},
}
questions = {
"some_user": {
"type": "user",
}
}
answers = {"some_user": "doesnt_exist.pouet"}
with patch.object(user, "user_list", return_value={"users": users}):
with pytest.raises(YunohostError), patch.object(
os, "isatty", return_value=False
):
ask_questions_and_parse_answers(questions, answers)
def test_question_user_two_users_no_default():
username = "my_username.com"
other_user = "some_other_user.tld"
users = {
username: {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "p@ynh.local",
"fullname": "the first name the last name",
},
other_user: {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "z@ynh.local",
"fullname": "john doe",
},
}
questions = {"some_user": {"type": "user", "ask": "choose a user"}}
answers = {}
with patch.object(user, "user_list", return_value={"users": users}):
with pytest.raises(YunohostError), patch.object(
os, "isatty", return_value=False
):
ask_questions_and_parse_answers(questions, answers)
def test_question_user_two_users_default_input():
username = "my_username.com"
other_user = "some_other_user.tld"
users = {
username: {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "p@ynh.local",
"fullname": "the first name the last name",
},
other_user: {
"ssh_allowed": False,
"username": "some_user",
"mailbox-quota": "0",
"mail": "z@ynh.local",
"fullname": "john doe",
},
}
questions = {"some_user": {"type": "user", "ask": "choose a user"}}
answers = {}
with patch.object(user, "user_list", return_value={"users": users}), patch.object(
os, "isatty", return_value=True
):
with patch.object(user, "user_info", return_value={}):
with patch.object(Moulinette, "prompt", return_value=username):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_user"
assert out.type == "user"
assert out.value == username
with patch.object(Moulinette, "prompt", return_value=other_user):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_user"
assert out.type == "user"
assert out.value == other_user
def test_question_number():
questions = {
"some_number": {
"type": "number",
}
}
answers = {"some_number": 1337}
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_number"
assert out.type == "number"
assert out.value == 1337
def test_question_number_no_input():
questions = {
"some_number": {
"type": "number",
}
}
answers = {}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
def test_question_number_bad_input():
questions = {
"some_number": {
"type": "number",
}
}
answers = {"some_number": "stuff"}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
answers = {"some_number": 1.5}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
def test_question_number_input():
questions = {
"some_number": {
"type": "number",
"ask": "some question",
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="1337"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_number"
assert out.type == "number"
assert out.value == 1337
with patch.object(Moulinette, "prompt", return_value=1337), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_number"
assert out.type == "number"
assert out.value == 1337
with patch.object(Moulinette, "prompt", return_value="0"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_number"
assert out.type == "number"
assert out.value == 0
def test_question_number_input_no_ask():
questions = {
"some_number": {
"type": "number",
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="1337"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_number"
assert out.type == "number"
assert out.value == 1337
def test_question_number_no_input_optional():
questions = {
"some_number": {
"type": "number",
"optional": True,
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_number"
assert out.type == "number"
assert out.value is None
def test_question_number_optional_with_input():
questions = {
"some_number": {
"ask": "some question",
"type": "number",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="1337"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_number"
assert out.type == "number"
assert out.value == 1337
def test_question_number_optional_with_input_without_ask():
questions = {
"some_number": {
"type": "number",
"optional": True,
}
}
answers = {}
with patch.object(Moulinette, "prompt", return_value="0"), patch.object(
os, "isatty", return_value=True
):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_number"
assert out.type == "number"
assert out.value == 0
def test_question_number_no_input_default():
questions = {
"some_number": {
"ask": "some question",
"type": "number",
"default": 1337,
}
}
answers = {}
with patch.object(os, "isatty", return_value=False):
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_number"
assert out.type == "number"
assert out.value == 1337
def test_question_number_bad_default():
questions = {
"some_number": {
"ask": "some question",
"type": "number",
"default": "bad default",
}
}
answers = {}
with pytest.raises(YunohostError), patch.object(os, "isatty", return_value=False):
ask_questions_and_parse_answers(questions, answers)
def test_question_number_input_test_ask():
ask_text = "some question"
questions = {
"some_number": {
"type": "number",
"ask": ask_text,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="1111"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
prompt.assert_called_with(
message=ask_text,
is_password=False,
confirm=False,
prefill="",
is_multiline=False,
autocomplete=[],
help=None,
)
def test_question_number_input_test_ask_with_default():
ask_text = "some question"
default_value = 1337
questions = {
"some_number": {
"type": "number",
"ask": ask_text,
"default": default_value,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="1111"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
prompt.assert_called_with(
message=ask_text,
is_password=False,
confirm=False,
prefill=str(default_value),
is_multiline=False,
autocomplete=[],
help=None,
)
@pytest.mark.skip # we should do something with this example
def test_question_number_input_test_ask_with_example():
ask_text = "some question"
example_value = 1337
questions = {
"some_number": {
"type": "number",
"ask": ask_text,
"example": example_value,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="1111"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
assert ask_text in prompt.call_args[1]["message"]
assert example_value in prompt.call_args[1]["message"]
@pytest.mark.skip # we should do something with this help
def test_question_number_input_test_ask_with_help():
ask_text = "some question"
help_value = 1337
questions = {
"some_number": {
"type": "number",
"ask": ask_text,
"help": help_value,
}
}
answers = {}
with patch.object(
Moulinette, "prompt", return_value="1111"
) as prompt, patch.object(os, "isatty", return_value=True):
ask_questions_and_parse_answers(questions, answers)
assert ask_text in prompt.call_args[1]["message"]
assert help_value in prompt.call_args[1]["message"]
def test_question_display_text():
questions = {"some_app": {"type": "display_text", "ask": "foobar"}}
answers = {}
with patch.object(sys, "stdout", new_callable=StringIO) as stdout, patch.object(
os, "isatty", return_value=True
):
ask_questions_and_parse_answers(questions, answers)
assert "foobar" in stdout.getvalue()
def test_question_file_from_cli():
FileQuestion.clean_upload_dirs()
filename = "/tmp/ynh_test_question_file"
os.system(f"rm -f {filename}")
os.system(f"echo helloworld > {filename}")
questions = {
"some_file": {
"type": "file",
}
}
answers = {"some_file": filename}
out = ask_questions_and_parse_answers(questions, answers)[0]
assert out.name == "some_file"
assert out.type == "file"
# The file is supposed to be copied somewhere else
assert out.value != filename
assert out.value.startswith("/tmp/")
assert os.path.exists(out.value)
assert "helloworld" in open(out.value).read().strip()
FileQuestion.clean_upload_dirs()
assert not os.path.exists(out.value)
def test_question_file_from_api():
FileQuestion.clean_upload_dirs()
from base64 import b64encode
b64content = b64encode(b"helloworld")
questions = {
"some_file": {
"type": "file",
}
}
answers = {"some_file": b64content}
interface_type_bkp = Moulinette.interface.type
try:
Moulinette.interface.type = "api"
out = ask_questions_and_parse_answers(questions, answers)[0]
finally:
Moulinette.interface.type = interface_type_bkp
assert out.name == "some_file"
assert out.type == "file"
assert out.value.startswith("/tmp/")
assert os.path.exists(out.value)
assert "helloworld" in open(out.value).read().strip()
FileQuestion.clean_upload_dirs()
assert not os.path.exists(out.value)
def test_normalize_boolean_nominal():
assert BooleanQuestion.normalize("yes") == 1
assert BooleanQuestion.normalize("Yes") == 1
assert BooleanQuestion.normalize(" yes ") == 1
assert BooleanQuestion.normalize("y") == 1
assert BooleanQuestion.normalize("true") == 1
assert BooleanQuestion.normalize("True") == 1
assert BooleanQuestion.normalize("on") == 1
assert BooleanQuestion.normalize("1") == 1
assert BooleanQuestion.normalize(1) == 1
assert BooleanQuestion.normalize("no") == 0
assert BooleanQuestion.normalize("No") == 0
assert BooleanQuestion.normalize(" no ") == 0
assert BooleanQuestion.normalize("n") == 0
assert BooleanQuestion.normalize("false") == 0
assert BooleanQuestion.normalize("False") == 0
assert BooleanQuestion.normalize("off") == 0
assert BooleanQuestion.normalize("0") == 0
assert BooleanQuestion.normalize(0) == 0
assert BooleanQuestion.normalize("") is None
assert BooleanQuestion.normalize(" ") is None
assert BooleanQuestion.normalize(" none ") is None
assert BooleanQuestion.normalize("None") is None
assert BooleanQuestion.normalize("noNe") is None
assert BooleanQuestion.normalize(None) is None
def test_normalize_boolean_humanize():
assert BooleanQuestion.humanize("yes") == "yes"
assert BooleanQuestion.humanize("true") == "yes"
assert BooleanQuestion.humanize("on") == "yes"
assert BooleanQuestion.humanize("no") == "no"
assert BooleanQuestion.humanize("false") == "no"
assert BooleanQuestion.humanize("off") == "no"
def test_normalize_boolean_invalid():
with pytest.raises(YunohostValidationError):
BooleanQuestion.normalize("yesno")
with pytest.raises(YunohostValidationError):
BooleanQuestion.normalize("foobar")
with pytest.raises(YunohostValidationError):
BooleanQuestion.normalize("enabled")
def test_normalize_boolean_special_yesno():
customyesno = {"yes": "enabled", "no": "disabled"}
assert BooleanQuestion.normalize("yes", customyesno) == "enabled"
assert BooleanQuestion.normalize("true", customyesno) == "enabled"
assert BooleanQuestion.normalize("enabled", customyesno) == "enabled"
assert BooleanQuestion.humanize("yes", customyesno) == "yes"
assert BooleanQuestion.humanize("true", customyesno) == "yes"
assert BooleanQuestion.humanize("enabled", customyesno) == "yes"
assert BooleanQuestion.normalize("no", customyesno) == "disabled"
assert BooleanQuestion.normalize("false", customyesno) == "disabled"
assert BooleanQuestion.normalize("disabled", customyesno) == "disabled"
assert BooleanQuestion.humanize("no", customyesno) == "no"
assert BooleanQuestion.humanize("false", customyesno) == "no"
assert BooleanQuestion.humanize("disabled", customyesno) == "no"
def test_normalize_domain():
assert DomainQuestion.normalize("https://yolo.swag/") == "yolo.swag"
assert DomainQuestion.normalize("http://yolo.swag") == "yolo.swag"
assert DomainQuestion.normalize("yolo.swag/") == "yolo.swag"
def test_normalize_path():
assert PathQuestion.normalize("") == "/"
assert PathQuestion.normalize("") == "/"
assert PathQuestion.normalize("macnuggets") == "/macnuggets"
assert PathQuestion.normalize("/macnuggets") == "/macnuggets"
assert PathQuestion.normalize(" /macnuggets ") == "/macnuggets"
assert PathQuestion.normalize("/macnuggets") == "/macnuggets"
assert PathQuestion.normalize("mac/nuggets") == "/mac/nuggets"
assert PathQuestion.normalize("/macnuggets/") == "/macnuggets"
assert PathQuestion.normalize("macnuggets/") == "/macnuggets"
assert PathQuestion.normalize("////macnuggets///") == "/macnuggets"
def test_simple_evaluate():
context = {
"a1": 1,
"b2": 2,
"c10": 10,
"foo": "bar",
"comp": "1>2",
"empty": "",
"lorem": "Lorem ipsum dolor et si qua met!",
"warning": "Warning! This sentence will fail!",
"quote": "Je s'apelle Groot",
"and_": "&&",
"object": {"a": "Security risk"},
}
supported = {
"42": 42,
"9.5": 9.5,
"'bopbidibopbopbop'": "bopbidibopbopbop",
"true": True,
"false": False,
"null": None,
# Math
"1 * (2 + 3 * (4 - 3))": 5,
"1 * (2 + 3 * (4 - 3)) > 10 - 2 || 3 * 2 > 9 - 2 * 3": True,
"(9 - 2) * 3 - 10": 11,
"12 - 2 * -2 + (3 - 4) * 3.1": 12.9,
"9 / 12 + 12 * 3 - 5": 31.75,
"9 / 12 + 12 * (3 - 5)": -23.25,
"12 > 13.1": False,
"12 < 14": True,
"12 <= 14": True,
"12 >= 14": False,
"12 == 14": False,
"12 % 5 > 3": False,
"12 != 14": True,
"9 - 1 > 10 && 3 * 5 > 10": False,
"9 - 1 > 10 || 3 * 5 > 10": True,
"a1 > 0 || a1 < -12": True,
"a1 > 0 && a1 < -12": False,
"a1 + 1 > 0 && -a1 > -12": True,
"-(a1 + 1) < 0 || -(a1 + 2) > -12": True,
"-a1 * 2": -2,
"(9 - 2) * 3 - c10": 11,
"(9 - b2) * 3 - c10": 11,
"c10 > b2": True,
# String
"foo == 'bar'": True,
"foo != 'bar'": False,
'foo == "bar" && 1 > 0': True,
"!!foo": True,
"!foo": False,
"foo": "bar",
'!(foo > "baa") || 1 > 2': False,
'!(foo > "baa") || 1 < 2': True,
'empty == ""': True,
'1 == "1"': True,
'1.0 == "1"': True,
'1 == "aaa"': False,
"'I am ' + b2 + ' years'": "I am 2 years",
"quote == 'Je s\\'apelle Groot'": True,
"lorem == 'Lorem ipsum dolor et si qua met!'": True,
"and_ == '&&'": True,
"warning == 'Warning! This sentence will fail!'": True,
# Match
"match(lorem, '^Lorem [ia]psumE?')": bool,
"match(foo, '^Lorem [ia]psumE?')": None,
"match(lorem, '^Lorem [ia]psumE?') && 1 == 1": bool,
# No code
"": False,
" ": False,
}
trigger_errors = {
"object.a": YunohostError, # Keep unsupported, for security reasons
"a1 ** b2": YunohostError, # Keep unsupported, for security reasons
"().__class__.__bases__[0].__subclasses__()": YunohostError, # Very dangerous code
"a1 > 11 ? 1 : 0": SyntaxError,
"c10 > b2 == false": YunohostError, # JS and Python doesn't do the same thing for this situation
"c10 > b2 == true": YunohostError,
}
for expression, result in supported.items():
if result == bool:
assert bool(evaluate_simple_js_expression(expression, context)), expression
else:
assert (
evaluate_simple_js_expression(expression, context) == result
), expression
for expression, error in trigger_errors.items():
with pytest.raises(error):
evaluate_simple_js_expression(expression, context)