jrnl/tests/lib/fixtures.py
Jonathan Wren f53110c69b
Rework how all output and messaging works in jrnl (#1475)
* fix missed statement from last PR
* replace print statement for adding an entry to a journal
* clean up linting and format
* change print statement over to new print_msg function
* make print_msg always print to stderr
* change print statement over to new print_msg function
* update importer to use new message function
* update yaml format to use new message function
* code cleanup
* update yaml format to use new message function
* update yaml format to use new exception handling
* update Journal class to use new message function
* update install module to use new message function
* update config module to use new message function
* update upgrade module to properly use new message and exception handling
* fix typo
* update upgrade module to use new message handling
* update welcome message to use new handling
* update upgrade module to use new message handling
* update upgrade module journal summaries to use new message handling
* take out old code
* update upgrade module to use new message handling
* update upgrade module to use new message handling
* update more modules to use new message handling
* take out old comment
* update deprecated_cmd to use new message handling
* update text_exporter with new message handling, get rid of old color constants
* get rid of hardcoded text
* whitespace changes
* rework MsgType into MsgStyle so messages can have different styles
* add comment
* Move around code to separate concerns of each function a bit more
* update create_password and yesno prompt functions for new messaging
* fix missing newline for keyboard interrupts
* fix misc linting
* fix bug with panel titles always showing 'error' after one error
* fix missing import
* update debug output after uncaught exception
* update exception for new exception handling
* rewrite yesno function to use new centralized messages
* reduce the debug output slightly
* clean up print_msgs function
* clean up create_password function
* clean up misc linting
* rename screen_input to hide_input to be more clear
* update encrypted journal prompt to use new messaging functionality
* fix typo in message key
* move rich console into function so we can mock properly
* update password mock to use rich console instead of getpass
* add more helpful output to then step
* fix test by updating expected output
* update message to use new functionality
* rework mocks in test suite for new messaging functionality
* fix linting issue
* fix more tests
* fix more tests
* fix more tests
* fix more tests
* fix merge bug
* update prompt_action_entries to use new messaging functionality
* Add new input_method "type"
  This does the same thing as input_method "pipe" but is more clear what
  it's doing (typing text into the builtin composer)
* get rid of old commented code
* get rid of unused code
* move some files around

Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
2022-06-11 13:32:11 -07:00

320 lines
7.3 KiB
Python

# Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
from collections import defaultdict
import os
from pathlib import Path
import tempfile
from collections.abc import Iterable
from keyring import backend
from keyring import errors
from pytest import fixture
from unittest.mock import patch
from unittest.mock import Mock
from .helpers import get_fixture
import toml
from rich.console import Console
from jrnl.config import load_config
from jrnl.os_compat import split_args
# --- Keyring --- #
@fixture
def keyring():
return NoKeyring()
@fixture
def keyring_type():
return "default"
class TestKeyring(backend.KeyringBackend):
"""A test keyring that just stores its values in a hash"""
priority = 1
keys = defaultdict(dict)
def set_password(self, servicename, username, password):
self.keys[servicename][username] = password
def get_password(self, servicename, username):
return self.keys[servicename].get(username)
def delete_password(self, servicename, username):
self.keys[servicename][username] = None
class NoKeyring(backend.KeyringBackend):
"""A keyring that simulated an environment with no keyring backend."""
priority = 2
keys = defaultdict(dict)
def set_password(self, servicename, username, password):
raise errors.NoKeyringError
def get_password(self, servicename, username):
raise errors.NoKeyringError
def delete_password(self, servicename, username):
raise errors.NoKeyringError
class FailedKeyring(backend.KeyringBackend):
"""A keyring that cannot be retrieved."""
priority = 2
def set_password(self, servicename, username, password):
raise errors.KeyringError
def get_password(self, servicename, username):
raise errors.KeyringError
def delete_password(self, servicename, username):
raise errors.KeyringError
# ----- Misc ----- #
@fixture
def cli_run(
mock_factories,
mock_args,
mock_is_tty,
mock_config_path,
mock_editor,
mock_user_input,
mock_overrides,
):
# Check if we need more mocks
mock_factories.update(mock_args)
mock_factories.update(mock_is_tty)
mock_factories.update(mock_overrides)
mock_factories.update(mock_editor)
mock_factories.update(mock_config_path)
mock_factories.update(mock_user_input)
return {
"status": 0,
"stdout": None,
"stderr": None,
"mocks": {},
"mock_factories": mock_factories,
}
@fixture
def mock_factories():
return {}
@fixture
def mock_args(cache_dir, request):
def _mock_args():
command = get_fixture(request, "command", "")
if cache_dir["exists"]:
command = command.format(cache_dir=cache_dir["path"])
args = split_args(command)
return patch("sys.argv", ["jrnl"] + args)
return {"args": _mock_args}
@fixture
def mock_is_tty(is_tty):
return {"is_tty": lambda: patch("sys.stdin.isatty", return_value=is_tty)}
@fixture
def mock_overrides(config_in_memory):
from jrnl.override import apply_overrides
def my_overrides(*args, **kwargs):
result = apply_overrides(*args, **kwargs)
config_in_memory["overrides"] = result
return result
return {
"overrides": lambda: patch(
"jrnl.jrnl.apply_overrides", side_effect=my_overrides
)
}
@fixture
def mock_config_path(request):
config_path = get_fixture(request, "config_path")
if not config_path:
return {}
return {
"config_path_install": lambda: patch(
"jrnl.install.get_config_path", return_value=config_path
),
"config_path_config": lambda: patch(
"jrnl.config.get_config_path", return_value=config_path
),
}
@fixture
def temp_dir():
return tempfile.TemporaryDirectory()
@fixture
def working_dir(request):
return os.path.join(request.config.rootpath, "tests")
@fixture
def toml_version(working_dir):
pyproject = os.path.join(working_dir, "..", "pyproject.toml")
pyproject_contents = toml.load(pyproject)
return pyproject_contents["tool"]["poetry"]["version"]
@fixture
def input_method():
return ""
@fixture
def cache_dir():
return {"exists": False, "path": ""}
@fixture
def str_value():
return ""
@fixture
def should_not():
return False
@fixture
def mock_user_input(request, password_input, stdin_input):
def _mock_user_input():
# user_input needs to be here because we don't know it until cli_run starts
user_input = get_fixture(request, "all_input", None)
if user_input is None:
user_input = Exception("Unexpected call for user input")
else:
user_input = iter(user_input.splitlines())
def mock_console_input(**kwargs):
if kwargs["password"] and not isinstance(password_input, Exception):
return password_input
if isinstance(user_input, Iterable):
return next(user_input)
# exceptions
return user_input if not kwargs["password"] else password_input
mock_console = Mock(wraps=Console(stderr=True))
mock_console.input = Mock(side_effect=mock_console_input)
return patch("jrnl.output._get_console", return_value=mock_console)
return {
"user_input": _mock_user_input,
"stdin_input": lambda: patch("sys.stdin.read", side_effect=stdin_input),
}
@fixture
def password_input(request):
password_input = get_fixture(request, "password", None)
if password_input is None:
password_input = Exception("Unexpected call for password input")
return password_input
@fixture
def stdin_input(request, is_tty):
stdin_input = get_fixture(request, "all_input", None)
if stdin_input is None or is_tty:
stdin_input = Exception("Unexpected call for stdin input")
else:
stdin_input = [stdin_input]
return stdin_input
@fixture
def is_tty(input_method):
assert input_method in ["", "enter", "pipe", "type"]
return input_method not in ["pipe", "type"]
@fixture
def config_on_disk(config_path):
return load_config(config_path)
@fixture
def config_in_memory():
return dict()
@fixture
def journal_name():
return None
@fixture
def which_output_stream():
return None
@fixture
def editor_input():
return None
@fixture
def num_args():
return None
@fixture
def parsed_output():
return {"lang": None, "obj": None}
@fixture
def editor_state():
return {
"command": "",
"intent": {"method": "r", "input": None},
"tmpfile": {"name": None, "content": None},
}
@fixture
def mock_editor(editor_state):
def _mock_editor(editor_command):
tmpfile = editor_command[-1]
editor_state["command"] = editor_command
editor_state["tmpfile"]["name"] = tmpfile
Path(tmpfile).touch()
with open(tmpfile, editor_state["intent"]["method"]) as f:
# Touch the file so jrnl knows it was edited
if editor_state["intent"]["input"] is not None:
f.write(editor_state["intent"]["input"])
file_content = f.read()
editor_state["tmpfile"]["content"] = file_content
return {"editor": lambda: patch("subprocess.call", side_effect=_mock_editor)}