You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
apprise/test/test_cli.py

547 lines
16 KiB

# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
#
# This code is licensed under the MIT License.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from __future__ import print_function
import re
import mock
from os.path import dirname
from os.path import join
from apprise import cli
from apprise import NotifyBase
from click.testing import CliRunner
from apprise.plugins import SCHEMA_MAP
from apprise.utils import environ
try:
# Python v3.4+
from importlib import reload
except ImportError:
try:
# Python v3.0-v3.3
from imp import reload
except ImportError:
# Python v2.7
pass
# Disable logging for a cleaner testing output
import logging
logging.disable(logging.CRITICAL)
def test_apprise_cli_nux_env(tmpdir):
"""
CLI: Nux Environment
"""
class GoodNotification(NotifyBase):
def __init__(self, *args, **kwargs):
super(GoodNotification, self).__init__(*args, **kwargs)
def notify(self, **kwargs):
# Pretend everything is okay
return True
def url(self, *args, **kwargs):
# Support url()
return 'good://'
class BadNotification(NotifyBase):
def __init__(self, *args, **kwargs):
super(BadNotification, self).__init__(*args, **kwargs)
def notify(self, **kwargs):
# Force a notification failure
return False
def url(self, *args, **kwargs):
# Support url()
return 'bad://'
# Set up our notification types
SCHEMA_MAP['good'] = GoodNotification
SCHEMA_MAP['bad'] = BadNotification
runner = CliRunner()
result = runner.invoke(cli.main)
# no servers specified; we return 1 (non-zero)
assert result.exit_code == 1
result = runner.invoke(cli.main, ['-v'])
assert result.exit_code == 1
result = runner.invoke(cli.main, ['-vv'])
assert result.exit_code == 1
result = runner.invoke(cli.main, ['-vvv'])
assert result.exit_code == 1
result = runner.invoke(cli.main, ['-vvvv'])
assert result.exit_code == 1
# Display version information and exit
result = runner.invoke(cli.main, ['-V'])
assert result.exit_code == 0
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'good://localhost',
])
assert result.exit_code == 0
# Run in synchronous mode
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'good://localhost',
'--disable-async',
])
assert result.exit_code == 0
# Test Debug Mode (--debug)
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'good://localhost',
'--debug',
])
assert result.exit_code == 0
# Test Debug Mode (-D)
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'good://localhost',
'-D',
])
assert result.exit_code == 0
result = runner.invoke(cli.main, [
'-t', 'test title',
'good://localhost',
], input='test stdin body\n')
assert result.exit_code == 0
# Run in synchronous mode
result = runner.invoke(cli.main, [
'-t', 'test title',
'good://localhost',
'--disable-async',
], input='test stdin body\n')
assert result.exit_code == 0
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'bad://localhost',
])
assert result.exit_code == 1
# Run in synchronous mode
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'bad://localhost',
'-Da',
])
assert result.exit_code == 1
# Testing with the --dry-run flag reveals a successful response since we
# don't actually execute the bad:// notification; we only display it
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'bad://localhost',
'--dry-run',
])
assert result.exit_code == 0
# Write a simple text based configuration file
t = tmpdir.mkdir("apprise-obj").join("apprise")
buf = """
# Include ourselves
include {}
taga,tagb=good://localhost
tagc=good://nuxref.com
""".format(str(t))
t.write(buf)
# This will read our configuration and not send any notices at all
# because we assigned tags to all of our urls and didn't identify
# a specific match below.
# 'include' reference in configuration file would have included the file a
# second time (since recursion default is 1).
result = runner.invoke(cli.main, [
'-b', 'test config',
'--config', str(t),
])
# Even when recursion take place, tags are all honored
# so 2 is returned because nothing was notified
assert result.exit_code == 3
# This will send out 1 notification because our tag matches
# one of the entries above
# translation: has taga
result = runner.invoke(cli.main, [
'-b', 'has taga',
'--config', str(t),
'--tag', 'taga',
])
assert result.exit_code == 0
# Test recursion
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'--config', str(t),
'--tag', 'tagc',
# Invalid entry specified for recursion
'-R', 'invalid',
])
assert result.exit_code == 2
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'--config', str(t),
'--tag', 'tagc',
# missing entry specified for recursion
'--recursive-depth',
])
assert result.exit_code == 2
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'--config', str(t),
'--tag', 'tagc',
# Disable recursion (thus inclusion will be ignored)
'-R', '0',
])
assert result.exit_code == 0
# Test recursion
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'--config', str(t),
'--tag', 'tagc',
# Recurse up to 5 times
'--recursion-depth', '5',
])
assert result.exit_code == 0
# This will send out 2 notifications because by specifying 2 tag
# entries, we 'or' them together:
# translation: has taga or tagb or tagd
result = runner.invoke(cli.main, [
'-b', 'has taga OR tagc OR tagd',
'--config', str(t),
'--tag', 'taga',
'--tag', 'tagc',
'--tag', 'tagd',
])
assert result.exit_code == 0
# Write a simple text based configuration file
t = tmpdir.mkdir("apprise-obj2").join("apprise-test2")
buf = """
good://localhost/1
good://localhost/2
good://localhost/3
good://localhost/4
good://localhost/5
myTag=good://localhost/6
"""
t.write(buf)
# This will read our configuration and send a notification to
# the first 5 entries in the list, but not the one that has
# the tag associated with it
result = runner.invoke(cli.main, [
'-b', 'test config',
'--config', str(t),
])
assert result.exit_code == 0
# Test our notification type switch (it defaults to info) so we want to
# try it as a different value. Should return without a problem
result = runner.invoke(cli.main, [
'-b', '# test config',
'--config', str(t),
'-n', 'success',
])
assert result.exit_code == 0
# Test our notification type switch when set to something unsupported
result = runner.invoke(cli.main, [
'-b', 'test config',
'--config', str(t),
'--notification-type', 'invalid',
])
# An error code of 2 is returned if invalid input is specified on the
# command line
assert result.exit_code == 2
# The notification type switch is case-insensitive
result = runner.invoke(cli.main, [
'-b', 'test config',
'--config', str(t),
'--notification-type', 'WARNING',
])
assert result.exit_code == 0
# Test our formatting switch (it defaults to text) so we want to try it as
# a different value. Should return without a problem
result = runner.invoke(cli.main, [
'-b', '# test config',
'--config', str(t),
'-i', 'markdown',
])
assert result.exit_code == 0
# Test our formatting switch when set to something unsupported
result = runner.invoke(cli.main, [
'-b', 'test config',
'--config', str(t),
'--input-format', 'invalid',
])
# An error code of 2 is returned if invalid input is specified on the
# command line
assert result.exit_code == 2
# The formatting switch is not case sensitive
result = runner.invoke(cli.main, [
'-b', '# test config',
'--config', str(t),
'--input-format', 'HTML',
])
assert result.exit_code == 0
# As a way of ensuring we match the first 5 entries, we can run a
# --dry-run against the same result set above and verify the output
result = runner.invoke(cli.main, [
'-b', 'test config',
'--config', str(t),
'--dry-run',
])
assert result.exit_code == 0
lines = re.split(r'[\r\n]', result.output.strip())
# 5 lines of all good:// entries matched
assert len(lines) == 5
# Verify we match against the remaining good:// entries
for i in range(0, 5):
assert lines[i].endswith('good://')
# This will fail because nothing matches mytag. It's case sensitive
# and we would only actually match against myTag
result = runner.invoke(cli.main, [
'-b', 'has mytag',
'--config', str(t),
'--tag', 'mytag',
])
assert result.exit_code == 3
# Same command as the one identified above except we set the --dry-run
# flag. This causes our list of matched results to be printed only.
# However, since we don't match anything; we still fail with a return code
# of 2.
result = runner.invoke(cli.main, [
'-b', 'has mytag',
'--config', str(t),
'--tag', 'mytag',
'--dry-run'
])
assert result.exit_code == 3
# Here is a case where we get what was expected; we also attach a file
result = runner.invoke(cli.main, [
'-b', 'has myTag',
'--config', str(t),
'--attach', join(dirname(__file__), 'var', 'apprise-test.gif'),
'--tag', 'myTag',
])
assert result.exit_code == 0
# Testing with the --dry-run flag reveals the same positive results
# because there was at least one match
result = runner.invoke(cli.main, [
'-b', 'has myTag',
'--config', str(t),
'--tag', 'myTag',
'--dry-run',
])
assert result.exit_code == 0
#
# Test environment variables
#
# Write a simple text based configuration file
t2 = tmpdir.mkdir("apprise-obj-env").join("apprise")
buf = """
# A general one
good://localhost
# A failure (if we use the fail tag)
fail=bad://localhost
# A normal one tied to myTag
myTag=good://nuxref.com
"""
t2.write(buf)
with environ(APPRISE_URLS="good://localhost"):
# This will load okay because we defined the environment
# variable with a valid URL
result = runner.invoke(cli.main, [
'-b', 'test environment',
# Test that we ignore our tag
'--tag', 'mytag',
])
assert result.exit_code == 0
# Same action but without --tag
result = runner.invoke(cli.main, [
'-b', 'test environment',
])
assert result.exit_code == 0
with mock.patch('apprise.cli.DEFAULT_SEARCH_PATHS', []):
with environ(APPRISE_URLS=" "):
# An empty string is not valid and therefore not loaded so the below
# fails. We override the DEFAULT_SEARCH_PATHS because we don't
# want to detect ones loaded on the machine running the unit tests
result = runner.invoke(cli.main, [
'-b', 'test environment',
])
assert result.exit_code == 1
with environ(APPRISE_URLS="bad://localhost"):
result = runner.invoke(cli.main, [
'-b', 'test environment',
])
assert result.exit_code == 1
# If we specify an inline URL, it will over-ride the environment
# variable
result = runner.invoke(cli.main, [
'-t', 'test title',
'-b', 'test body',
'good://localhost',
])
assert result.exit_code == 0
# A Config file also over-rides the environment variable if
# specified on the command line:
result = runner.invoke(cli.main, [
'-b', 'has myTag',
'--config', str(t2),
'--tag', 'myTag',
])
assert result.exit_code == 0
with environ(APPRISE_CONFIG=str(t2)):
# Our configuration file will load from our environmment variable
result = runner.invoke(cli.main, [
'-b', 'has myTag',
'--tag', 'myTag',
])
assert result.exit_code == 0
with mock.patch('apprise.cli.DEFAULT_SEARCH_PATHS', []):
with environ(APPRISE_CONFIG=" "):
# We will fail to send the notification as no path was
# specified.
# We override the DEFAULT_SEARCH_PATHS because we don't
# want to detect ones loaded on the machine running the unit tests
result = runner.invoke(cli.main, [
'-b', 'my message',
])
assert result.exit_code == 1
with environ(APPRISE_CONFIG="garbage/file/path.yaml"):
# We will fail to send the notification as the path
# specified is not loadable
result = runner.invoke(cli.main, [
'-b', 'my message',
])
assert result.exit_code == 1
# We can force an over-ride by specifying a config file on the
# command line options:
result = runner.invoke(cli.main, [
'-b', 'has myTag',
'--config', str(t2),
'--tag', 'myTag',
])
assert result.exit_code == 0
# Just a general test; if both the --config and urls are specified
# then the the urls trumps all
result = runner.invoke(cli.main, [
'-b', 'has myTag',
'--config', str(t2),
'good://localhost',
'--tag', 'fail',
])
# Tags are ignored, URL specified, so it trump config
assert result.exit_code == 0
# we just repeat the test as a proof that it only executes
# the urls despite the fact the --config was specified
result = runner.invoke(cli.main, [
'-b', 'reads the url entry only',
'--config', str(t2),
'good://localhost',
'--tag', 'fail',
])
# Tags are ignored, URL specified, so it trump config
assert result.exit_code == 0
# once agian, but we call bad://
result = runner.invoke(cli.main, [
'-b', 'reads the url entry only',
'--config', str(t2),
'bad://localhost',
'--tag', 'myTag',
])
assert result.exit_code == 1
@mock.patch('platform.system')
def test_apprise_cli_windows_env(mock_system):
"""
CLI: Windows Environment
"""
# Force a windows environment
mock_system.return_value = 'Windows'
# Reload our module
reload(cli)